refactor(parser): make core module depend on parser

This commit is contained in:
Orhun Parmaksız 2021-10-28 01:31:20 +03:00
parent b95b0ebf06
commit f7a084260f
No known key found for this signature in database
GPG key ID: F83424824B3E4B90
13 changed files with 180 additions and 134 deletions

3
Cargo.lock generated
View file

@ -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]]

View file

@ -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"

View file

@ -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,
}
}
}

View file

@ -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),

View file

@ -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;

View file

@ -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;
}
}
}
}

View file

@ -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"

View 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 }
}
}

View 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),
}

View file

@ -4,3 +4,12 @@
/// RST parser.
pub mod parser;
/// Parsed document.
pub mod document;
/// Error implementation.
pub mod error;
/// File reader.
pub mod reader;

View file

@ -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)
}
}

View file

@ -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(),
);
}