use clap::{Args, Parser, Subcommand, ValueEnum};
use eyre::Context;
const HEXLOWER_WRAPPED_PERMISSIVE: data_encoding::Encoding = data_encoding_macro::new_encoding! {
symbols: "0123456789abcdef",
translate_from: "ABCDEF, -:",
translate_to: "abcdef\n\n\n\n",
wrap_width: 64,
wrap_separator: "\n",
};
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
#[command(name = "diag2cbor")]
Diag2Cbor {
#[command(flatten)]
from_diag: FromDiagArgs,
#[command(flatten)]
to_cbor: ToCborArgs,
#[command(flatten)]
common: CommonArgs,
},
#[command(name = "cbor2diag")]
Cbor2Diag {
#[command(flatten)]
from_cbor: FromCborArgs,
#[command(flatten)]
to_diag: ToDiagArgs,
#[command(flatten)]
common: CommonArgs,
},
#[command(name = "diag2diag")]
Diag2Diag {
#[command(flatten)]
from_diag: FromDiagArgs,
#[command(flatten)]
to_diag: ToDiagArgs,
#[command(flatten)]
common: CommonArgs,
#[arg(long)]
preserve_space: bool,
#[arg(long)]
remove_comments: bool,
},
}
impl Commands {
fn common(&mut self) -> &mut CommonArgs {
use Commands::*;
match self {
Diag2Cbor { common, .. } | Cbor2Diag { common, .. } | Diag2Diag { common, .. } => {
common
}
}
}
fn args_from_cbor(&self) -> Option<&FromCborArgs> {
use Commands::*;
match self {
Cbor2Diag { ref from_cbor, .. } => Some(from_cbor),
_ => None,
}
}
fn args_from_diag(&self) -> Option<&FromDiagArgs> {
use Commands::*;
match self {
Diag2Cbor { ref from_diag, .. } | Diag2Diag { ref from_diag, .. } => Some(from_diag),
_ => None,
}
}
fn args_to_diag(&self) -> Option<&ToDiagArgs> {
use Commands::*;
match self {
Cbor2Diag { ref to_diag, .. } | Diag2Diag { ref to_diag, .. } => Some(to_diag),
_ => None,
}
}
fn load<'a>(&self, data: &'a [u8]) -> eyre::Result<cbor_edn::Sequence<'a>> {
use Commands::*;
Ok(match self {
Diag2Cbor { .. } | Diag2Diag { .. } => {
let data = &std::str::from_utf8(data).context("Error processing input file")?;
cbor_edn::Sequence::parse(data).context("Error parsing input data")?
}
Cbor2Diag {
from_cbor, common, ..
} => {
let input_format = from_cbor.input_format.unwrap_or_default();
let hexdecoded;
let data = if input_format == BinaryFormat::Hex
|| (input_format == BinaryFormat::Auto && common.input.is_tty())
{
hexdecoded = HEXLOWER_WRAPPED_PERMISSIVE.decode(data)?;
&hexdecoded
} else {
data
};
cbor_edn::Sequence::from_cbor(data)?
}
})
}
fn serialize(&self, data: cbor_edn::Sequence<'_>) -> eyre::Result<Vec<u8>> {
use Commands::*;
Ok(match self {
Diag2Cbor {
common, to_cbor, ..
} => {
let output = data.to_cbor()?;
let output_format = to_cbor.output_format.unwrap_or_default();
if output_format == BinaryFormat::Hex
|| (output_format == BinaryFormat::Auto && common.output.is_tty())
{
HEXLOWER_WRAPPED_PERMISSIVE.encode(&output).into()
} else {
output
}
}
Cbor2Diag { .. } | Diag2Diag { .. } => data.serialize().into_bytes(),
})
}
fn transform(&self, data: &mut cbor_edn::Sequence<'_>) -> eyre::Result<()> {
if self.args_from_diag().is_some_and(|fd| fd.annotate_tags)
|| self.args_from_cbor().is_some_and(|fc| !fc.no_annotate_tags)
{
data.visit_tag(&mut cbor_edn::application::ip_tag_to_aol);
data.visit_tag(&mut cbor_edn::application::dt_tag_to_aol);
data.visit_tag(&mut cbor_edn::application::comment_lang_tag);
}
if matches!(
self,
Commands::Diag2Diag {
remove_comments: true,
..
}
) {
data.set_delimiters(cbor_edn::DelimiterPolicy::DiscardAll);
}
if self.args_to_diag().is_none() || self.args_to_diag().is_some_and(|td| td.aol_to_item) {
data.visit_application_literals(&mut cbor_edn::application::ip_aol_to_item);
data.visit_application_literals(&mut cbor_edn::application::dt_aol_to_item);
}
if let Some(annotator) = self.args_to_diag().and_then(|td| td.annotate) {
match annotator {
KnownAnnotation::Ccs => {
for item in data.items_mut() {
let _ = item.visit_map_elements(&mut cbor_edn::application::comment_ccs);
}
}
}
}
if self
.args_to_diag()
.is_some_and(|td| !td.no_bignum_from_tags)
{
data.visit_tag(&mut cbor_edn::application::tag23_to_edn_integer);
}
if self.args_from_diag().is_some_and(|fd| fd.apply_999) {
data.visit_application_literals(&mut cbor_edn::application::any_aol_to_tag999);
}
if !matches!(
self,
Commands::Diag2Diag {
preserve_space: true,
..
}
) {
data.set_delimiters(cbor_edn::DelimiterPolicy::indented_with_final_newline());
}
Ok(())
}
}
#[derive(Args)]
struct CommonArgs {
#[clap(value_parser, default_value = "-")]
input: clio::Input,
#[clap(value_parser, default_value = "-")]
output: clio::Output,
}
#[derive(Copy, Clone, Default, PartialEq, ValueEnum)]
enum BinaryFormat {
Hex,
Binary,
#[default]
Auto,
}
#[derive(Args)]
struct ToCborArgs {
#[arg(short, long, value_enum)]
output_format: Option<BinaryFormat>,
}
#[derive(Args)]
struct FromCborArgs {
#[arg(long)]
no_annotate_tags: bool,
#[arg(short, long, value_enum)]
input_format: Option<BinaryFormat>,
}
#[derive(Args)]
struct ToDiagArgs {
#[arg(long)]
aol_to_item: bool,
#[arg(long)]
no_bignum_from_tags: bool,
#[arg(long, value_enum)]
annotate: Option<KnownAnnotation>,
}
#[derive(Copy, Clone, ValueEnum)]
enum KnownAnnotation {
Ccs,
}
#[derive(Args)]
struct FromDiagArgs {
#[arg(long)]
apply_999: bool,
#[arg(long)]
annotate_tags: bool,
}
fn main() -> eyre::Result<()> {
let mut cli = Cli::parse();
use std::io::{Read, Write};
let mut input_data = Vec::new();
cli.command.common().input.read_to_end(&mut input_data)?;
let mut parsed = cli.command.load(&input_data)?;
cli.command.transform(&mut parsed)?;
let bytes = cli.command.serialize(parsed)?;
cli.command.common().output.write_all(&bytes)?;
Ok(())
}