mirror of
https://github.com/orhun/systeroid
synced 2024-07-21 18:35:03 +00:00
refactor(parser): make core module depend on parser
This commit is contained in:
parent
b95b0ebf06
commit
f7a084260f
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -326,6 +326,7 @@ name = "systeroid-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sysctl",
|
||||
"systeroid-parser",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -335,7 +336,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"globwalk",
|
||||
"regex",
|
||||
"systeroid-core",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -5,5 +5,9 @@ authors = ["Orhun Parmaksız <orhunparmaksiz@gmail.com>"]
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.29"
|
||||
sysctl = "0.4.2"
|
||||
thiserror = "1.0.29"
|
||||
|
||||
[dependencies.systeroid-parser]
|
||||
version = "0.1.0"
|
||||
path = "../systeroid-parser"
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
use crate::sysctl::Section;
|
||||
|
||||
/// Documentation of a kernel parameter.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Documentation {
|
||||
/// Name of the kernel parameter.
|
||||
pub name: String,
|
||||
/// Description of the kernel parameter.
|
||||
pub description: String,
|
||||
/// Section of the kernel parameter.
|
||||
pub section: Section,
|
||||
}
|
||||
|
||||
impl Documentation {
|
||||
/// Constructs a new instance.
|
||||
pub fn new(name: String, description: String, section: Section) -> Self {
|
||||
Self {
|
||||
name,
|
||||
description,
|
||||
section,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,17 +10,8 @@ pub enum Error {
|
|||
#[error("thread lock error: `{0}`")]
|
||||
ThreadLockError(String),
|
||||
/// Error that may occur while parsing documents.
|
||||
#[error("parse error: `{0}`")]
|
||||
ParseError(String),
|
||||
/// Error that may occur due to invalid UTF-8 strings.
|
||||
#[error("non-UTF-8 string")]
|
||||
Utf8Error,
|
||||
/// Error that may occur while traversing paths using a glob pattern.
|
||||
#[error("glob error: `{0}`")]
|
||||
GlobError(String),
|
||||
/// Error that may occur during the compilation of a regex.
|
||||
#[error("regex error: `{0}`")]
|
||||
RegexError(String),
|
||||
#[error("parser error: `{0}`")]
|
||||
ParseError(#[from] systeroid_parser::error::Error),
|
||||
/// Error that may occur while handling sysctl operations.
|
||||
#[error("sysctl error: `{0}`")]
|
||||
SysctlError(#[from] sysctl::SysctlError),
|
||||
|
|
|
@ -2,14 +2,8 @@
|
|||
|
||||
#![warn(missing_docs, clippy::unwrap_used)]
|
||||
|
||||
/// Linux kernel documentation.
|
||||
pub mod docs;
|
||||
|
||||
/// Linux kernel parameter handler.
|
||||
pub mod sysctl;
|
||||
|
||||
/// File reader.
|
||||
pub mod reader;
|
||||
|
||||
/// Error implementation.
|
||||
pub mod error;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::docs::Documentation;
|
||||
use crate::error::Result;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::Path;
|
||||
use std::result::Result as StdResult;
|
||||
use sysctl::{CtlFlags, CtlIter, Sysctl as SysctlImpl};
|
||||
use systeroid_parser::document::Document;
|
||||
|
||||
/// Sections of the sysctl documentation.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -44,7 +44,7 @@ impl<'a> From<&'a Path> for Section {
|
|||
return *section;
|
||||
}
|
||||
}
|
||||
Self::Unknown
|
||||
Self::Net
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,8 +80,8 @@ pub struct Parameter {
|
|||
pub description: Option<String>,
|
||||
/// Section of the kernel parameter.
|
||||
pub section: Section,
|
||||
/// Documentation of the kernel parameter.
|
||||
pub documentation: Option<Documentation>,
|
||||
/// Parsed document about the kernel parameter.
|
||||
pub document: Option<Document>,
|
||||
}
|
||||
|
||||
/// Sysctl wrapper for managing the kernel parameters.
|
||||
|
@ -105,35 +105,37 @@ impl Sysctl {
|
|||
value: ctl.value_string()?,
|
||||
description: ctl.description().ok(),
|
||||
section: Section::from(ctl.name()?),
|
||||
documentation: None,
|
||||
document: None,
|
||||
});
|
||||
}
|
||||
Ok(Self { parameters })
|
||||
}
|
||||
|
||||
/// Updates the description of the kernel parameters based on the parsed documentation.
|
||||
/// Updates the description of the kernel parameters based on the [`parsed document`].
|
||||
///
|
||||
/// [`parsed documentation`]: Documentation
|
||||
pub fn update_docs(&mut self, docs: Vec<Documentation>) {
|
||||
/// [`parsed document`]: Document
|
||||
pub fn update_docs(&mut self, documents: Vec<Document>) {
|
||||
for param in self
|
||||
.parameters
|
||||
.iter_mut()
|
||||
.filter(|p| p.description.is_none() || p.description.as_deref() == Some("[N/A]"))
|
||||
{
|
||||
if let Some(documentation) =
|
||||
docs.iter().find(
|
||||
|doc| match param.name.split('.').collect::<Vec<&str>>().last() {
|
||||
for document in documents
|
||||
.iter()
|
||||
.filter(|document| Section::from(document.path.as_path()) == param.section)
|
||||
{
|
||||
if let Some(paragraph) = document.paragraphs.iter().find(|paragraph| {
|
||||
match param.name.split('.').collect::<Vec<&str>>().last() {
|
||||
Some(absolute_name) => {
|
||||
absolute_name.len() > 2
|
||||
&& doc.name.contains(absolute_name)
|
||||
&& doc.section == param.section
|
||||
absolute_name.len() > 2 && paragraph.title.contains(absolute_name)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
)
|
||||
{
|
||||
param.description = Some(documentation.description.to_owned());
|
||||
param.documentation = Some(documentation.clone());
|
||||
}
|
||||
}) {
|
||||
param.description = Some(paragraph.contents.to_owned());
|
||||
param.document = Some(document.clone());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,4 @@ edition = "2021"
|
|||
[dependencies]
|
||||
regex = "1.5.4"
|
||||
globwalk = "0.8.1"
|
||||
|
||||
[dependencies.systeroid-core]
|
||||
version = "0.1.0"
|
||||
path = "../systeroid-core"
|
||||
thiserror = "1.0.29"
|
||||
|
|
71
systeroid-parser/src/document.rs
Normal file
71
systeroid-parser/src/document.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use crate::error::Error;
|
||||
use regex::Captures;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Representation of a paragraph in a [`Document`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Paragraph {
|
||||
/// Paragraph title.
|
||||
pub title: String,
|
||||
/// Raw contents of a paragraph.
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
/// Constructs a new instance.
|
||||
pub fn new(title: String, contents: String) -> Self {
|
||||
Self { title, contents }
|
||||
}
|
||||
|
||||
/// Constructs a vector of paragraphs from the given regex capture groups.
|
||||
pub fn from_captures(
|
||||
capture_group: Vec<Captures<'_>>,
|
||||
input: &str,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let mut paragraphs = Vec::new();
|
||||
for (i, captures) in capture_group.iter().enumerate() {
|
||||
let title_capture = captures
|
||||
.iter()
|
||||
.last()
|
||||
.flatten()
|
||||
.ok_or(Error::CaptureError)?;
|
||||
let content_capture = captures
|
||||
.iter()
|
||||
.next()
|
||||
.flatten()
|
||||
.ok_or(Error::CaptureError)?;
|
||||
paragraphs.push(Paragraph::new(
|
||||
title_capture.as_str().trim().to_string(),
|
||||
if let Some(next_capture) = capture_group.get(i + 1) {
|
||||
let next_capture = next_capture
|
||||
.iter()
|
||||
.next()
|
||||
.flatten()
|
||||
.ok_or(Error::CaptureError)?;
|
||||
(input[content_capture.end()..next_capture.start()])
|
||||
.trim()
|
||||
.to_string()
|
||||
} else {
|
||||
(input[content_capture.end()..]).trim().to_string()
|
||||
},
|
||||
));
|
||||
}
|
||||
Ok(paragraphs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a parsed document.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Document {
|
||||
/// Paragraphs in the document.
|
||||
pub paragraphs: Vec<Paragraph>,
|
||||
/// Source of the document.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// Constructs a new instance.
|
||||
pub fn new(paragraphs: Vec<Paragraph>, path: PathBuf) -> Self {
|
||||
Self { paragraphs, path }
|
||||
}
|
||||
}
|
21
systeroid-parser/src/error.rs
Normal file
21
systeroid-parser/src/error.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use thiserror::Error as ThisError;
|
||||
|
||||
/// Custom error type.
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error {
|
||||
/// Error that may occur during I/O operations.
|
||||
#[error("IO error: `{0}`")]
|
||||
IoError(#[from] std::io::Error),
|
||||
/// Error that may occur due to invalid UTF-8 strings.
|
||||
#[error("non-UTF-8 string")]
|
||||
Utf8Error,
|
||||
/// Error that may occur when the capture group does not exist.
|
||||
#[error("capture group does not exist")]
|
||||
CaptureError,
|
||||
/// Error that may occur while traversing paths using a glob pattern.
|
||||
#[error("glob error: `{0}`")]
|
||||
GlobError(#[from] globwalk::GlobError),
|
||||
/// Error that may occur during the compilation of a regex.
|
||||
#[error("regex error: `{0}`")]
|
||||
RegexError(#[from] regex::Error),
|
||||
}
|
|
@ -4,3 +4,12 @@
|
|||
|
||||
/// RST parser.
|
||||
pub mod parser;
|
||||
|
||||
/// Parsed document.
|
||||
pub mod document;
|
||||
|
||||
/// Error implementation.
|
||||
pub mod error;
|
||||
|
||||
/// File reader.
|
||||
pub mod reader;
|
||||
|
|
|
@ -1,65 +1,49 @@
|
|||
use regex::{Captures, RegexBuilder};
|
||||
use crate::document::{Document, Paragraph};
|
||||
use crate::error::Error;
|
||||
use crate::reader;
|
||||
use regex::{Captures, Regex, RegexBuilder};
|
||||
use std::path::Path;
|
||||
use std::result::Result as StdResult;
|
||||
use systeroid_core::docs::Documentation;
|
||||
use systeroid_core::error::{Error, Result};
|
||||
use systeroid_core::reader;
|
||||
use systeroid_core::sysctl::Section;
|
||||
|
||||
/// Parser for the reStructuredText format.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RstParser<'a> {
|
||||
/// Glob pattern to specify the files to parse.
|
||||
pub glob_path: &'a str,
|
||||
/// Regular expression to use for parsing.
|
||||
pub regex: &'a str,
|
||||
/// Section of the parsed documents.
|
||||
pub section: Option<Section>,
|
||||
pub regex: Regex,
|
||||
}
|
||||
|
||||
impl RstParser<'_> {
|
||||
/// Parses the given reStructuredText input and returns the [`documentation`] of kernel parameters.
|
||||
///
|
||||
/// [`documentation`]: Documentation
|
||||
pub fn parse(&self, kernel_docs: &Path) -> Result<Vec<Documentation>> {
|
||||
let mut param_docs = Vec::new();
|
||||
impl<'a> RstParser<'a> {
|
||||
/// Constructs a new instance.
|
||||
pub fn new(glob_path: &'a str, regex: &'a str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
glob_path,
|
||||
regex: RegexBuilder::new(regex).multi_line(true).build()?,
|
||||
})
|
||||
}
|
||||
|
||||
let regex = RegexBuilder::new(self.regex)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.map_err(|e| Error::RegexError(e.to_string()))?;
|
||||
/// Parses the files in the given base path and returns the documents.
|
||||
pub fn parse(&self, base_path: &Path) -> Result<Vec<Document>, Error> {
|
||||
let mut documents = Vec::new();
|
||||
for file in globwalk::glob(
|
||||
kernel_docs
|
||||
base_path
|
||||
.join(self.glob_path)
|
||||
.to_str()
|
||||
.ok_or(Error::Utf8Error)?,
|
||||
)
|
||||
.map_err(|e| Error::GlobError(e.to_string()))?
|
||||
)?
|
||||
.filter_map(StdResult::ok)
|
||||
{
|
||||
let section = self.section.unwrap_or_else(|| Section::from(file.path()));
|
||||
let input = reader::read_to_string(file.path())?;
|
||||
let capture_group = regex.captures_iter(&input).collect::<Vec<Captures<'_>>>();
|
||||
|
||||
for (i, captures) in capture_group.iter().enumerate() {
|
||||
let title_capture = captures.iter().last().flatten().unwrap();
|
||||
let capture = captures.iter().next().flatten().unwrap();
|
||||
|
||||
param_docs.push(Documentation::new(
|
||||
title_capture.as_str().trim().to_string(),
|
||||
if let Some(next_capture) = capture_group.get(i + 1) {
|
||||
let next_capture = next_capture.iter().next().flatten().unwrap();
|
||||
(input[capture.end()..next_capture.start()])
|
||||
.trim()
|
||||
.to_string()
|
||||
} else {
|
||||
(input[capture.end()..]).trim().to_string()
|
||||
},
|
||||
section,
|
||||
));
|
||||
}
|
||||
let capture_group = self
|
||||
.regex
|
||||
.captures_iter(&input)
|
||||
.collect::<Vec<Captures<'_>>>();
|
||||
documents.push(Document::new(
|
||||
Paragraph::from_captures(capture_group, &input)?,
|
||||
file.path().to_path_buf(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(param_docs)
|
||||
Ok(documents)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ pub mod args;
|
|||
use crate::args::Args;
|
||||
use rayon::prelude::*;
|
||||
use std::sync::Mutex;
|
||||
use systeroid_core::docs::Documentation;
|
||||
use systeroid_core::error::{Error, Result};
|
||||
use systeroid_core::sysctl::{Section, Sysctl};
|
||||
use systeroid_core::sysctl::Sysctl;
|
||||
use systeroid_parser::document::Document;
|
||||
use systeroid_parser::parser::RstParser;
|
||||
|
||||
/// Runs `systeroid`.
|
||||
|
@ -18,53 +18,48 @@ pub fn run(args: Args) -> Result<()> {
|
|||
let mut sysctl = Sysctl::init()?;
|
||||
|
||||
let parsers = vec![
|
||||
RstParser {
|
||||
glob_path: "admin-guide/sysctl/*.rst",
|
||||
regex: "^\n([a-z].*)\n[=,-]{2,}+\n\n",
|
||||
section: None,
|
||||
},
|
||||
RstParser {
|
||||
glob_path: "networking/*-sysctl.rst",
|
||||
regex: "^([a-zA-Z0-9_/-]+)[ ]-[ ][a-zA-Z].*$",
|
||||
section: Some(Section::Net),
|
||||
},
|
||||
RstParser::new("admin-guide/sysctl/*.rst", "^\n([a-z].*)\n[=,-]{2,}+\n\n")?,
|
||||
RstParser::new(
|
||||
"networking/*-sysctl.rst",
|
||||
"^([a-zA-Z0-9_/-]+)[ ]-[ ][a-zA-Z].*$",
|
||||
)?,
|
||||
];
|
||||
|
||||
let param_docs = if let Some(kernel_docs) = args.kernel_docs {
|
||||
let param_docs = Mutex::new(Vec::new());
|
||||
let documents = if let Some(kernel_docs) = args.kernel_docs {
|
||||
let documents = Mutex::new(Vec::new());
|
||||
parsers.par_iter().try_for_each(|s| {
|
||||
let mut param_docs = param_docs
|
||||
let mut documents = documents
|
||||
.lock()
|
||||
.map_err(|e| Error::ThreadLockError(e.to_string()))?;
|
||||
let mut parse = |parser: RstParser| -> Result<()> {
|
||||
param_docs.extend(parser.parse(&kernel_docs)?);
|
||||
documents.extend(parser.parse(&kernel_docs)?);
|
||||
Ok(())
|
||||
};
|
||||
parse(*s)
|
||||
parse(s.clone())
|
||||
})?;
|
||||
let param_docs = param_docs
|
||||
let documents = documents
|
||||
.lock()
|
||||
.map_err(|e| Error::ThreadLockError(e.to_string()))?
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<Documentation>>();
|
||||
Some(param_docs)
|
||||
.collect::<Vec<Document>>();
|
||||
Some(documents)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(param_docs) = param_docs {
|
||||
sysctl.update_docs(param_docs);
|
||||
if let Some(documents) = documents {
|
||||
sysctl.update_docs(documents);
|
||||
}
|
||||
|
||||
for param in sysctl.parameters {
|
||||
println!(
|
||||
"{} ({})\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n{}\n",
|
||||
"{}\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n{}\n(from: {:?})\n",
|
||||
param.name,
|
||||
param.documentation.map(|d| d.name).unwrap_or_default(),
|
||||
param
|
||||
.description
|
||||
.unwrap_or_else(|| String::from("no documentation"))
|
||||
.unwrap_or_else(|| String::from("no documentation")),
|
||||
param.document.map(|d| d.path).unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue