init
This commit is contained in:
commit
57ea88db79
19 changed files with 507 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"sercon-base",
|
||||
"sercon-backends",
|
||||
"sercon-cli",
|
||||
]
|
12
rustfmt.toml
Normal file
12
rustfmt.toml
Normal 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
|
26
sercon-backends/Cargo.toml
Normal file
26
sercon-backends/Cargo.toml
Normal 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"
|
20
sercon-backends/src/backends.rs
Normal file
20
sercon-backends/src/backends.rs
Normal 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,];
|
30
sercon-backends/src/backends/bincode.rs
Normal file
30
sercon-backends/src/backends/bincode.rs
Normal 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"]);
|
24
sercon-backends/src/backends/cbor.rs
Normal file
24
sercon-backends/src/backends/cbor.rs
Normal 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"]);
|
24
sercon-backends/src/backends/json.rs
Normal file
24
sercon-backends/src/backends/json.rs
Normal 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"]);
|
24
sercon-backends/src/backends/nbt.rs
Normal file
24
sercon-backends/src/backends/nbt.rs
Normal 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"]);
|
25
sercon-backends/src/backends/ron.rs
Normal file
25
sercon-backends/src/backends/ron.rs
Normal 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"]);
|
24
sercon-backends/src/backends/s_expression.rs
Normal file
24
sercon-backends/src/backends/s_expression.rs
Normal 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"]);
|
35
sercon-backends/src/backends/toml.rs
Normal file
35
sercon-backends/src/backends/toml.rs
Normal 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"]);
|
24
sercon-backends/src/backends/xml.rs
Normal file
24
sercon-backends/src/backends/xml.rs
Normal 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"]);
|
24
sercon-backends/src/backends/yaml.rs
Normal file
24
sercon-backends/src/backends/yaml.rs
Normal 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"]);
|
24
sercon-backends/src/lib.rs
Normal file
24
sercon-backends/src/lib.rs
Normal 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
11
sercon-base/Cargo.toml
Normal 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
25
sercon-base/src/lib.rs
Normal 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
18
sercon-cli/Cargo.toml
Normal 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
129
sercon-cli/src/main.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in a new issue