mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-22 18:57:13 +00:00
remember the whole setup, not a third of it
The config file held one key while the program grew three axes, so --set-default recorded the jurisdiction and silently dropped the discipline and the usage. On this machine that produced exactly the failure it sounds like: a terminal reckoning by ROCOR beside a desktop widget reckoning by the Ruthenian calendar, both correct, neither agreeing. --set-default now writes whatever was given alongside it, and an ordinary run reads all three back with flags still winning over the file.
This commit is contained in:
@@ -100,8 +100,17 @@ $ typikon --list # every accepted value
|
||||
```
|
||||
|
||||
The first run writes your choice to `~/.config/typikon/config.toml` and stops
|
||||
asking. Change it with `--set-default`, override it for one run with the flag,
|
||||
or set `TYPIKON_JURISDICTION`.
|
||||
asking. `--set-default` remembers the whole invocation, not just the
|
||||
jurisdiction:
|
||||
|
||||
```console
|
||||
$ typikon --set-default ruthenian --typikon
|
||||
```
|
||||
|
||||
records the discipline and the usage alongside the church, so a terminal and a
|
||||
desktop widget on the same machine cannot end up disagreeing about the same
|
||||
day. Override any of it for one run with the flags, or set
|
||||
`TYPIKON_JURISDICTION`.
|
||||
|
||||
Twenty-three jurisdictions ship: the old-calendar churches (Moscow, ROCOR,
|
||||
Serbia, Georgia, Jerusalem, the Holy Mountain, Poland, Macedonia), the
|
||||
|
||||
+50
-19
@@ -26,43 +26,74 @@ pub fn path() -> PathBuf {
|
||||
base.join("typikon").join("config.toml")
|
||||
}
|
||||
|
||||
/// The one key this file holds, read without a TOML parser because a
|
||||
/// dependency for `key = "value"` is a dependency too many.
|
||||
fn read() -> Option<String> {
|
||||
let text = std::fs::read_to_string(path()).ok()?;
|
||||
/// Everything the file can hold. All three axes, because remembering only the
|
||||
/// jurisdiction meant a terminal and a desktop widget on the same machine
|
||||
/// could disagree about the same day.
|
||||
#[derive(Default)]
|
||||
pub struct Saved {
|
||||
pub jurisdiction: Option<String>,
|
||||
pub tradition: Option<String>,
|
||||
/// True when the day should be shown at the Typikon's discipline rather
|
||||
/// than at the jurisdiction's own particular law.
|
||||
pub typikon: bool,
|
||||
}
|
||||
|
||||
/// Read without a TOML parser, because a dependency for `key = "value"` is a
|
||||
/// dependency too many.
|
||||
fn read() -> Saved {
|
||||
let mut out = Saved::default();
|
||||
let Ok(text) = std::fs::read_to_string(path()) else { return out };
|
||||
for line in text.lines() {
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let Some((key, value)) = line.split_once('=') else { continue };
|
||||
if key.trim() != "jurisdiction" {
|
||||
let value = value.trim().trim_matches(['"', '\'']);
|
||||
if value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let value = value.trim().trim_matches(['"', '\'']);
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_string());
|
||||
match key.trim() {
|
||||
"jurisdiction" => out.jurisdiction = Some(value.to_string()),
|
||||
"tradition" => out.tradition = Some(value.to_string()),
|
||||
"typikon" => out.typikon = value == "true",
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
out
|
||||
}
|
||||
|
||||
pub fn write(jurisdiction: &str) -> std::io::Result<PathBuf> {
|
||||
pub fn write(saved: &Saved) -> std::io::Result<PathBuf> {
|
||||
let p = path();
|
||||
if let Some(dir) = p.parent() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
std::fs::write(
|
||||
&p,
|
||||
format!(
|
||||
"# Which reckoning typikon uses when no --jurisdiction is given.\n\
|
||||
# Run `typikon --list` to see every accepted value.\n\
|
||||
jurisdiction = \"{jurisdiction}\"\n"
|
||||
),
|
||||
)?;
|
||||
let mut text = String::from(
|
||||
"# What typikon assumes when the flags are not given.\n\
|
||||
# Run `typikon --list` to see every accepted jurisdiction.\n",
|
||||
);
|
||||
if let Some(j) = &saved.jurisdiction {
|
||||
text.push_str(&format!("jurisdiction = \"{j}\"\n"));
|
||||
}
|
||||
if let Some(t) = &saved.tradition {
|
||||
text.push_str(&format!("tradition = \"{t}\"\n"));
|
||||
}
|
||||
if saved.typikon {
|
||||
text.push_str(
|
||||
"\n# Show the day at the Typikon's full discipline rather than at the\n\
|
||||
# minimum this church's particular law requires. Obligations are still\n\
|
||||
# reported either way.\n\
|
||||
typikon = true\n",
|
||||
);
|
||||
}
|
||||
std::fs::write(&p, text)?;
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
pub fn saved() -> Saved {
|
||||
read()
|
||||
}
|
||||
|
||||
/// The environment beats the config file; both beat the built-in default.
|
||||
pub fn default_jurisdiction() -> (Option<String>, Source) {
|
||||
if let Ok(v) = std::env::var(ENV_VAR) {
|
||||
@@ -70,7 +101,7 @@ pub fn default_jurisdiction() -> (Option<String>, Source) {
|
||||
return (Some(v), Source::Environment);
|
||||
}
|
||||
}
|
||||
match read() {
|
||||
match read().jurisdiction {
|
||||
Some(v) => (Some(v), Source::Config),
|
||||
None => (None, Source::Default),
|
||||
}
|
||||
|
||||
+32
-4
@@ -291,8 +291,31 @@ fn run() -> Result<i32, String> {
|
||||
if let Some(name) = &args.set_default {
|
||||
let jur = jurisdictions::resolve(Some(name))
|
||||
.map_err(|n| format!("unknown jurisdiction {n:?}; try --list"))?;
|
||||
let where_ = config::write(jur.key).map_err(|e| e.to_string())?;
|
||||
// Whatever else was given alongside is remembered too, so that
|
||||
// `--set-default ruthenian --typikon` records the whole setup rather
|
||||
// than half of it and leaves a terminal disagreeing with a widget.
|
||||
let tradition = match &args.tradition {
|
||||
None => None,
|
||||
Some(t) => Some(
|
||||
jurisdictions::Tradition::parse(t)
|
||||
.ok_or_else(|| format!("unknown tradition {t:?}; try slavic or greek"))?
|
||||
.key()
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
let saved = config::Saved {
|
||||
jurisdiction: Some(jur.key.to_string()),
|
||||
tradition,
|
||||
typikon: args.typikon,
|
||||
};
|
||||
let where_ = config::write(&saved).map_err(|e| e.to_string())?;
|
||||
println!(" {} \u{2014} written to {}", jur.name, where_.display());
|
||||
if let Some(t) = &saved.tradition {
|
||||
println!(" {t} usage");
|
||||
}
|
||||
if saved.typikon {
|
||||
println!(" shown at the Typikon's discipline");
|
||||
}
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
@@ -307,12 +330,13 @@ fn run() -> Result<i32, String> {
|
||||
// carries. Leaked as a 'static because a Jurisdiction is otherwise a
|
||||
// compile-time constant, and one override per run is not worth threading
|
||||
// a lifetime through the whole library for.
|
||||
let tradition = match &args.tradition {
|
||||
let saved = config::saved();
|
||||
let tradition = match args.tradition.as_deref().or(saved.tradition.as_deref()) {
|
||||
None => base.tradition,
|
||||
Some(t) => jurisdictions::Tradition::parse(t)
|
||||
.ok_or_else(|| format!("unknown tradition {t:?}; try slavic or greek"))?,
|
||||
};
|
||||
let discipline = if args.typikon {
|
||||
let discipline = if args.typikon || saved.typikon {
|
||||
Some(jurisdictions::RuleKind::Orthodox)
|
||||
} else {
|
||||
base.discipline
|
||||
@@ -399,7 +423,11 @@ fn run() -> Result<i32, String> {
|
||||
|
||||
// The first run should say what it assumed, and then stop saying it.
|
||||
if source == Source::Default {
|
||||
if let Ok(p) = config::write(jur.key) {
|
||||
let first = config::Saved {
|
||||
jurisdiction: Some(jur.key.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
if let Ok(p) = config::write(&first) {
|
||||
println!(" Reckoning by the {}, written to {}.", jur.name.to_lowercase(), p.display());
|
||||
println!(" Change it with `typikon --set-default <jurisdiction>`, or see `typikon --list`.\n");
|
||||
}
|
||||
|
||||
@@ -585,3 +585,43 @@ fn the_override_changes_nothing_for_a_jurisdiction_already_on_the_typikon() {
|
||||
assert_eq!(a.obligation, b.obligation, "{}", a.date);
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────── what gets remembered ──────────────────────────
|
||||
|
||||
#[test]
|
||||
fn the_config_round_trips_all_three_axes() {
|
||||
use typikon::config::{self, Saved};
|
||||
// A scratch XDG_CONFIG_HOME, so the test cannot touch a real one.
|
||||
let dir = std::env::temp_dir().join(format!("typikon-test-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
// SAFETY: single-threaded test, and the variable is restored below.
|
||||
let prev = std::env::var_os("XDG_CONFIG_HOME");
|
||||
unsafe { std::env::set_var("XDG_CONFIG_HOME", &dir) };
|
||||
|
||||
config::write(&Saved {
|
||||
jurisdiction: Some("ruthenian".into()),
|
||||
tradition: Some("slavic".into()),
|
||||
typikon: true,
|
||||
})
|
||||
.unwrap();
|
||||
let back = config::saved();
|
||||
assert_eq!(back.jurisdiction.as_deref(), Some("ruthenian"));
|
||||
assert_eq!(back.tradition.as_deref(), Some("slavic"));
|
||||
assert!(back.typikon);
|
||||
|
||||
// Remembering only a jurisdiction leaves the other two unset, rather than
|
||||
// inventing a default that would disagree with the flags.
|
||||
config::write(&Saved { jurisdiction: Some("rocor".into()), ..Default::default() }).unwrap();
|
||||
let plain = config::saved();
|
||||
assert_eq!(plain.jurisdiction.as_deref(), Some("rocor"));
|
||||
assert_eq!(plain.tradition, None);
|
||||
assert!(!plain.typikon);
|
||||
|
||||
unsafe {
|
||||
match prev {
|
||||
Some(v) => std::env::set_var("XDG_CONFIG_HOME", v),
|
||||
None => std::env::remove_var("XDG_CONFIG_HOME"),
|
||||
}
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user