feat(args): add --tree flag for displaying in tree format

This commit is contained in:
Orhun Parmaksız 2021-12-16 23:30:10 +03:00
parent fb5616f196
commit 457cedc40f
No known key found for this signature in database
GPG key ID: F83424824B3E4B90
6 changed files with 270 additions and 17 deletions

View file

@ -22,3 +22,6 @@ pub mod config;
/// Cache manager.
pub mod cache;
/// Tree output generator.
pub mod tree;

View file

@ -4,6 +4,7 @@ use crate::sysctl::display::DisplayType;
use crate::sysctl::section::Section;
use colored::*;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::io::Write;
use std::path::PathBuf;
use sysctl::{Ctl, Sysctl as SysctlImpl};
@ -43,6 +44,12 @@ impl<'a> TryFrom<&'a Ctl> for Parameter {
}
}
impl Display for Parameter {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{} = {}", self.name, self.value.replace('\n', " "))
}
}
impl Parameter {
/// Returns the absolute name of the parameter, without the sections.
pub fn absolute_name(&self) -> Option<&str> {

234
systeroid-core/src/tree.rs Normal file
View file

@ -0,0 +1,234 @@
use std::fmt::Display;
use std::io::{Result as IoResult, Write};
/// Vertical character for connecting sequential nodes.
const VERTICAL_CHAR: char = '│';
/// Horizontal connector.
const HORIZONTAL_STR: &str = "├──";
/// Horizontal connector for the last node.
const LAST_HORIZONTAL_STR: &str = "└──";
/// Representation of a tree node.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct TreeNode {
/// Value of the node.
value: String,
/// Childs of the node.
childs: Vec<TreeNode>,
}
impl TreeNode {
/// Adds new child nodes to the tree node.
fn add<'a, I: Iterator<Item = &'a str>>(&mut self, values: &mut I) {
if let Some(value) = values.next() {
let mut found = false;
for child in self.childs.iter_mut() {
if &*child.value == value {
child.add(values);
found = true;
break;
}
}
if !found {
let new_child = TreeNode {
value: value.to_string(),
childs: Vec::new(),
};
self.childs.push(new_child);
if let Some(last_child) = self.childs.last_mut() {
last_child.add(values);
}
}
}
}
/// Prints the node to the given output.
pub fn print<Output: Write>(
&self,
out: &mut Output,
connectors: &mut Vec<bool>,
) -> IoResult<()> {
self.print_line(out, &connectors[..])?;
connectors.push(false);
for (i, child) in self.childs.iter().enumerate() {
if self.childs.len() == i + 1 {
if let Some(last_connector) = connectors.last_mut() {
*last_connector = true;
}
}
child.print(out, connectors)?;
}
connectors.pop();
Ok(())
}
/// Prints a single line with the given connectors.
fn print_line<Output: Write>(&self, output: &mut Output, connectors: &[bool]) -> IoResult<()> {
if let Some(last_connector) = connectors.last() {
for last in &connectors[..connectors.len() - 1] {
write!(output, "{} ", if *last { ' ' } else { VERTICAL_CHAR })?;
}
if *last_connector {
write!(output, "{} ", LAST_HORIZONTAL_STR)?;
} else {
write!(output, "{} ", HORIZONTAL_STR)?;
}
}
writeln!(output, "{}", self.value)?;
Ok(())
}
}
/// Representation of a tree structure.
#[derive(Debug)]
pub struct Tree {
/// Nodes of the tree.
nodes: Vec<TreeNode>,
}
impl Tree {
/// Constructs a new instance.
pub fn new<I, O>(input: &mut I, seperator: char) -> Self
where
I: Iterator<Item = O>,
O: Display,
{
let mut root = TreeNode::default();
for line in input.map(|v| v.to_string()) {
let mut components = line.split(seperator);
root.add(&mut components);
}
Self { nodes: root.childs }
}
/// Prints the full tree to the given output.
pub fn print<Output: Write>(&self, out: &mut Output) -> IoResult<()> {
for node in &self.nodes {
node.print(out, &mut Vec::new())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_single_tree_creation(lines: &[&str], seperator: char, expected_tree: TreeNode) {
let tree = Tree::new(&mut lines.iter(), seperator);
assert_eq!(1, tree.nodes.len());
assert_eq!(expected_tree, tree.nodes[0]);
}
#[test]
fn test_tree_creation1() {
let lines = ["a", "a/b", "a/b/c/d", "a/b/e"];
let e = TreeNode {
value: String::from("e"),
childs: Vec::new(),
};
let d = TreeNode {
value: String::from("d"),
childs: Vec::new(),
};
let c = TreeNode {
value: String::from("c"),
childs: vec![d],
};
let b = TreeNode {
value: String::from("b"),
childs: vec![c, e],
};
let expected_tree = TreeNode {
value: String::from("a"),
childs: vec![b],
};
test_single_tree_creation(&lines, '/', expected_tree);
}
#[test]
fn test_tree_creation2() {
let lines = ["a", "a/b/e", "a/b", "a/b/c/d"];
let e = TreeNode {
value: String::from("e"),
childs: Vec::new(),
};
let d = TreeNode {
value: String::from("d"),
childs: Vec::new(),
};
let c = TreeNode {
value: String::from("c"),
childs: vec![d],
};
let b = TreeNode {
value: String::from("b"),
childs: vec![e, c],
};
let expected_tree = TreeNode {
value: String::from("a"),
childs: vec![b],
};
test_single_tree_creation(&lines, '/', expected_tree);
}
#[test]
fn test_trees_creation() {
let lines = ["a", "a/b", "c/d"];
let d = TreeNode {
value: String::from("d"),
childs: Vec::new(),
};
let c = TreeNode {
value: String::from("c"),
childs: vec![d],
};
let b = TreeNode {
value: String::from("b"),
childs: Vec::new(),
};
let a = TreeNode {
value: String::from("a"),
childs: vec![b],
};
let tree = Tree::new(&mut lines.iter(), '/');
assert_eq!(2, tree.nodes.len());
assert_eq!(a, tree.nodes[0]);
assert_eq!(c, tree.nodes[1]);
}
#[test]
fn test_print_line() {
let value = String::from("abc\ndef");
let mut output = Vec::new();
TreeNode {
value: value.to_string(),
childs: Vec::new(),
}
.print_line(&mut output, &[])
.unwrap();
assert_eq!(b"abc\ndef\n", &*output);
let mut output = Vec::new();
TreeNode {
value: value.to_string(),
childs: Vec::new(),
}
.print_line(&mut output, &[true, false, true])
.unwrap();
assert_eq!(" │ └── abc\ndef\n".as_bytes(), &*output);
let mut output = Vec::new();
TreeNode {
value: value.to_string(),
childs: Vec::new(),
}
.print_line(&mut output, &[true, false, false])
.unwrap();
assert_eq!(" │ ├── abc\ndef\n".as_bytes(), &*output);
}
}

View file

@ -8,6 +8,7 @@ use systeroid_core::parsers::KERNEL_DOCS_PATH;
use systeroid_core::regex::Regex;
use systeroid_core::sysctl::controller::Sysctl;
use systeroid_core::sysctl::{DEPRECATED_PARAMS, SYSTEM_PRELOAD};
use systeroid_core::tree::Tree;
use systeroid_parser::globwalk;
use systeroid_parser::reader;
@ -40,24 +41,27 @@ impl<'a> App<'a> {
&mut self,
pattern: Option<Regex>,
display_deprecated: bool,
tree_output: bool,
) -> Result<()> {
self.sysctl
.parameters
.iter()
.filter(|parameter| {
if let Some(pattern) = &pattern {
return pattern.is_match(&parameter.name);
let mut parameters = self.sysctl.parameters.iter().filter(|parameter| {
if let Some(pattern) = &pattern {
return pattern.is_match(&parameter.name);
}
if !display_deprecated {
if let Some(param_name) = parameter.absolute_name() {
return !DEPRECATED_PARAMS.contains(&param_name);
}
if !display_deprecated {
if let Some(param_name) = parameter.absolute_name() {
return !DEPRECATED_PARAMS.contains(&param_name);
}
}
true
})
.try_for_each(|parameter| {
}
true
});
if tree_output {
Tree::new(&mut parameters, '.').print(&mut self.stdout)?;
} else {
parameters.try_for_each(|parameter| {
parameter.display_value(&self.sysctl.config, &mut self.stdout)
})
})?;
}
Ok(())
}
/// Updates the documentation for kernel parameters.

View file

@ -42,6 +42,8 @@ pub struct Args {
pub pattern: Option<Regex>,
/// Whether if the documentation should be shown.
pub explain: bool,
/// Whether if the output should be in tree format.
pub tree_output: bool,
/// Free string fragments.
pub values: Vec<String>,
}
@ -51,6 +53,7 @@ impl Args {
pub fn parse() -> Option<Self> {
let mut opts = Options::new();
opts.optflag("a", "all", "display all variables");
opts.optflag("T", "tree", "display all variables in tree format");
opts.optflag("A", "", "alias of -a");
opts.optflag("X", "", "alias of -a");
opts.optflag(
@ -107,7 +110,8 @@ impl Args {
|| preload_files
|| matches.opt_present("S")
|| matches.opt_present("r")
|| matches.opt_present("E");
|| matches.opt_present("E")
|| matches.opt_present("T");
if show_help || env_args.len() == 1 {
let usage = opts.usage_with_format(|opts| {
@ -157,6 +161,7 @@ impl Args {
display_type,
display_deprecated: matches.opt_present("D"),
ignore_errors: matches.opt_present("e"),
tree_output: matches.opt_present("T"),
no_pager: matches.opt_present("P"),
preload_files,
preload_system_files: matches.opt_present("S"),

View file

@ -31,7 +31,7 @@ pub fn run(args: Args) -> Result<()> {
if args.preload_system_files {
app.preload_from_system()?;
} else if args.values.is_empty() {
app.display_parameters(args.pattern, args.display_deprecated)?;
app.display_parameters(args.pattern, args.display_deprecated, args.tree_output)?;
} else if args.explain {
app.update_documentation(args.kernel_docs.as_ref())?;
for param in args.values {