This commit is contained in:
LordMZTE 2022-02-22 21:27:36 +01:00
commit 57ea88db79
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
19 changed files with 507 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[workspace]
members = [
"sercon-base",
"sercon-backends",
"sercon-cli",
]

12
rustfmt.toml Normal file
View file

@ -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

View file

@ -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"

View file

@ -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<String> {
vec![$(String::from($ft)),*]
}
fn as_backend(&self) -> &dyn Backend { self }
}
)
}
backends![bincode, cbor, json, nbt, ron, s_expression, toml, xml, yaml,];

View file

@ -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<Value> {
bail!("Bincode does not support deserializing into any");
}
}
impl_filetype_backend!(BincodeBackend, ["bincode"]);

View file

@ -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<Value> {
ciborium::de::from_reader(data)
.into_diagnostic()
.wrap_err("CBOR deserialization error")
}
}
impl_filetype_backend!(CborBackend, ["cbor"]);

View file

@ -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<Value> {
serde_json::from_reader(data)
.into_diagnostic()
.wrap_err("JSON deserialization error")
}
}
impl_filetype_backend!(JsonBackend, ["json"]);

View file

@ -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<Value> {
nbt::from_reader(data)
.into_diagnostic()
.wrap_err("NBT deserialization error")
}
}
impl_filetype_backend!(NbtBackend, ["nbt"]);

View file

@ -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<Value> {
ron::de::from_reader(data)
.into_diagnostic()
.wrap_err("RON deserialization error")
}
}
impl_filetype_backend!(RonBackend, ["ron"]);

View file

@ -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<Value> {
serde_lexpr::from_reader(data)
.into_diagnostic()
.wrap_err("S-Expression deserialization error")
}
}
impl_filetype_backend!(SExpressionBackend, ["sexpr"]);

View file

@ -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<Value> {
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"]);

View file

@ -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<Value> {
serde_xml_rs::from_reader(data)
.into_diagnostic()
.wrap_err("XML deserialization error")
}
}
impl_filetype_backend!(XmlBackend, ["xml", "html"]);

View file

@ -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<Value> {
serde_yaml::from_reader(data)
.into_diagnostic()
.wrap_err("YAML deserialization error")
}
}
impl_filetype_backend!(YamlBackend, ["yml", "yaml"]);

View file

@ -0,0 +1,24 @@
use sercon_base::Backend;
use crate::backends::*;
pub mod backends;
pub trait FiletypeBackend: Backend {
fn supported_filetypes(&self) -> Vec<String>;
fn as_backend(&self) -> &dyn Backend;
}
pub fn all() -> Vec<Box<dyn FiletypeBackend>> {
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),
]
}

11
sercon-base/Cargo.toml Normal file
View file

@ -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"

25
sercon-base/src/lib.rs Normal file
View file

@ -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<Value>;
}
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(())
}

18
sercon-cli/Cargo.toml Normal file
View file

@ -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" }

129
sercon-cli/src/main.rs Normal file
View file

@ -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<PathBuf>,
/// 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<PathBuf>,
/// The format used to parse the input.
#[clap(
short = 'f',
long,
required_unless_present_any = &["infile", "list-formats"],
)]
informat: Option<String>,
/// The format to convert the input into.
#[clap(
short = 't',
long,
required_unless_present_any = &["outfile", "list-formats"],
)]
outformat: Option<String>,
/// 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::<Vec<_>>()
.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<dyn Read> = 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<dyn Write> = 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(())
}