settingspec 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {settingspec-0.5.0 → settingspec-0.6.0}/Cargo.lock +67 -1
  2. {settingspec-0.5.0 → settingspec-0.6.0}/Cargo.toml +2 -1
  3. {settingspec-0.5.0 → settingspec-0.6.0}/PKG-INFO +27 -9
  4. {settingspec-0.5.0 → settingspec-0.6.0}/README.md +26 -8
  5. {settingspec-0.5.0 → settingspec-0.6.0}/src/cli.rs +89 -44
  6. {settingspec-0.5.0 → settingspec-0.6.0}/LICENSE-APACHE +0 -0
  7. {settingspec-0.5.0 → settingspec-0.6.0}/LICENSE-MIT +0 -0
  8. {settingspec-0.5.0 → settingspec-0.6.0}/pyproject.toml +0 -0
  9. {settingspec-0.5.0 → settingspec-0.6.0}/src/envfile.rs +0 -0
  10. {settingspec-0.5.0 → settingspec-0.6.0}/src/error.rs +0 -0
  11. {settingspec-0.5.0 → settingspec-0.6.0}/src/filter.rs +0 -0
  12. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/common.rs +0 -0
  13. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/env.rs +0 -0
  14. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/javascript.rs +0 -0
  15. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/json.rs +0 -0
  16. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/lua.rs +0 -0
  17. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/mod.rs +0 -0
  18. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/python.rs +0 -0
  19. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/toml.rs +0 -0
  20. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/tree.rs +0 -0
  21. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/typescript.rs +0 -0
  22. {settingspec-0.5.0 → settingspec-0.6.0}/src/generator/yaml.rs +0 -0
  23. {settingspec-0.5.0 → settingspec-0.6.0}/src/lib.rs +0 -0
  24. {settingspec-0.5.0 → settingspec-0.6.0}/src/main.rs +0 -0
  25. {settingspec-0.5.0 → settingspec-0.6.0}/src/model.rs +0 -0
  26. {settingspec-0.5.0 → settingspec-0.6.0}/src/parser.rs +0 -0
  27. {settingspec-0.5.0 → settingspec-0.6.0}/src/profile.rs +0 -0
  28. {settingspec-0.5.0 → settingspec-0.6.0}/src/resolver.rs +0 -0
@@ -282,6 +282,15 @@ dependencies = [
282
282
  "generic-array",
283
283
  ]
284
284
 
285
+ [[package]]
286
+ name = "block2"
287
+ version = "0.6.2"
288
+ source = "registry+https://github.com/rust-lang/crates.io-index"
289
+ checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
290
+ dependencies = [
291
+ "objc2",
292
+ ]
293
+
285
294
  [[package]]
286
295
  name = "blowfish"
287
296
  version = "0.9.1"
@@ -324,6 +333,12 @@ version = "1.0.5"
324
333
  source = "registry+https://github.com/rust-lang/crates.io-index"
325
334
  checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
326
335
 
336
+ [[package]]
337
+ name = "cfg_aliases"
338
+ version = "0.2.2"
339
+ source = "registry+https://github.com/rust-lang/crates.io-index"
340
+ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
341
+
327
342
  [[package]]
328
343
  name = "chacha20"
329
344
  version = "0.9.1"
@@ -519,6 +534,17 @@ dependencies = [
519
534
  "cipher",
520
535
  ]
521
536
 
537
+ [[package]]
538
+ name = "ctrlc"
539
+ version = "3.5.2"
540
+ source = "registry+https://github.com/rust-lang/crates.io-index"
541
+ checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
542
+ dependencies = [
543
+ "dispatch2",
544
+ "nix",
545
+ "windows-sys",
546
+ ]
547
+
522
548
  [[package]]
523
549
  name = "curve25519-dalek"
524
550
  version = "4.1.3"
@@ -584,6 +610,18 @@ dependencies = [
584
610
  "crypto-common 0.2.2",
585
611
  ]
586
612
 
