cli: Add `rad cob log` format options

Add `--format json` and `--format pretty`.
This commit is contained in:
Lorenz Leutgeb 2024-05-21 14:19:23 +02:00 committed by cloudhead
parent 72dad241e2
commit 6147a8a2fb
No known key found for this signature in database
2 changed files with 79 additions and 34 deletions

View File

@ -5,9 +5,11 @@ use anyhow::anyhow;
use chrono::prelude::*; use chrono::prelude::*;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle::cob; use radicle::cob;
use radicle::cob::Op;
use radicle::prelude::RepoId; use radicle::prelude::RepoId;
use radicle::storage::ReadStorage; use radicle::storage::ReadStorage;
use radicle_cob::object::collaboration::list; use radicle_cob::object::collaboration::list;
use serde_json::json;
use crate::git::Rev; use crate::git::Rev;
use crate::terminal as term; use crate::terminal as term;
@ -21,20 +23,25 @@ pub const HELP: Help = Help {
Usage Usage
rad cob <command> [<option>...] rad cob <command> [<option>...]
rad cob list --repo <rid> --type <typename> rad cob list --repo <rid> --type <typename>
rad cob log --repo <rid> --type <typename> --object <oid> rad cob log --repo <rid> --type <typename> --object <oid> [<option>..]
Commands Commands
list List all COBs of a given type (--object is not needed) list List all COBs of a given type (--object is not needed)
log Print a log of all raw operations on a COB log Print a log of all raw operations on a COB
Options Log options
--help Print help --format (pretty | json) Desired output format (default: pretty)
Other options
--help Print help
"#, "#,
}; };
#[derive(PartialEq)]
enum OperationName { enum OperationName {
List, List,
Log, Log,
@ -45,10 +52,16 @@ enum Operation {
Log(Rev), Log(Rev),
} }
enum Format {
Json,
Pretty,
}
pub struct Options { pub struct Options {
rid: RepoId, rid: RepoId,
op: Operation, op: Operation,
type_name: cob::TypeName, type_name: cob::TypeName,
format: Format,
} }
impl Args for Options { impl Args for Options {
@ -60,6 +73,7 @@ impl Args for Options {
let mut type_name: Option<cob::TypeName> = None; let mut type_name: Option<cob::TypeName> = None;
let mut oid: Option<Rev> = None; let mut oid: Option<Rev> = None;
let mut rid: Option<RepoId> = None; let mut rid: Option<RepoId> = None;
let mut format: Option<Format> = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -90,6 +104,14 @@ impl Args for Options {
Long("help") | Short('h') => { Long("help") | Short('h') => {
return Err(Error::Help.into()); return Err(Error::Help.into());
} }
Long("format") if op == Some(OperationName::Log) => {
let v: String = term::args::string(&parser.value()?);
match v.as_ref() {
"pretty" => format = Some(Format::Pretty),
"json" => format = Some(Format::Json),
unknown => anyhow::bail!("unknown format '{unknown}'"),
}
}
_ => return Err(anyhow::anyhow!(arg.unexpected())), _ => return Err(anyhow::anyhow!(arg.unexpected())),
} }
} }
@ -108,6 +130,7 @@ impl Args for Options {
.ok_or_else(|| anyhow!("a repository id must be specified with `--repo`"))?, .ok_or_else(|| anyhow!("a repository id must be specified with `--repo`"))?,
type_name: type_name type_name: type_name
.ok_or_else(|| anyhow!("an object type must be specified with `--type`"))?, .ok_or_else(|| anyhow!("an object type must be specified with `--type`"))?,
format: format.unwrap_or(Format::Pretty),
}, },
vec![], vec![],
)) ))
@ -131,33 +154,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let ops = cob::store::ops(&oid, &options.type_name, &repo)?; let ops = cob::store::ops(&oid, &options.type_name, &repo)?;
for op in ops.into_iter().rev() { for op in ops.into_iter().rev() {
let time = DateTime::<Utc>::from( match options.format {
std::time::UNIX_EPOCH + std::time::Duration::from_secs(op.timestamp.as_secs()), Format::Json => print_op_json(op)?,
) Format::Pretty => print_op_pretty(op)?,
.to_rfc2822();
term::print(term::format::yellow(format!("commit {}", op.id)));
if let Some(oid) = op.identity {
term::print(term::format::tertiary(format!("resource {oid}")));
}
for parent in op.parents {
term::print(format!("parent {}", parent));
}
for parent in op.related {
term::print(format!("rel {}", parent));
}
term::print(format!("author {}", op.author));
term::print(format!("date {}", time));
term::blank();
for action in op.actions {
let obj: serde_json::Value = serde_json::from_slice(&action)?;
let val = serde_json::to_string_pretty(&obj)?;
for line in val.lines() {
term::indented(term::format::dim(line));
}
term::blank();
} }
} }
} }
@ -165,3 +164,48 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn print_op_pretty(op: Op<Vec<u8>>) -> anyhow::Result<()> {
let time = DateTime::<Utc>::from(
std::time::UNIX_EPOCH + std::time::Duration::from_secs(op.timestamp.as_secs()),
)
.to_rfc2822();
term::print(term::format::yellow(format!("commit {}", op.id)));
if let Some(oid) = op.identity {
term::print(term::format::tertiary(format!("resource {oid}")));
}
for parent in op.parents {
term::print(format!("parent {}", parent));
}
for parent in op.related {
term::print(format!("rel {}", parent));
}
term::print(format!("author {}", op.author));
term::print(format!("date {}", time));
term::blank();
for action in op.actions {
let obj: serde_json::Value = serde_json::from_slice(&action)?;
let val = serde_json::to_string_pretty(&obj)?;
for line in val.lines() {
term::indented(term::format::dim(line));
}
term::blank();
}
Ok(())
}
fn print_op_json(op: Op<Vec<u8>>) -> anyhow::Result<()> {
let mut ser = json!(op);
ser.as_object_mut().unwrap().insert(
"actions".to_string(),
json!(op
.actions
.iter()
.map(|action: &Vec<u8>| -> Result<serde_json::Value, _> {
serde_json::from_slice(action)
})
.collect::<Result<Vec<serde_json::Value>, _>>()?),
);
term::print(ser);
Ok(())
}

View File

@ -1,5 +1,6 @@
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle_cob::Manifest; use radicle_cob::Manifest;
use serde::Serialize;
use thiserror::Error; use thiserror::Error;
use radicle_cob::history::{Entry, EntryId}; use radicle_cob::history::{Entry, EntryId};
@ -26,7 +27,7 @@ pub enum OpEncodingError {
/// ///
/// Everything that can be done in the system is represented by an `Op`. /// Everything that can be done in the system is represented by an `Op`.
/// Operations are applied to an accumulator to yield a final state. /// Operations are applied to an accumulator to yield a final state.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Op<A> { pub struct Op<A> {
/// Id of the entry under which this operation lives. /// Id of the entry under which this operation lives.
pub id: EntryId, pub id: EntryId,