1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#[cfg(feature = "cli")]
mod cli {
use clap::{Parser, Subcommand};
use config_get::{get_config_file, ConfigGet};
#[derive(Parser)]
#[command(
name = "config-get",
about = "Cross-platform configuration file locator and reader",
version,
author = "Hadi Cahyadi <cumulus13@gmail.com>"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Find and print the path of a config file.
Find {
/// Config stem (e.g. "myapp")
stem: String,
/// Sub-directory to search in
#[arg(short, long)]
dir: Option<String>,
},
/// Print all key/value pairs from a config file.
Dump {
/// Config stem or explicit file path
stem: String,
#[arg(short, long)]
dir: Option<String>,
/// Treat `stem` as an explicit file path
#[arg(short, long)]
file: bool,
},
/// Get a single value from a config file.
Get {
/// Config stem or explicit file path
stem: String,
/// Key to look up (use `section.key` for sectioned configs)
key: String,
#[arg(short, long)]
dir: Option<String>,
#[arg(short, long)]
file: bool,
/// Value to print if key is absent
#[arg(long)]
fallback: Option<String>,
},
/// Print candidate search paths without loading.
Paths {
stem: String,
#[arg(short, long)]
dir: Option<String>,
},
}
pub fn run() {
let cli = Cli::parse();
match cli.command {
Commands::Find { stem, dir } => {
let d = dir.as_deref().unwrap_or(&stem);
if let Some(p) = get_config_file(&stem, d) {
println!("{}", p.display());
} else {
eprintln!("No config file found for '{stem}'");
std::process::exit(1);
}
}
Commands::Dump { stem, dir, file } => {
let cfg = load_cfg(&stem, dir.as_deref(), file);
for (k, v) in cfg.iter() {
println!("{k}={v}");
}
for section in cfg.sections() {
if let Ok(kv) = cfg.get_section(section) {
for (k, v) in &kv {
println!("[{section}] {k}={v}");
}
}
}
}
Commands::Get {
stem,
key,
dir,
file,
fallback,
} => {
let cfg = load_cfg(&stem, dir.as_deref(), file);
let value = if key.contains('.') {
let mut parts = key.splitn(2, '.');
let section = parts.next().unwrap_or_default();
let k = parts.next().unwrap_or_default();
cfg.get_in(section, k).or_else(|| cfg.get(&key))
} else {
cfg.get(&key)
};
if let Some(v) = value.or(fallback.as_deref()) {
println!("{v}");
} else {
eprintln!("Key '{key}' not found");
std::process::exit(1);
}
}
Commands::Paths { stem, dir } => {
let d = dir.as_deref().unwrap_or(&stem);
for p in ConfigGet::search_paths(&stem, d) {
let exists = if p.exists() { " [EXISTS]" } else { "" };
println!("{}{exists}", p.display());
}
}
}
}
fn load_cfg(stem: &str, dir: Option<&str>, is_file: bool) -> ConfigGet {
let result = if is_file {
ConfigGet::from_file(stem)
} else {
ConfigGet::builder(stem)
.config_dir(dir.unwrap_or(stem))
.build()
};
result.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
})
}
}
fn main() {
#[cfg(feature = "cli")]
{
log::set_max_level(log::LevelFilter::Info);
cli::run();
}
#[cfg(not(feature = "cli"))]
{
eprintln!("Recompile with `--features cli` to enable the CLI binary.");
std::process::exit(1);
}
}