From 57ea88db792c6a86c233ea51f0582f4827832d86 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Tue, 22 Feb 2022 21:27:36 +0100 Subject: [PATCH] init --- .gitignore | 2 + Cargo.toml | 6 + rustfmt.toml | 12 ++ sercon-backends/Cargo.toml | 26 ++++ sercon-backends/src/backends.rs | 20 +++ sercon-backends/src/backends/bincode.rs | 30 +++++ sercon-backends/src/backends/cbor.rs | 24 ++++ sercon-backends/src/backends/json.rs | 24 ++++ sercon-backends/src/backends/nbt.rs | 24 ++++ sercon-backends/src/backends/ron.rs | 25 ++++ sercon-backends/src/backends/s_expression.rs | 24 ++++ sercon-backends/src/backends/toml.rs | 35 +++++ sercon-backends/src/backends/xml.rs | 24 ++++ sercon-backends/src/backends/yaml.rs | 24 ++++ sercon-backends/src/lib.rs | 24 ++++ sercon-base/Cargo.toml | 11 ++ sercon-base/src/lib.rs | 25 ++++ sercon-cli/Cargo.toml | 18 +++ sercon-cli/src/main.rs | 129 +++++++++++++++++++ 19 files changed, 507 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 rustfmt.toml create mode 100644 sercon-backends/Cargo.toml create mode 100644 sercon-backends/src/backends.rs create mode 100644 sercon-backends/src/backends/bincode.rs create mode 100644 sercon-backends/src/backends/cbor.rs create mode 100644 sercon-backends/src/backends/json.rs create mode 100644 sercon-backends/src/backends/nbt.rs create mode 100644 sercon-backends/src/backends/ron.rs create mode 100644 sercon-backends/src/backends/s_expression.rs create mode 100644 sercon-backends/src/backends/toml.rs create mode 100644 sercon-backends/src/backends/xml.rs create mode 100644 sercon-backends/src/backends/yaml.rs create mode 100644 sercon-backends/src/lib.rs create mode 100644 sercon-base/Cargo.toml create mode 100644 sercon-base/src/lib.rs create mode 100644 sercon-cli/Cargo.toml create mode 100644 sercon-cli/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ca6ac42 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "sercon-base", + "sercon-backends", + "sercon-cli", +] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..1059111 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,12 @@ +unstable_features = true +binop_separator = "Back" +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +imports_layout = "HorizontalVertical" +match_block_trailing_comma = true +merge_imports = true +normalize_comments = true +use_field_init_shorthand = true +use_try_shorthand = true +wrap_comments = true diff --git a/sercon-backends/Cargo.toml b/sercon-backends/Cargo.toml new file mode 100644 index 0000000..48638a9 --- /dev/null +++ b/sercon-backends/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sercon-backends" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +miette = "4.2" +sercon-base = { path = "../sercon-base" } +serde = "1.0" +serde-value = "0.7" + +ciborium = "0.2.0" +hematite-nbt = "0.5.2" +ron = "0.7.0" +serde-lexpr = "0.1.2" +serde-xml-rs = "0.5.1" +serde_json = "1.0.79" +serde_yaml = "0.8.23" +toml = "0.5.8" + +[dependencies.bincode] +default-features = false +features = ["std", "serde"] +version = "2.0.0-beta.3" diff --git a/sercon-backends/src/backends.rs b/sercon-backends/src/backends.rs new file mode 100644 index 0000000..6fd4d17 --- /dev/null +++ b/sercon-backends/src/backends.rs @@ -0,0 +1,20 @@ +macro_rules! backends { + ($($name:ident),* $(,)?) => ($( + mod $name; + pub use self::$name::*; + )*) +} + +macro_rules! impl_filetype_backend { + ($type:ty, [$($ft:expr),* $(,)?]) => ( + impl crate::FiletypeBackend for $type { + fn supported_filetypes(&self) -> Vec { + vec![$(String::from($ft)),*] + } + + fn as_backend(&self) -> &dyn Backend { self } + } + ) +} + +backends![bincode, cbor, json, nbt, ron, s_expression, toml, xml, yaml,]; diff --git a/sercon-backends/src/backends/bincode.rs b/sercon-backends/src/backends/bincode.rs new file mode 100644 index 0000000..6cd323b --- /dev/null +++ b/sercon-backends/src/backends/bincode.rs @@ -0,0 +1,30 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic, bail}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct BincodeBackend; + +impl Backend for BincodeBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + let data = bincode::serde::encode_to_vec( + data, + bincode::config::standard().write_fixed_array_length(), + ) + .into_diagnostic() + .wrap_err("bincode serialization error")?; +out.write_all(&data) + .into_diagnostic() + .wrap_err("error writing bincode data")?; + + Ok(()) + } + + fn deserialize(&self, _data: &mut dyn Read) -> miette::Result { + bail!("Bincode does not support deserializing into any"); + } +} + +impl_filetype_backend!(BincodeBackend, ["bincode"]); diff --git a/sercon-backends/src/backends/cbor.rs b/sercon-backends/src/backends/cbor.rs new file mode 100644 index 0000000..38eb43c --- /dev/null +++ b/sercon-backends/src/backends/cbor.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct CborBackend; + +impl Backend for CborBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + ciborium::ser::into_writer(&data, out) + .into_diagnostic() + .wrap_err("CBOR serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + ciborium::de::from_reader(data) + .into_diagnostic() + .wrap_err("CBOR deserialization error") + } +} + +impl_filetype_backend!(CborBackend, ["cbor"]); diff --git a/sercon-backends/src/backends/json.rs b/sercon-backends/src/backends/json.rs new file mode 100644 index 0000000..f09f623 --- /dev/null +++ b/sercon-backends/src/backends/json.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct JsonBackend; + +impl Backend for JsonBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + serde_json::to_writer_pretty(out, &data) + .into_diagnostic() + .wrap_err("JSON serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + serde_json::from_reader(data) + .into_diagnostic() + .wrap_err("JSON deserialization error") + } +} + +impl_filetype_backend!(JsonBackend, ["json"]); diff --git a/sercon-backends/src/backends/nbt.rs b/sercon-backends/src/backends/nbt.rs new file mode 100644 index 0000000..3be9122 --- /dev/null +++ b/sercon-backends/src/backends/nbt.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct NbtBackend; + +impl Backend for NbtBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + nbt::to_writer(out, &data, None) + .into_diagnostic() + .wrap_err("NBT serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + nbt::from_reader(data) + .into_diagnostic() + .wrap_err("NBT deserialization error") + } +} + +impl_filetype_backend!(NbtBackend, ["nbt"]); diff --git a/sercon-backends/src/backends/ron.rs b/sercon-backends/src/backends/ron.rs new file mode 100644 index 0000000..63b070f --- /dev/null +++ b/sercon-backends/src/backends/ron.rs @@ -0,0 +1,25 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use ron::ser::PrettyConfig; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct RonBackend; + +impl Backend for RonBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + ron::ser::to_writer_pretty(out, &data, PrettyConfig::new().new_line("\n".to_string())) + .into_diagnostic() + .wrap_err("RON serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + ron::de::from_reader(data) + .into_diagnostic() + .wrap_err("RON deserialization error") + } +} + +impl_filetype_backend!(RonBackend, ["ron"]); diff --git a/sercon-backends/src/backends/s_expression.rs b/sercon-backends/src/backends/s_expression.rs new file mode 100644 index 0000000..79e9eab --- /dev/null +++ b/sercon-backends/src/backends/s_expression.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct SExpressionBackend; + +impl Backend for SExpressionBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + serde_lexpr::to_writer(out, &data) + .into_diagnostic() + .wrap_err("S-Expression serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + serde_lexpr::from_reader(data) + .into_diagnostic() + .wrap_err("S-Expression deserialization error") + } +} + +impl_filetype_backend!(SExpressionBackend, ["sexpr"]); diff --git a/sercon-backends/src/backends/toml.rs b/sercon-backends/src/backends/toml.rs new file mode 100644 index 0000000..6717735 --- /dev/null +++ b/sercon-backends/src/backends/toml.rs @@ -0,0 +1,35 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct TomlBackend; + +impl Backend for TomlBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + let data = toml::to_string_pretty(&data) + .into_diagnostic() + .wrap_err("TOML serialization error")?; + + out.write_all(data.as_bytes()) + .into_diagnostic() + .wrap_err("error writing TOML data")?; + + Ok(()) + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + let mut buf = Vec::new(); + data.read_to_end(&mut buf) + .into_diagnostic() + .wrap_err("error reading TOML input")?; + + toml::from_slice(&buf) + .into_diagnostic() + .wrap_err("TOML deserialization error") + } +} + +impl_filetype_backend!(TomlBackend, ["toml"]); diff --git a/sercon-backends/src/backends/xml.rs b/sercon-backends/src/backends/xml.rs new file mode 100644 index 0000000..bd32138 --- /dev/null +++ b/sercon-backends/src/backends/xml.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct XmlBackend; + +impl Backend for XmlBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + serde_xml_rs::to_writer(out, &data) + .into_diagnostic() + .wrap_err("XML serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + serde_xml_rs::from_reader(data) + .into_diagnostic() + .wrap_err("XML deserialization error") + } +} + +impl_filetype_backend!(XmlBackend, ["xml", "html"]); diff --git a/sercon-backends/src/backends/yaml.rs b/sercon-backends/src/backends/yaml.rs new file mode 100644 index 0000000..a778eb2 --- /dev/null +++ b/sercon-backends/src/backends/yaml.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; + +use miette::{Context, IntoDiagnostic}; +use sercon_base::Backend; +use serde_value::Value; + +#[derive(Default)] +pub struct YamlBackend; + +impl Backend for YamlBackend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()> { + serde_yaml::to_writer(out, &data) + .into_diagnostic() + .wrap_err("YAML serialization error") + } + + fn deserialize(&self, data: &mut dyn Read) -> miette::Result { + serde_yaml::from_reader(data) + .into_diagnostic() + .wrap_err("YAML deserialization error") + } +} + +impl_filetype_backend!(YamlBackend, ["yml", "yaml"]); diff --git a/sercon-backends/src/lib.rs b/sercon-backends/src/lib.rs new file mode 100644 index 0000000..a0e8f4b --- /dev/null +++ b/sercon-backends/src/lib.rs @@ -0,0 +1,24 @@ +use sercon_base::Backend; + +use crate::backends::*; + +pub mod backends; + +pub trait FiletypeBackend: Backend { + fn supported_filetypes(&self) -> Vec; + fn as_backend(&self) -> &dyn Backend; +} + +pub fn all() -> Vec> { + vec![ + Box::new(BincodeBackend), + Box::new(CborBackend), + Box::new(JsonBackend), + Box::new(NbtBackend), + Box::new(RonBackend), + Box::new(SExpressionBackend), + Box::new(TomlBackend), + Box::new(XmlBackend), + Box::new(YamlBackend), + ] +} diff --git a/sercon-base/Cargo.toml b/sercon-base/Cargo.toml new file mode 100644 index 0000000..c70d86e --- /dev/null +++ b/sercon-base/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sercon-base" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +miette = "4.2" +serde = "1.0" +serde-value = "0.7" diff --git a/sercon-base/src/lib.rs b/sercon-base/src/lib.rs new file mode 100644 index 0000000..16770f7 --- /dev/null +++ b/sercon-base/src/lib.rs @@ -0,0 +1,25 @@ +use std::io::{Read, Write}; + +use miette::Context; +use serde_value::Value; + +pub trait Backend { + fn serialize(&self, out: &mut dyn Write, data: Value) -> miette::Result<()>; + fn deserialize(&self, data: &mut dyn Read) -> miette::Result; +} + +pub fn transcode( + inp: &mut dyn Read, + out: &mut dyn Write, + ser_backend: &dyn Backend, + de_backend: &dyn Backend, +) -> miette::Result<()> { + let data = de_backend + .deserialize(inp) + .wrap_err("error in deserialization backend")?; + ser_backend + .serialize(out, data) + .wrap_err("error in serialization backend")?; + + Ok(()) +} diff --git a/sercon-cli/Cargo.toml b/sercon-cli/Cargo.toml new file mode 100644 index 0000000..e14c2a4 --- /dev/null +++ b/sercon-cli/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sercon-cli" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "sercon" +path = "src/main.rs" + +[dependencies] +miette = { version = "4.2", features = ["fancy"] } +serde = "1.0" +serde-value = "0.7" +clap = { version = "3.1", features = ["derive"] } +sercon-base = { path = "../sercon-base" } +sercon-backends = { path = "../sercon-backends" } diff --git a/sercon-cli/src/main.rs b/sercon-cli/src/main.rs new file mode 100644 index 0000000..c4866f0 --- /dev/null +++ b/sercon-cli/src/main.rs @@ -0,0 +1,129 @@ +use std::{ + fs::{File, OpenOptions}, + io::{self, Read, Write}, + path::PathBuf, +}; + +use clap::Parser; +use miette::{miette, Context, IntoDiagnostic}; + +/// Convert between different data formats. +#[derive(Parser)] +struct Opt { + /// The input file to convert. stdin is used if omitted. + /// The type will be inferred if not explicitly specified. + #[clap(short, long)] + infile: Option, + + /// The output file to write the output to. stdout is used if omitted. + /// The type will be inferred if not explicitly specified. + #[clap(short, long)] + outfile: Option, + + /// The format used to parse the input. + #[clap( + short = 'f', + long, + required_unless_present_any = &["infile", "list-formats"], + )] + informat: Option, + + /// The format to convert the input into. + #[clap( + short = 't', + long, + required_unless_present_any = &["outfile", "list-formats"], + )] + outformat: Option, + + /// List all supported formats and exit. + #[clap(long, exclusive = true)] + list_formats: bool, +} + +fn main() -> miette::Result<()> { + let opt = Opt::parse(); + + if opt.list_formats { + println!( + "{}", + sercon_backends::all() + .into_iter() + .flat_map(|b| b.supported_filetypes()) + .collect::>() + .join("\n") + ); + + return Ok(()); + } + + let informat = opt + .informat + .or_else(|| { + opt.infile + .as_ref() + .and_then(|p| p.extension().map(|s| s.to_string_lossy().to_string())) + }) + .ok_or_else(|| miette!("Couldn't infer input format. Please specify explicitly."))?; + + let outformat = opt + .outformat + .or_else(|| { + opt.outfile + .as_ref() + .and_then(|p| p.extension().map(|s| s.to_string_lossy().to_string())) + }) + .ok_or_else(|| miette!("Couldn't infer output format. Please specify explicitly."))?; + + let backends = sercon_backends::all(); + + let in_backend = backends + .iter() + .find(|b| b.supported_filetypes().contains(&informat)) + .ok_or_else(|| miette!("No parser found for input format '{}'!", &informat))?; + + let out_backend = backends + .iter() + .find(|b| b.supported_filetypes().contains(&outformat)) + .ok_or_else(|| miette!("No parser found for output format '{}'!", &outformat))?; + + let mut input: Box = if let Some(p) = opt.infile { + Box::new( + File::open(p) + .into_diagnostic() + .wrap_err("Failed to open infile")?, + ) + } else { + Box::new(io::stdin()) + }; + + let mut using_stdout = false; + let mut output: Box = if let Some(p) = opt.outfile { + Box::new( + OpenOptions::new() + .write(true) + .create(true) + .open(p) + .into_diagnostic() + .wrap_err("Failed to open outfile")?, + ) + } else { + using_stdout = true; + Box::new(io::stdout()) + }; + + sercon_base::transcode( + &mut input, + &mut output, + out_backend.as_backend(), + in_backend.as_backend(), + ) + .wrap_err("error during transcoding")?; + + if using_stdout { + println!(); + io::stdout().flush().into_diagnostic()?; + } + + Ok(()) +}