settingspec 0.5.0__tar.gz → 0.5.1__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.5.1}/Cargo.lock +67 -1
  2. {settingspec-0.5.0 → settingspec-0.5.1}/Cargo.toml +2 -1
  3. {settingspec-0.5.0 → settingspec-0.5.1}/PKG-INFO +26 -9
  4. {settingspec-0.5.0 → settingspec-0.5.1}/README.md +25 -8
  5. {settingspec-0.5.0 → settingspec-0.5.1}/src/cli.rs +74 -44
  6. {settingspec-0.5.0 → settingspec-0.5.1}/LICENSE-APACHE +0 -0
  7. {settingspec-0.5.0 → settingspec-0.5.1}/LICENSE-MIT +0 -0
  8. {settingspec-0.5.0 → settingspec-0.5.1}/pyproject.toml +0 -0
  9. {settingspec-0.5.0 → settingspec-0.5.1}/src/envfile.rs +0 -0
  10. {settingspec-0.5.0 → settingspec-0.5.1}/src/error.rs +0 -0
  11. {settingspec-0.5.0 → settingspec-0.5.1}/src/filter.rs +0 -0
  12. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/common.rs +0 -0
  13. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/env.rs +0 -0
  14. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/javascript.rs +0 -0
  15. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/json.rs +0 -0
  16. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/lua.rs +0 -0
  17. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/mod.rs +0 -0
  18. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/python.rs +0 -0
  19. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/toml.rs +0 -0
  20. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/tree.rs +0 -0
  21. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/typescript.rs +0 -0
  22. {settingspec-0.5.0 → settingspec-0.5.1}/src/generator/yaml.rs +0 -0
  23. {settingspec-0.5.0 → settingspec-0.5.1}/src/lib.rs +0 -0
  24. {settingspec-0.5.0 → settingspec-0.5.1}/src/main.rs +0 -0
  25. {settingspec-0.5.0 → settingspec-0.5.1}/src/model.rs +0 -0
  26. {settingspec-0.5.0 → settingspec-0.5.1}/src/parser.rs +0 -0
  27. {settingspec-0.5.0 → settingspec-0.5.1}/src/profile.rs +0 -0
  28. {settingspec-0.5.0 → settingspec-0.5.1}/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.5.1"
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.5.1"
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.5.1
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,22 @@ 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
+
200
+ # decryption.key.env = "AGEBOX_PRIVATE_KEYS"
201
+ # decryption.key.env = "COTTAGE_IDENTITY"
202
+ decryption.key.path = "~/.config/cottage/identity:~/.ssh"
203
+
204
+ [settings]
205
+ secret.default.env = "SECRET"
206
+ ```
207
+
191
208
  ## Learn more
192
209
 
193
210
  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,22 @@ 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
+
183
+ # decryption.key.env = "AGEBOX_PRIVATE_KEYS"
184
+ # decryption.key.env = "COTTAGE_IDENTITY"
185
+ decryption.key.path = "~/.config/cottage/identity:~/.ssh"
186
+
187
+ [settings]
188
+ secret.default.env = "SECRET"
189
+ ```
190
+
174
191
  ## Learn more
175
192
 
176
193
  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",
