mirror of
https://github.com/orhun/systeroid
synced 2024-07-21 18:35:03 +00:00
feat(args): add --tree
flag for displaying in tree format
This commit is contained in:
parent
fb5616f196
commit
457cedc40f
|
@ -22,3 +22,6 @@ pub mod config;
|
|||
|
||||
/// Cache manager.
|
||||
pub mod cache;
|
||||
|
||||
/// Tree output generator.
|
||||
pub mod tree;
|
||||
|
|
|
@ -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
234
systeroid-core/src/tree.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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(¶meter.name);
|
||||
let mut parameters = self.sysctl.parameters.iter().filter(|parameter| {
|
||||
if let Some(pattern) = &pattern {
|
||||
return pattern.is_match(¶meter.name);
|
||||
}
|
||||
if !display_deprecated {
|
||||
if let Some(param_name) = parameter.absolute_name() {
|
||||
return !DEPRECATED_PARAMS.contains(¶m_name);
|
||||
}
|
||||
if !display_deprecated {
|
||||
if let Some(param_name) = parameter.absolute_name() {
|
||||
return !DEPRECATED_PARAMS.contains(¶m_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.
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue