Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
term: Table to JSON
Draft lorenz opened 1 year ago
10 files changed +56 -11 e130b4dc 83bc28ed
modified Cargo.lock
@@ -2164,6 +2164,8 @@ dependencies = [
 "once_cell",
 "pretty_assertions",
 "radicle-signals",
+
 "serde",
+
 "serde_json",
 "shlex",
 "tempfile",
 "termion 3.0.0",
modified radicle-cli/src/commands/follow.rs
@@ -45,9 +45,16 @@ pub enum OperationName {
}

#[derive(Debug)]
+
pub enum OutputFormat {
+
    Text,
+
    Json,
+
}
+

+
#[derive(Debug)]
pub struct Options {
    pub op: Operation,
    pub verbose: bool,
+
    pub output_format: OutputFormat,
}

impl Args for Options {
@@ -58,6 +65,7 @@ impl Args for Options {
        let mut verbose = false;
        let mut nid: Option<NodeId> = None;
        let mut alias: Option<Alias> = None;
+
        let mut output_format = OutputFormat::Text;

        while let Some(arg) = parser.next()? {
            match &arg {
@@ -76,6 +84,7 @@ impl Args for Options {

                    alias = Some(name.to_owned());
                }
+
                Long("json") => output_format = OutputFormat::Json,
                Long("verbose") | Short('v') => verbose = true,
                Long("help") | Short('h') => {
                    return Err(Error::Help.into());
@@ -90,7 +99,7 @@ impl Args for Options {
            Some(nid) => Operation::Follow { nid, alias },
            None => Operation::List { alias },
        };
-
        Ok((Options { op, verbose }, vec![]))
+
        Ok((Options { op, verbose, output_format }, vec![]))
    }
}

@@ -100,7 +109,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {

    match options.op {
        Operation::Follow { nid, alias } => follow(nid, alias, &mut node, &profile)?,
-
        Operation::List { alias } => following(&profile, alias)?,
+
        Operation::List { alias } => following(&profile, alias, options.output_format)?,
    }

    Ok(())
@@ -137,11 +146,11 @@ pub fn follow(
    Ok(())
}

-
pub fn following(profile: &Profile, alias: Option<Alias>) -> anyhow::Result<()> {
+
pub fn following(profile: &Profile, alias: Option<Alias>, output_format: OutputFormat) -> anyhow::Result<()> {
    let store = profile.policies()?;
    let aliases = profile.aliases();
    let mut t = term::Table::new(term::table::TableOptions::bordered());
-
    t.push([
+
    t.header([
        term::format::default(String::from("DID")),
        term::format::default(String::from("Alias")),
        term::format::default(String::from("Policy")),
@@ -158,7 +167,14 @@ pub fn following(profile: &Profile, alias: Option<Alias>) -> anyhow::Result<()>
                .filter(|p| p.alias.as_ref().is_some_and(|alias_| *alias_ == alias)),
        ),
    };
-
    t.print();
+

+
    match output_format {
+
        OutputFormat::Text => t.print(),
+
        OutputFormat::Json => {
+
            Paint::disable();
+
            serde_json::to_writer_pretty(std::io::stdout(), &t.to_json())?
+
        }
+
    }

    Ok(())
}
modified radicle-cli/src/commands/id.rs
@@ -504,7 +504,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let mut revisions =
                term::Table::<7, term::Label>::new(term::table::TableOptions::bordered());

-
            revisions.push([
+
            revisions.header([
                term::format::dim(String::from("●")).into(),
                term::format::bold(String::from("ID")).into(),
                term::format::bold(String::from("Title")).into(),
modified radicle-cli/src/commands/issue.rs
@@ -664,7 +664,7 @@ where
    });

    let mut table = term::Table::new(term::table::TableOptions::bordered());
-
    table.push([
+
    table.header([
        term::format::dim(String::from("●")).into(),
        term::format::bold(String::from("ID")).into(),
        term::format::bold(String::from("Title")).into(),
modified radicle-cli/src/commands/ls.rs
@@ -148,7 +148,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    if rows.is_empty() {
        term::print(term::format::italic("Nothing to show."));
    } else {
-
        table.push([
+
        table.header([
            "Name".into(),
            "RID".into(),
            "Visibility".into(),
modified radicle-cli/src/commands/node/control.rs
@@ -246,7 +246,7 @@ pub fn sessions(node: &Node) -> Result<Option<term::Table<4, term::Label>>, node
    let mut table = term::Table::new(term::table::TableOptions::bordered());
    let now = LocalTime::now();

-
    table.push([
+
    table.header([
        term::format::bold("Peer").into(),
        term::format::bold("Address").into(),
        term::format::bold("State").into(),
modified radicle-cli/src/commands/patch/list.rs
@@ -56,7 +56,7 @@ pub fn run(
        ..TableOptions::default()
    });

-
    table.push([
+
    table.header([
        term::format::dim(String::from("●")).into(),
        term::format::bold(String::from("ID")).into(),
        term::format::bold(String::from("Title")).into(),
modified radicle-cli/src/commands/sync.rs
@@ -320,7 +320,7 @@ fn sync_status(
    let local_nid = node.nid()?;
    let aliases = profile.aliases();

-
    table.push([
+
    table.header([
        term::format::dim(String::from("●")).into(),
        term::format::bold(String::from("Node")).into(),
        term::Label::blank(),
modified radicle-term/Cargo.toml
@@ -18,6 +18,8 @@ crossbeam-channel = { version = "0.5.6" }
inquire = { version = "0.7.4", default-features = false, features = ["termion", "editor"] }
libc = { version = "0.2" }
once_cell = { version = "1.13" }
+
serde = { version = "1" }
+
serde_json = { version = "1" }
shlex = { version = "1.1" }
termion = { version = "3" }
thiserror = { version = "1" }
modified radicle-term/src/table.rs
@@ -223,6 +223,31 @@ impl<const W: usize, T: Cell> Table<W, T> {
        }
        Size::new(cols, rows).constrain(c)
    }
+

+
    pub fn to_json(&self) -> serde_json::Value {
+
        assert!(self.rows.len() > 1);
+

+
        let header = {
+
            match &self.rows[0] {
+
                Row::Header(header) => header,
+
                _ => panic!("Cannot convert table to JSON. Expecting header in first row, but encountered row of different variant."),
+
            }
+
        };
+

+
        serde_json::Value::Array(self.rows[1..].iter().enumerate().filter_map(|(index, row)| {
+
            match row {
+
                Row::Data(cells) => {
+
                    let mut obj = serde_json::Map::new();
+
                    header.iter().zip(cells.iter()).for_each(|(key, value)| {
+
                        obj.insert(key.to_string(), serde_json::Value::String(value.to_string()));
+
                    });
+
                    Some(serde_json::Value::Object(obj))
+
                },
+
                Row::Divider => None,
+
                Row::Header(_) => panic!("Cannot convert table to JSON. Encountered unexpected header in row {}.", index + 1),
+
            }
+
        }).collect())
+
    }
}

#[cfg(test)]