613
+ [[package]]
614
+ name = "dispatch2"
615
+ version = "0.3.1"
616
+ source = "registry+https://github.com/rust-lang/crates.io-index"
617
+ checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
618
+ dependencies = [
619
+ "bitflags",
620
+ "block2",
621
+ "libc",
622
+ "objc2",
623
+ ]
624
+
587
625
  [[package]]
588
626
  name = "displaydoc"
589
627
  version = "0.2.7"
@@ -1180,6 +1218,18 @@ dependencies = [
1180
1218
  "sha3",
1181
1219
  ]
1182
1220
 
1221
+ [[package]]
1222
+ name = "nix"
1223
+ version = "0.31.3"
1224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1225
+ checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
1226
+ dependencies = [
1227
+ "bitflags",
1228
+ "cfg-if",
1229
+ "cfg_aliases",
1230
+ "libc",
1231
+ ]
1232
+
1183
1233
  [[package]]
1184
1234
  name = "nom"
1185
1235
  version = "8.0.0"
@@ -1240,6 +1290,21 @@ dependencies = [
1240
1290
  "libm",
1241
1291
  ]
1242
1292
 
1293
+ [[package]]
1294
+ name = "objc2"
1295
+ version = "0.6.4"
1296
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1297
+ checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
1298
+ dependencies = [
1299
+ "objc2-encode",
1300
+ ]
1301
+
1302
+ [[package]]
1303
+ name = "objc2-encode"
1304
+ version = "4.1.0"
1305
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1306
+ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
1307
+
1243
1308
  [[package]]
1244
1309
  name = "once_cell"
1245
1310
  version = "1.21.4"
@@ -1758,7 +1823,7 @@ dependencies = [
1758
1823
 
1759
1824
  [[package]]
1760
1825
  name = "settingspec"
1761
- version = "0.5.0"
1826
+ version = "0.6.0"
1762
1827
  dependencies = [
1763
1828
  "age",
1764
1829
  "anyhow",
@@ -1766,6 +1831,7 @@ dependencies = [
1766
1831
  "assert_fs",
1767
1832
  "clap",
1768
1833
  "clap_complete",
1834
+ "ctrlc",
1769
1835
  "dotenvy",
1770
1836
  "indexmap",
1771
1837
  "predicates",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "settingspec"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  edition = "2024"
5
5
  description = "One file for all environments, all languages, all submodules."
6
6
  readme = "README.md"
@@ -40,6 +40,7 @@ serde_yaml = "0.9"
40
40
  indexmap = { version = "2", features = ["serde"] }
41
41
  dotenvy = "0.15.7"
42
42
  age = { version = "0.12.1", features = ["armor", "ssh"] }
43
+ ctrlc = "3.5.2"
43
44
 
44
45
  [dev-dependencies]
45
46
  assert_fs = "1.1.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: settingspec
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -30,14 +30,15 @@ Project-URL: Source Code, https://github.com/sayanarijit/settingspec
30
30
 
31
31
  # SettingSpec
32
32
 
33
- 1. [Concepts](#concepts)
34
- 1. [Single file convenience](#single-file-convenience)
35
- 2. [Declarative profiles as environments](#declarative-profiles-as-environments)
36
- 3. [Language independent](#language-independent)
37
- 4. [Easy secrets](#easy-secrets)
38
- 1. [SecretSpec](#secretspec)
39
- 2. [Dotenv](#dotenv)
40
- 2. [Learn more](#learn-more)
33
+ 1. [Concepts](#concepts)
34
+ 1. [Single file convenience](#single-file-convenience)
35
+ 2. [Declarative profiles as environments](#declarative-profiles-as-environments)
36
+ 3. [Language independent](#language-independent)
37
+ 4. [Easy secrets](#easy-secrets)
38
+ 1. [SecretSpec](#secretspec)
39
+ 2. [Dotenv](#dotenv)
40
+ 3. [Cottage or agebox](#cottage-or-agebox)
41
+ 2. [Learn more](#learn-more)
41
42
 
42
43
  ## Concepts
43
44
 
@@ -188,6 +189,23 @@ envfile.prod = "-" # Read from stdin, encrypted or not
188
189
  secret1.default.env = "SECRET1" # Load value from $SECRET1
189
190
  ```
190
191
 
192
+ #### Cottage or agebox
193
+
194
+ Too simple? Use [Cottage](https://cottage-cli.pages.dev) or [agebox](https://github.com/slok/agebox).
195
+
196
+ ```toml
197
+ [spec]
198
+ envfile.default = ".env.cott.age"
199
+ # envfile.default = ".env.agebox"
200
+
201
+ decryption.key.path = "~/.config/cottage/identity:~/.ssh"
202
+ decryption.key.env = "COTTAGE_IDENTITY"
203
+ # decryption.key.env = "AGEBOX_PRIVATE_KEYS"
204
+
205
+ [settings]
206
+ secret.default.env = "SECRET"
207
+ ```
208
+
191
209
  ## Learn more
192
210
 
193
211
  Check out the [settingspec.toml](./settingspec.toml) example file and [full specification](./SPECIFICATION.md) for more details.
@@ -13,14 +13,15 @@
13
13
 
14
14
  # SettingSpec
15
15
 
16
- 1. [Concepts](#concepts)
17
- 1. [Single file convenience](#single-file-convenience)
18
- 2. [Declarative profiles as environments](#declarative-profiles-as-environments)
19
- 3. [Language independent](#language-independent)
20
- 4. [Easy secrets](#easy-secrets)
21
- 1. [SecretSpec](#secretspec)
22
- 2. [Dotenv](#dotenv)
23
- 2. [Learn more](#learn-more)
16
+ 1. [Concepts](#concepts)
17
+ 1. [Single file convenience](#single-file-convenience)
18
+ 2. [Declarative profiles as environments](#declarative-profiles-as-environments)
19
+ 3. [Language independent](#language-independent)
20
+ 4. [Easy secrets](#easy-secrets)
21
+ 1. [SecretSpec](#secretspec)
22
+ 2. [Dotenv](#dotenv)
23
+ 3. [Cottage or agebox](#cottage-or-agebox)
24
+ 2. [Learn more](#learn-more)
24
25
 
25
26
  ## Concepts
26
27
 
@@ -171,6 +172,23 @@ envfile.prod = "-" # Read from stdin, encrypted or not
171
172
  secret1.default.env = "SECRET1" # Load value from $SECRET1
172
173
  ```
173
174
 
175
+ #### Cottage or agebox
176
+
177
+ Too simple? Use [Cottage](https://cottage-cli.pages.dev) or [agebox](https://github.com/slok/agebox).
178
+
179
+ ```toml
180
+ [spec]
181
+ envfile.default = ".env.cott.age"
182
+ # envfile.default = ".env.agebox"
183
+
184
+ decryption.key.path = "~/.config/cottage/identity:~/.ssh"
185
+ decryption.key.env = "COTTAGE_IDENTITY"
186
+ # decryption.key.env = "AGEBOX_PRIVATE_KEYS"
187
+
188
+ [settings]
189
+ secret.default.env = "SECRET"
190
+ ```
191
+
174
192
  ## Learn more
175
193
 
176
194
  Check out the [settingspec.toml](./settingspec.toml) example file and [full specification](./SPECIFICATION.md) for more details.
@@ -7,12 +7,30 @@ use crate::parser::parse_config_str;
7
7
  use crate::profile::resolve_active_profile;
8
8
  use crate::resolver::resolve_settings;
9
9
  use clap::{Parser, Subcommand};
10
- use std::collections::HashMap;
10
+ use std::collections::{HashMap, HashSet};
11
11
  use std::io::Write;
12
12
  use std::path::{Path, PathBuf};
13
13
  use std::sync::mpsc;
14
14
  use std::time::Duration;
15
15
 
16
+ #[derive(Default)]
17
+ struct CleanupGuard {
18
+ keep: bool,
19
+ paths: HashSet<PathBuf>,
20
+ }
21
+
22
+ impl Drop for CleanupGuard {
23
+ fn drop(&mut self) {
24
+ if !self.keep {
25
+ self.paths.iter().for_each(|path| {
26
+ if std::fs::exists(path).unwrap_or(false) {
27
+ let _ = std::fs::remove_file(path);
28
+ }
29
+ });
30
+ }
31
+ }
32
+ }
33
+
16
34
  #[derive(Parser, Debug)]
17
35
  #[command(
18
36
  name = "settingspec",
@@ -39,6 +57,9 @@ pub enum Commands {
39
57
  },
40
58
  /// Watches settingspec.toml and regenerates exports whenever it changes
41
59
  Watch,
60
+
61
+ /// Deletes all generated export files
62
+ Clean,
42
63
  }
43
64
 
44
65
  pub const INIT_CONFIG: &str = r#"[spec]
@@ -151,40 +172,72 @@ pub fn run_cli() -> Result<()> {
151
172
  execute_run(&ctx, &command)
152
173
  }
153
174
  Commands::Watch => run_watch(),
175
+ Commands::Clean => clean_exported_files(),
154
176
  }
155
177
  }
156
178
 
157
179
  fn run_watch() -> Result<()> {
158
180
  let ctx = load_and_resolve()?;
159
- perform_export(&ctx)?;
160
-
161
181
  let config_path = ctx.project_root.join("settingspec.toml");
182
+ let mut guard = CleanupGuard::default();
162
183
 
163
184
  let (tx, rx) = mpsc::channel();
185
+ let (txsig, rxsig) = mpsc::channel();
186
+
187
+ ctrlc::set_handler(move || txsig.send(()).expect("Could not send signal on channel."))
188
+ .expect("Error setting Ctrl-C handler");
164
189
 
165
190
  std::thread::spawn(move || {
191
+ let _ = tx.send(true); // Trigger initial export
192
+
166
193
  let mut modified = std::fs::metadata(&config_path)
167
194
  .ok()
168
195
  .and_then(|m| m.modified().ok());
196
+
169
197
  loop {
170
198
  std::thread::sleep(Duration::from_secs(1));
171
- let new_modified = std::fs::metadata(&config_path)
172
- .ok()
173
- .and_then(|m| m.modified().ok());
174
- if new_modified != modified {
175
- modified = new_modified;
176
- let _ = tx.send(());
199
+ if rxsig.try_recv().is_ok() {
200
+ let _ = tx.send(false);
201
+ break;
202
+ } else {
203
+ let new_modified = std::fs::metadata(&config_path)
204
+ .ok()
205
+ .and_then(|m| m.modified().ok());
206
+ if new_modified != modified {
207
+ modified = new_modified;
208
+ let _ = tx.send(true);
209
+ }
177
210
  }
178
211
  }
179
212
  });
180
213
 
181
- while rx.recv().is_ok() {
182
- println!("Detected change in settingspec.toml, reloading...");
214
+ while rx.recv().unwrap_or(false) {
183
215
  match load_and_resolve() {
184
- Ok(ctx) => {
216
+ Ok(mut ctx) => {
217
+ guard.keep = ctx.doc.spec.export.keep;
218
+ if ctx.doc.spec.export.file.is_empty() {
219
+ eprintln!("No export files configured, skipping export.");
220
+ continue;
221
+ }
222
+ ctx.doc.spec.export.stdout = None;
223
+ ctx.doc.spec.export.stdin = None;
224
+ ctx.doc.spec.export.env = None;
225
+
226
+ eprintln!("Exporting files...");
185
227
  if let Err(e) = perform_export(&ctx) {
186
228
  eprintln!("Error during export: {}", e);
187
229
  }
230
+ ctx.doc
231
+ .spec
232
+ .export
233
+ .file
234
+ .iter()
235
+ .for_each(|(file_path_str, filter)| {
236
+ if !matches!(filter, ExportFilter::Disabled) {
237
+ guard.paths.insert(PathBuf::from(file_path_str));
238
+ println!("{file_path_str}");
239
+ }
240
+ });
188
241
  }
189
242
  Err(e) => {
190
243
  eprintln!("Error loading configuration: {}", e);
@@ -192,6 +245,7 @@ fn run_watch() -> Result<()> {
192
245
  }
193
246
  }
194
247
 
248
+ eprintln!("Exiting watch mode.");
195
249
  Ok(())
196
250
  }
197
251
 
@@ -228,6 +282,17 @@ fn perform_export(ctx: &ExecutionContext) -> Result<Vec<PathBuf>> {
228
282
  Ok(generated_files)
229
283
  }
230
284
 
285
+ fn clean_exported_files() -> Result<()> {
286
+ let ctx = load_and_resolve()?;
287
+
288
+ let _guard = CleanupGuard {
289
+ keep: false,
290
+ paths: ctx.doc.spec.export.file.keys().map(PathBuf::from).collect(),
291
+ };
292
+
293
+ Ok(())
294
+ }
295
+
231
296
  fn execute_run(ctx: &ExecutionContext, command_args: &[String]) -> Result<()> {
232
297
  if command_args.is_empty() {
233
298
  return Err(SettingSpecError::InvalidToml(
@@ -236,28 +301,10 @@ fn execute_run(ctx: &ExecutionContext, command_args: &[String]) -> Result<()> {
236
301
  }
237
302
 
238
303
  // Export all files
239
- let generated_files = perform_export(ctx)?;
240
-
241
- // Setup cleanup guard
242
- struct CleanupGuard {
243
- files: Vec<PathBuf>,
244
- keep: bool,
245
- }
246
-
247
- impl Drop for CleanupGuard {
248
- fn drop(&mut self) {
249
- if !self.keep {
250
- for f in &self.files {
251
- let _ = std::fs::remove_file(f);
252
- }
253
- }
254
- }
255
- }
256
-
257
- let _guard = CleanupGuard {
258
- files: generated_files,
304
+ let _guard = perform_export(ctx).map(|files| CleanupGuard {
259
305
  keep: ctx.doc.spec.export.keep,
260
- };
306
+ paths: files.into_iter().collect(),
307
+ })?;
261
308
 
262
309
  let stdin_payload = if let Some(ref stdin_fmt) = ctx.doc.spec.export.stdin {
263
310
  Some(generate_by_format(stdin_fmt, &ctx.resolved_settings)?)
@@ -418,16 +465,14 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
418
465
  return Ok(());
419
466
  }
420
467
 
421
- let current_dir = &ctx.project_root;
422
- let git_root = match find_git_root(current_dir) {
423
- Some(root) => root,
424
- None => return Ok(()),
425
- };
468
+ if find_git_root(&ctx.project_root).is_none() {
469
+ return Ok(());
470
+ }
426
471
 
427
- let git_root_canon = git_root.canonicalize().unwrap_or_else(|_| git_root.clone());
428
- let current_dir_canon = current_dir
472
+ let project_root = &ctx.project_root;
473
+ let project_root_canon = project_root
429
474
  .canonicalize()
430
- .unwrap_or_else(|_| current_dir.clone());
475
+ .unwrap_or_else(|_| project_root.clone());
431
476
 
432
477
  let mut entries_to_add = Vec::new();
433
478
 
@@ -438,13 +483,13 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
438
483
 
439
484
  let target_path = Path::new(file_path_str);
440
485
  let full_target = if target_path.is_relative() {
441
- current_dir_canon.join(target_path)
486
+ project_root_canon.join(target_path)
442
487
  } else {
443
488
  target_path.to_path_buf()
444
489
  };
445
490
 
446
491
  let normalized = normalize_path(&full_target);
447
- let rel_path = match normalized.strip_prefix(&git_root_canon) {
492
+ let rel_path = match normalized.strip_prefix(&project_root_canon) {
448
493
  Ok(rel) => rel,
449
494
  Err(_) => continue,
450
495
  };
@@ -468,7 +513,7 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
468
513
  return Ok(());
469
514
  }
470
515
 
471
- let gitignore_path = git_root.join(".gitignore");
516
+ let gitignore_path = project_root.join(".gitignore");
472
517
  let existing_content = if gitignore_path.exists() {
473
518
  std::fs::read_to_string(&gitignore_path)?
474
519
  } else {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes