Skip to content

Commit fb7e85e

Browse files
committed
feat: add proper support for config files
1 parent 21b24e9 commit fb7e85e

File tree

7 files changed

+233
-210
lines changed

7 files changed

+233
-210
lines changed

cbundl.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
no-format = true
2-
formatter = "/home/kat/.nix-profile/bin/clang-format"
3-
deterministic = true
1+
[bundle]
2+
output = "test/frob/final.c"
3+
deterministic = false
4+
5+
[formatter]
6+
# enable = true
7+
# path = "clang-format"

src/config.rs

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
use std::fs;
2+
use std::io;
3+
use std::path::{Path, PathBuf};
4+
5+
use clap::parser::ValueSource;
6+
use clap::{CommandFactory, Parser};
7+
use eyre::{Context, Result};
8+
use serde::Deserialize;
9+
10+
use crate::consts::{
11+
CRATE_DESCRIPTION, DEFAULT_CONFIG_FILES, DEFAULT_FORMATTER, LONG_VERSION, SHORT_VERSION,
12+
};
13+
use crate::display::display_path;
14+
15+
#[derive(Debug, Clone, Parser)]
16+
#[command(
17+
version = SHORT_VERSION,
18+
long_version = LONG_VERSION,
19+
about = CRATE_DESCRIPTION,
20+
long_about = None
21+
)]
22+
struct Args {
23+
#[arg(
24+
long,
25+
help = "Don't load any configuration file. (Overrides `--config`)"
26+
)]
27+
no_config: bool,
28+
29+
#[arg(long, help = "Specify an alternate configuration file")]
30+
config: Option<PathBuf>,
31+
32+
#[arg(long, help = "Don't pass the resulting bundle through the formatter.")]
33+
no_format: bool,
34+
35+
#[arg(
36+
long,
37+
help = "Code formatter executable.",
38+
long_help = "Code formatter. Must format the code from stdin and write it to stdout.",
39+
value_name = "exe",
40+
default_value = DEFAULT_FORMATTER
41+
)]
42+
formatter: PathBuf,
43+
44+
#[arg(long = "deterministic", help = "Output a deterministic bundle.")]
45+
deterministic: bool,
46+
47+
#[arg(
48+
short = 'o',
49+
long = "output",
50+
help = "Specify where to write the resulting bundle.",
51+
value_name = "path",
52+
default_value = "-"
53+
)]
54+
output_file: PathBuf,
55+
56+
#[arg(help = "Path to the entry source file.", value_name = "path")]
57+
entry: PathBuf,
58+
}
59+
60+
#[derive(Debug, Clone, Deserialize)]
61+
struct File {
62+
bundle: Option<BundleSection>,
63+
formatter: Option<FormatterSection>,
64+
}
65+
66+
#[derive(Debug, Clone, Deserialize)]
67+
struct BundleSection {
68+
deterministic: Option<bool>,
69+
70+
#[serde(rename = "output")]
71+
output_file: Option<PathBuf>,
72+
}
73+
74+
#[derive(Debug, Clone, Deserialize)]
75+
struct FormatterSection {
76+
enable: Option<bool>,
77+
path: Option<PathBuf>,
78+
}
79+
80+
impl File {
81+
fn read(path: &Path) -> Option<Result<Self>> {
82+
let x = match fs::read_to_string(path) {
83+
Ok(x) => x,
84+
Err(e) if e.kind() == io::ErrorKind::NotFound => return None,
85+
Err(e) => return Some(Err(e).context("failed to read file")),
86+
};
87+
88+
let x = toml::from_str(&x).context("failed to parse file");
89+
Some(x)
90+
}
91+
92+
fn read_many<'a, I>(paths: I) -> Option<Self>
93+
where
94+
I: Iterator<Item = &'a Path>,
95+
{
96+
for path in paths {
97+
match Self::read(path) {
98+
Some(r) => {
99+
match r
100+
.with_context(|| format!("failed to read config `{}`", display_path(path)))
101+
{
102+
Ok(x) => return Some(x),
103+
Err(e) => {
104+
warn!("{e:#}");
105+
continue;
106+
}
107+
}
108+
}
109+
None => continue,
110+
}
111+
}
112+
113+
None
114+
}
115+
}
116+
117+
#[derive(Debug, Clone)]
118+
pub struct Config {
119+
pub no_format: bool,
120+
pub formatter: PathBuf,
121+
pub deterministic: bool,
122+
pub output_file: Option<PathBuf>,
123+
pub entry: PathBuf,
124+
}
125+
126+
impl Config {
127+
pub fn new() -> Result<Self> {
128+
let args = Args::command().get_matches();
129+
130+
let file = if args.get_flag("no_config") {
131+
None
132+
} else if let Some(config) = args.get_one::<&PathBuf>("config") {
133+
let x = File::read(config)
134+
.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))
135+
.with_context(|| format!("failed to read config `{}`", display_path(config)))??;
136+
137+
Some(x)
138+
} else {
139+
File::read_many(DEFAULT_CONFIG_FILES.iter().copied().map(Path::new))
140+
};
141+
142+
let no_format = if let ValueSource::CommandLine = args.value_source("no_format").unwrap() {
143+
args.get_flag("no_format")
144+
} else if let Some(x) = file
145+
.as_ref()
146+
.and_then(|x| x.formatter.as_ref())
147+
.and_then(|x| x.enable)
148+
{
149+
!x
150+
} else {
151+
false
152+
};
153+
154+
let formatter = if let ValueSource::CommandLine = args.value_source("formatter").unwrap() {
155+
args.get_one::<PathBuf>("formatter").unwrap().clone()
156+
} else if let Some(x) = file
157+
.as_ref()
158+
.and_then(|x| x.formatter.as_ref())
159+
.and_then(|x| x.path.as_ref())
160+
{
161+
x.clone()
162+
} else {
163+
PathBuf::from(DEFAULT_FORMATTER)
164+
};
165+
166+
let deterministic =
167+
if let ValueSource::CommandLine = args.value_source("deterministic").unwrap() {
168+
args.get_flag("deterministic")
169+
} else if let Some(x) = file
170+
.as_ref()
171+
.and_then(|x| x.bundle.as_ref())
172+
.and_then(|x| x.deterministic)
173+
{
174+
x
175+
} else {
176+
false
177+
};
178+
179+
let output_file =
180+
if let ValueSource::CommandLine = args.value_source("output_file").unwrap() {
181+
let x = args.get_one::<PathBuf>("output_file").unwrap();
182+
(!path_is_stdio(x)).then(|| x.clone())
183+
} else if let Some(x) = file
184+
.as_ref()
185+
.and_then(|x| x.bundle.as_ref())
186+
.and_then(|x| x.output_file.as_ref())
187+
{
188+
Some(x.to_owned())
189+
} else {
190+
None
191+
};
192+
193+
let entry = args.get_one::<PathBuf>("entry").unwrap().clone();
194+
195+
Ok(Self {
196+
no_format,
197+
formatter,
198+
deterministic,
199+
output_file,
200+
entry,
201+
})
202+
}
203+
}
204+
205+
fn path_is_stdio(path: &Path) -> bool {
206+
path.as_os_str().as_encoded_bytes().eq(b"-")
207+
}

src/config/args.rs

-80
This file was deleted.

src/config/file.rs

-77
This file was deleted.

0 commit comments

Comments
 (0)