Skip to content

Commit

Permalink
Merge pull request #25 from tinythings/isbm-module-sample-mode
Browse files Browse the repository at this point in the history
Implement module sample mode
  • Loading branch information
isbm authored Oct 24, 2024
2 parents 30a59a7 + f1ca418 commit b742a09
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 70 deletions.
27 changes: 22 additions & 5 deletions docs/moddev/modstruct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,29 @@ Below are currently supported and required sections and attributes:
``returns``
^^^^^^^^^^^

Description in a free form what additional data might be returned in ``data`` section of the :ref:`formatting-response`. It has two sections to describe the return data type in details.
This section is mainly used to define return data structure and it is also used as a help sample.
The following syntax is used:

``description``
.. code-block:: text
:caption: Synopsis of return data structure definition
returns:
<option|argument|$>:
:description: |
Multiline text
<structure>
From the described synopsis above:

- ``option`` or ``argument`` or ``$`` is the sections what is returned in case these options are specified.
The ``$`` symbol means "no options specified" or *the default* state.

A multiline description what the data type is all about. Type ``String``, **required**.
- ``:description`` is an internal field, used for a prefixed help section in the manual output.
- ``<structure>`` is any YAML-compliant structure that will be then converted to JSON and displayed as a sample.
Refer to the existing modules source code to see that in action.

``example``
.. important::

An actual multiline example of a returned data type in a JSON format. Type ``String``, **required**.
From the synopsis, the fied ``:description`` is prefixed with ``:`` (colon) symbol. The colon symbol marks
a field as "internal". These fields *do not belong* to the defined ``<structure>`` and will be excluded
from the final output.
70 changes: 46 additions & 24 deletions libsysinspect/src/modlib/modinit.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
use crate::util::dataconv;
use colored::Colorize;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_yaml::Value;
use std::env::args;
use std::{collections::HashMap, env::args};
use textwrap::{fill, Options};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModReturn {
description: String,
example: String,
}

impl ModReturn {
// Format return explanation
fn format(&self) -> String {
let opts = Options::new(80).initial_indent(" ").subsequent_indent(" ");
format!("{}\n\n{}\n", fill(&format!("{}. Example:", self.description.trim()), &opts), fill(self.example.trim(), opts))
}
}
static H_WIDTH: usize = 80;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModExample {
Expand Down Expand Up @@ -48,7 +38,7 @@ impl ModOption {
format!(
" {}\n{}\n",
self.name.bright_magenta().bold(),
fill(&self.description, Options::new(80).initial_indent(" ").subsequent_indent(" "))
fill(&self.description, Options::new(H_WIDTH).initial_indent(" ").subsequent_indent(" "))
)
}
}
Expand Down Expand Up @@ -80,7 +70,7 @@ impl ModArgument {
" {} {}\n{}\n",
self.name.bright_magenta().bold(),
format!("(type: {}{}{})", self.argtype, req, def).bright_magenta(),
fill(&self.description, Options::new(80).initial_indent(" ").subsequent_indent(" "))
fill(&self.description, Options::new(H_WIDTH).initial_indent(" ").subsequent_indent(" "))
)
}
}
Expand All @@ -94,7 +84,9 @@ pub struct ModInterface {
options: Vec<ModOption>,
arguments: Vec<ModArgument>,
examples: Vec<ModExample>,
returns: Option<ModReturn>,

// Map of flags/args to output data structure
returns: HashMap<String, Value>,
}