@@ -156,35 +174,66 @@ pub fn run_cli() -> Result<()> {
156
174
 
157
175
  fn run_watch() -> Result<()> {
158
176
  let ctx = load_and_resolve()?;
159
- perform_export(&ctx)?;
160
-
161
177
  let config_path = ctx.project_root.join("settingspec.toml");
178
+ let mut guard = CleanupGuard::default();
162
179
 
163
180
  let (tx, rx) = mpsc::channel();
181
+ let (txsig, rxsig) = mpsc::channel();
182
+
183
+ ctrlc::set_handler(move || txsig.send(()).expect("Could not send signal on channel."))
184
+ .expect("Error setting Ctrl-C handler");
164
185
 
165
186
  std::thread::spawn(move || {
187
+ let _ = tx.send(true); // Trigger initial export
188
+
166
189
  let mut modified = std::fs::metadata(&config_path)
167
190
  .ok()
168
191
  .and_then(|m| m.modified().ok());
192
+
169
193
  loop {
170
194
  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(());
195
+ if rxsig.try_recv().is_ok() {
196
+ let _ = tx.send(false);
197
+ break;
198
+ } else {
199
+ let new_modified = std::fs::metadata(&config_path)
200
+ .ok()
201
+ .and_then(|m| m.modified().ok());
202
+ if new_modified != modified {
203
+ modified = new_modified;
204
+ let _ = tx.send(true);
205
+ }
177
206
  }
178
207
  }
179
208
  });
180
209
 
181
- while rx.recv().is_ok() {
182
- println!("Detected change in settingspec.toml, reloading...");
210
+ while rx.recv().unwrap_or(false) {
183
211
  match load_and_resolve() {
184
- Ok(ctx) => {
212
+ Ok(mut ctx) => {
213
+ guard.keep = ctx.doc.spec.export.keep;
214
+ if ctx.doc.spec.export.file.is_empty() {
215
+ eprintln!("No export files configured, skipping export.");
216
+ continue;
217
+ }
218
+ ctx.doc.spec.export.stdout = None;
219
+ ctx.doc.spec.export.stdin = None;
220
+ ctx.doc.spec.export.env = None;
221
+
222
+ eprintln!("Exporting files...");
185
223
  if let Err(e) = perform_export(&ctx) {
186
224
  eprintln!("Error during export: {}", e);
187
225
  }
226
+ ctx.doc
227
+ .spec
228
+ .export
229
+ .file
230
+ .iter()
231
+ .for_each(|(file_path_str, filter)| {
232
+ if !matches!(filter, ExportFilter::Disabled) {
233
+ guard.paths.insert(PathBuf::from(file_path_str));
234
+ println!("{file_path_str}");
235
+ }
236
+ });
188
237
  }
189
238
  Err(e) => {
190
239
  eprintln!("Error loading configuration: {}", e);
@@ -192,6 +241,7 @@ fn run_watch() -> Result<()> {
192
241
  }
193
242
  }
194
243
 
244
+ eprintln!("Exiting watch mode.");
195
245
  Ok(())
196
246
  }
197
247
 
@@ -236,28 +286,10 @@ fn execute_run(ctx: &ExecutionContext, command_args: &[String]) -> Result<()> {
236
286
  }
237
287
 
238
288
  // 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,
289
+ let _guard = perform_export(ctx).map(|files| CleanupGuard {
259
290
  keep: ctx.doc.spec.export.keep,
260
- };
291
+ paths: files.into_iter().collect(),
292
+ })?;
261
293
 
262
294
  let stdin_payload = if let Some(ref stdin_fmt) = ctx.doc.spec.export.stdin {
263
295
  Some(generate_by_format(stdin_fmt, &ctx.resolved_settings)?)
@@ -418,16 +450,14 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
418
450
  return Ok(());
419
451
  }
420
452
 
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
- };
453
+ if find_git_root(&ctx.project_root).is_none() {
454
+ return Ok(());
455
+ }
426
456
 
427
- let git_root_canon = git_root.canonicalize().unwrap_or_else(|_| git_root.clone());
428
- let current_dir_canon = current_dir
457
+ let project_root = &ctx.project_root;
458
+ let project_root_canon = project_root
429
459
  .canonicalize()
430
- .unwrap_or_else(|_| current_dir.clone());
460
+ .unwrap_or_else(|_| project_root.clone());
431
461
 
432
462
  let mut entries_to_add = Vec::new();
433
463
 
@@ -438,13 +468,13 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
438
468
 
439
469
  let target_path = Path::new(file_path_str);
440
470
  let full_target = if target_path.is_relative() {
441
- current_dir_canon.join(target_path)
471
+ project_root_canon.join(target_path)
442
472
  } else {
443
473
  target_path.to_path_buf()
444
474
  };
445
475
 
446
476
  let normalized = normalize_path(&full_target);
447
- let rel_path = match normalized.strip_prefix(&git_root_canon) {
477
+ let rel_path = match normalized.strip_prefix(&project_root_canon) {
448
478
  Ok(rel) => rel,
449
479
  Err(_) => continue,
450
480
  };
@@ -468,7 +498,7 @@ pub fn auto_append_gitignore(ctx: &ExecutionContext) -> Result<()> {
468
498
  return Ok(());
469
499
  }
470
500
 
471
- let gitignore_path = git_root.join(".gitignore");
501
+ let gitignore_path = project_root.join(".gitignore");
472
502
  let existing_content = if gitignore_path.exists() {
473
503
  std::fs::read_to_string(&gitignore_path)?
474
504
  } 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