From 21b24e996c78973f4b3835e0fc3b4fb20c41a591 Mon Sep 17 00:00:00 2001 From: threadexio Date: Fri, 27 Dec 2024 21:58:12 +0200 Subject: [PATCH] feat: add config file --- Cargo.lock | 74 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + cbundl.toml | 3 ++ src/cli.rs | 95 +++++++++------------------------------------- src/config/args.rs | 80 ++++++++++++++++++++++++++++++++++++++ src/config/file.rs | 77 +++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 40 +++++++++++++++++++ src/main.rs | 1 + 8 files changed, 295 insertions(+), 77 deletions(-) create mode 100644 cbundl.toml create mode 100644 src/config/args.rs create mode 100644 src/config/file.rs create mode 100644 src/config/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ea02d5e..38c72cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,7 +137,9 @@ dependencies = [ "owo-colors 4.1.0", "petgraph", "rand", + "serde", "thiserror", + "toml", ] [[package]] @@ -580,6 +582,35 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -642,6 +673,40 @@ dependencies = [ "once_cell", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -849,6 +914,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 6dbf80c..de81302 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ log = { version = "0.4" } owo-colors = { version = "4.1" } petgraph = { version = "0.6" } rand = "0.8.5" +serde = { version = "1.0", features = ["derive"] } thiserror = { version = "2.0" } +toml = { version = "0.8" } [build-dependencies] chrono = "0.4.39" diff --git a/cbundl.toml b/cbundl.toml new file mode 100644 index 0000000..e0c5964 --- /dev/null +++ b/cbundl.toml @@ -0,0 +1,3 @@ +no-format = true +formatter = "/home/kat/.nix-profile/bin/clang-format" +deterministic = true diff --git a/src/cli.rs b/src/cli.rs index 6d13988..62b3db2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,96 +1,50 @@ use std::fs::File; use std::io::{stdout, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -use clap::Parser; use eyre::{Context, Result}; use crate::banner::Banner; use crate::bundler::Bundler; -use crate::consts::{CRATE_DESCRIPTION, LONG_VERSION, SHORT_VERSION}; +use crate::config::Config; use crate::display::display_path; use crate::formatter::Formatter; use crate::pipeline::Pipeline; use crate::quotes::Quotes; use crate::source::Sources; -#[derive(Debug, Clone, Parser)] -#[command( - version = SHORT_VERSION, - long_version = LONG_VERSION, - about = CRATE_DESCRIPTION, - long_about = None -)] -pub struct GlobalArgs { - #[arg( - long, - help = "Don't pass the resulting bundle through the formatter.", - default_value_t = false - )] - pub no_format: bool, - - #[arg( - long, - help = "Code formatter executable.", - long_help = "Code formatter. Must format the code from stdin and write it to stdout.", - default_value = "clang-format", - value_name = "exe" - )] - pub formatter: PathBuf, - - #[arg( - long = "deterministic", - help = "Output a deterministic bundle.", - default_value_t = false - )] - pub deterministic: bool, - - #[arg( - short = 'o', - long = "output", - help = "Specify where to write the resulting bundle.", - default_value = "-", - value_name = "path" - )] - pub output_file: PathBuf, - - #[arg(help = "Path to the entry source file.", value_name = "path")] - pub entry: PathBuf, -} - pub fn run() -> Result<()> { - let args = GlobalArgs::parse(); - trace!("args = {args:#?}"); + let config = Config::new()?; + trace!("config = {config:#?}"); - let sources = Sources::new(args.entry)?; + let sources = Sources::new(config.entry)?; let mut pipeline = Pipeline { bundler: Bundler {}, banner: Banner { quotes: Quotes {}, - deterministic: args.deterministic, + deterministic: config.deterministic, }, - formatter: some_if(!args.no_format, || Formatter { - exe: args.formatter, + formatter: (!config.no_format).then(|| Formatter { + exe: config.formatter, }), }; let bundle = pipeline.process(&sources)?; - write_bundle(bundle, &args.output_file).with_context(|| { - format!( - "failed to write bundle to `{}`", - display_path(&args.output_file) - ) + write_bundle(bundle, config.output_file.as_ref()).with_context(|| { + if let Some(path) = config.output_file.as_ref() { + format!("failed to write bundle to `{}`", display_path(path)) + } else { + format!("failed to write bundle to stdout") + } })?; Ok(()) } -fn write_bundle(bundle: String, path: &Path) -> Result<()> { - let mut writer: Box = if path_is_stdio(path) { - Box::new(stdout().lock()) - } else { +fn write_bundle(bundle: String, path: Option<&PathBuf>) -> Result<()> { + let mut writer: Box = if let Some(path) = path { Box::new( File::options() .write(true) @@ -98,24 +52,11 @@ fn write_bundle(bundle: String, path: &Path) -> Result<()> { .create(true) .open(path)?, ) + } else { + Box::new(stdout().lock()) }; writer.write_all(bundle.as_bytes())?; writer.flush()?; Ok(()) } - -fn path_is_stdio(path: &Path) -> bool { - path.as_os_str().as_encoded_bytes().eq(b"-") -} - -fn some_if(cond: bool, f: F) -> Option -where - F: FnOnce() -> T, -{ - if cond { - Some(f()) - } else { - None - } -} diff --git a/src/config/args.rs b/src/config/args.rs new file mode 100644 index 0000000..bd7cd3b --- /dev/null +++ b/src/config/args.rs @@ -0,0 +1,80 @@ +use std::path::{Path, PathBuf}; + +use clap::parser::ValueSource; +use clap::{CommandFactory, Parser}; +use eyre::Result; + +use crate::consts::{CRATE_DESCRIPTION, LONG_VERSION, SHORT_VERSION}; + +use super::Config; + +#[derive(Debug, Clone, Parser)] +#[command( + version = SHORT_VERSION, + long_version = LONG_VERSION, + about = CRATE_DESCRIPTION, + long_about = None +)] +pub struct Args { + #[arg(long, help = "Don't pass the resulting bundle through the formatter.")] + no_format: bool, + + #[arg( + long, + help = "Code formatter executable.", + long_help = "Code formatter. Must format the code from stdin and write it to stdout.", + value_name = "exe", + default_value = "clang-format" + )] + formatter: PathBuf, + + #[arg(long = "deterministic", help = "Output a deterministic bundle.")] + deterministic: bool, + + #[arg( + short = 'o', + long = "output", + help = "Specify where to write the resulting bundle.", + value_name = "path", + default_value = "-" + )] + output_file: PathBuf, + + #[arg(help = "Path to the entry source file.", value_name = "path")] + entry: PathBuf, +} + +impl Args { + pub fn merge(config: &mut Config) -> Result<()> { + let matches = Args::command().get_matches(); + + // SAFETY: The following `unwrap`s are safe because the these arguments have default values. + + if matches.contains_id("no_format") { + config.no_format = matches.get_flag("no_format"); + } + + if matches.value_source("formatter").unwrap() != ValueSource::DefaultValue { + config.formatter = matches.get_one::("formatter").unwrap().to_owned(); + } + + if matches.contains_id("deterministic") { + config.deterministic = matches.get_flag("deterministic"); + } + + if matches.value_source("output_file").unwrap() != ValueSource::DefaultValue { + let output_file = matches.get_one::("output_file").unwrap(); + config.output_file = path_is_stdio(output_file).then(|| output_file.to_owned()); + } + + if let Some(x) = matches.get_one::("entry").cloned() { + config.entry = x; + } + + Ok(()) + } +} + +fn path_is_stdio(path: &Path) -> bool { + path.as_os_str().as_encoded_bytes().eq(b"-") +} diff --git a/src/config/file.rs b/src/config/file.rs new file mode 100644 index 0000000..8324b34 --- /dev/null +++ b/src/config/file.rs @@ -0,0 +1,77 @@ +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use eyre::{Context, Result}; +use serde::Deserialize; + +use crate::display::display_path; + +use super::Config; + +const DEFAULT_FILES: &[&str] = &[".cbundl.toml", "cbundl.toml"]; + +#[derive(Debug, Clone, Deserialize)] +pub struct File { + #[serde(rename = "no-format")] + no_format: Option, + + formatter: Option, + + deterministic: Option, + + #[serde(rename = "output")] + output_file: Option, +} + +impl File { + fn read() -> Option> { + for f in DEFAULT_FILES { + let x = match read_file(Path::new(f)) { + Some(Ok(x)) => x, + Some(Err(e)) => return Some(Err(e)), + None => continue, + }; + + match toml::from_str(&x) { + Ok(x) => return Some(Ok(x)), + Err(e) => return Some(Err(e.into())), + } + } + + None + } + + pub fn merge(config: &mut Config) -> Result<()> { + let file = match Self::read() { + Some(x) => x?, + None => return Ok(()), + }; + + if let Some(x) = file.no_format { + config.no_format = x; + } + + if let Some(x) = file.formatter { + config.formatter = x; + } + + if let Some(x) = file.deterministic { + config.deterministic = x; + } + + if let Some(x) = file.output_file { + config.output_file = Some(x); + } + + Ok(()) + } +} + +fn read_file(path: &Path) -> Option> { + match fs::read_to_string(path) { + Ok(x) => Some(Ok(x)), + Err(e) if e.kind() == io::ErrorKind::NotFound => None, + r @ Err(_) => Some(r.with_context(|| format!("failed to read `{}`", display_path(path)))), + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..c9169b0 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use eyre::Result; + +mod args; +mod file; + +use self::args::Args; +use self::file::File; + +#[derive(Debug, Clone)] +pub struct Config { + pub no_format: bool, + pub formatter: PathBuf, + pub deterministic: bool, + pub output_file: Option, + pub entry: PathBuf, +} + +impl Config { + pub fn new() -> Result { + let mut config = Self { + no_format: false, + formatter: PathBuf::from("clang-format"), + deterministic: false, + output_file: None, + entry: PathBuf::new(), + }; + + trace!("default config: {config:#?}"); + + File::merge(&mut config)?; + trace!("with config file: {config:#?}"); + + Args::merge(&mut config)?; + trace!("with args: {config:#?}"); + + Ok(config) + } +} diff --git a/src/main.rs b/src/main.rs index f14edb1..b152cb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ extern crate log; mod banner; mod bundler; mod cli; +mod config; mod consts; mod display; mod formatter;