impl ModInterface {
Expand All @@ -109,6 +101,40 @@ impl ModInterface {
false
}

fn fmt_returns(&self) -> String {
let mut out: Vec<String> = Vec::new();
for (arg, data) in &self.returns {
let mut stct: HashMap<String, serde_json::Value> = HashMap::new();
let mut descr = String::new();

if let Value::Mapping(out_data) = data {
for (k, v) in out_data {
let k = dataconv::as_str(Some(k).cloned());
if k.eq(":description") {
descr.push_str(dataconv::as_str(Some(v).cloned()).trim());
} else {
stct.insert(k, json!(v));
}
}
}

let f_opts = Options::new(H_WIDTH).initial_indent(" ").subsequent_indent(" ");
if arg.eq("$") {
out.push(fill(&format!("{} {}", descr, "If no options or arguments specified:"), &f_opts).yellow().to_string());
} else {
out.push(fill(&format!("{} If {} speified:", descr, arg.bright_magenta().bold()), &f_opts).yellow().to_string());
}

out.push(fill(
&serde_json::to_string_pretty(&json!(stct)).unwrap_or_default(),
f_opts.initial_indent(" ").subsequent_indent(" "),
));
out.push("".to_string());
}

out.join("\n")
}

/// Format help string, ready to print.
pub fn help(&self) -> String {
fn args(cls: &ModInterface) -> String {
Expand All @@ -133,12 +159,8 @@ impl ModInterface {
}

fn returns(cls: &ModInterface) -> String {
if let Some(ret) = &cls.returns {
let ret_title = "Additional returned data:".bright_yellow();
return format!("\n\n{ret_title}\n\n{}", ret.format());
}

"".to_string()
let ret_title = "Returned data structure:".bright_yellow();
format!("\n\n{ret_title}\n\n{}", cls.fmt_returns())
}

let dsc_title = "Description:".bright_yellow();
Expand All @@ -160,7 +182,7 @@ impl ModInterface {
self.name.bold(),
self.version.green().bold(),
self.author,
fill(&self.description, Options::new(80).subsequent_indent(" ")).yellow(),
fill(&self.description, Options::new(H_WIDTH).subsequent_indent(" ")).yellow(),
args(self),
ex_code,
returns(self),
Expand Down
45 changes: 25 additions & 20 deletions modules/sys/net/src/mod_doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,28 @@ examples:
}
returns:
description: "Route information example"
example: |
"route-table":
[
{ "gateway": "192.168.1.1", "mask": "0" },
{
"dst": "169.254.0.0",
"mask": "16",
"proto": "boot",
"scope": "link",
},
{
"dst": "192.168.2.0",
"if": "eth0",
"mask": "24",
"proto": "kernel",
"scope": "link",
"src": "192.168.1.123",
},
]
route-table:
:description: "Returns network routing table (sample)."
retcode: 0
message: "Network data obtained"
data:
route-table:
- gateway: "192.168.1.1"
mask: "0"
- dst: "192.168.2.0"
if: "eth0"
mask: "24"
proto: "kernel"
scope: "link"
src: "192.168.1.123"

if-up:
:description: "Returns the list of all available network devices (sample)."
retcode: 0
message: "Network data obtained"
data:
if-up:
lo:
- mac: "00:00:00:00:00:00"
- IPv4: "127.0.0.1"
port: 0
29 changes: 16 additions & 13 deletions modules/sys/proc/src/mod_doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,19 @@ examples:
# Description of additional data format
returns:
description: |
Returns tabular data of "limits" and "cmd" containing an actual command line.
example: |
{
"limits": [
["attribute", "soft", "hard", "units"],
["cpu time", -1, -1, "seconds"],
["processes", 126599, 126599, "processes"],
["open files", 1024, 524288, "files"],
],
"cmd": "/lib/systemd/systemd-logind",
},
limits:
:description: |
Returns tabular data of "limits" and "cmd" containing an actual command line.
limits:
- [attribute, soft, hard, units]
- [cpu time, -1, -1, seconds]
- [processes, 126599, 126599, processes]
- [open files, 1024, 524288, files]
cmd: /lib/systemd/systemd-logind

pid:
:description: |
Includes a PID of the process.
pid: 12345
24 changes: 16 additions & 8 deletions modules/sys/run/src/mod_doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ examples:
# Description of additional data format
returns:
description: |
Returns just a regular text of the command STDOUT.
NOTE: if "disown" flag is specified, no data is returned.
example: |
{
"stdout": "..."
},
# Output data structure as a sample
# Happens by default
$:
:description: |
Returns just a regular text of the command STDOUT.
retcode: 0
message: "'uname -p' finished"
data:
stdout: "x86_64"

# Happens when "disown" flag is specified
disown:
:description: |
No data block is returned for background processes
retcode: 0
message: "'uname -p' left running on a background"

0 comments on commit b742a09

Please sign in to comment.