mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-23 11:07:16 +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
|
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,
|
asking. `--set-default` remembers the whole invocation, not just the
|
||||||
or set `TYPIKON_JURISDICTION`.
|
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,
|
Twenty-three jurisdictions ship: the old-calendar churches (Moscow, ROCOR,
|
||||||
Serbia, Georgia, Jerusalem, the Holy Mountain, Poland, Macedonia), the
|
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")
|
base.join("typikon").join("config.toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The one key this file holds, read without a TOML parser because a
|
/// Everything the file can hold. All three axes, because remembering only the
|
||||||
/// dependency for `key = "value"` is a dependency too many.
|
/// jurisdiction meant a terminal and a desktop widget on the same machine
|
||||||
fn read() -> Option<String> {
|
/// could disagree about the same day.
|
||||||
let text = std::fs::read_to_string(path()).ok()?;
|
#[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() {
|
for line in text.lines() {
|
||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
if line.starts_with('#') {
|
if line.starts_with('#') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some((key, value)) = line.split_once('=') else { 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;
|
continue;
|
||||||
}
|
}
|
||||||
let value = value.trim().trim_matches(['"', '\'']);
|
match key.trim() {
|
||||||
if !value.is_empty() {
|
"jurisdiction" => out.jurisdiction = Some(value.to_string()),
|
||||||
return 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();
|
let p = path();
|
||||||
if let Some(dir) = p.parent() {
|
if let Some(dir) = p.parent() {
|
||||||
std::fs::create_dir_all(dir)?;
|
std::fs::create_dir_all(dir)?;
|
||||||
}
|
}
|
||||||
std::fs::write(
|
let mut text = String::from(
|
||||||
&p,
|
"# What typikon assumes when the flags are not given.\n\
|
||||||
format!(
|
# Run `typikon --list` to see every accepted jurisdiction.\n",
|
||||||
"# Which reckoning typikon uses when no --jurisdiction is given.\n\
|
);
|
||||||
# Run `typikon --list` to see every accepted value.\n\
|
if let Some(j) = &saved.jurisdiction {
|
||||||
jurisdiction = \"{jurisdiction}\"\n"
|
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)
|
Ok(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saved() -> Saved {
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
|
||||||
/// The environment beats the config file; both beat the built-in default.
|
/// The environment beats the config file; both beat the built-in default.
|
||||||
pub fn default_jurisdiction() -> (Option<String>, Source) {
|
pub fn default_jurisdiction() -> (Option<String>, Source) {
|
||||||
if let Ok(v) = std::env::var(ENV_VAR) {
|
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);
|
return (Some(v), Source::Environment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match read() {
|
match read().jurisdiction {
|
||||||
Some(v) => (Some(v), Source::Config),
|
Some(v) => (Some(v), Source::Config),
|
||||||
None => (None, Source::Default),
|
None => (None, Source::Default),
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-4
@@ -291,8 +291,31 @@ fn run() -> Result<i32, String> {
|
|||||||
if let Some(name) = &args.set_default {
|
if let Some(name) = &args.set_default {
|
||||||
let jur = jurisdictions::resolve(Some(name))
|
let jur = jurisdictions::resolve(Some(name))
|
||||||
.map_err(|n| format!("unknown jurisdiction {n:?}; try --list"))?;
|
.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());
|
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);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,12 +330,13 @@ fn run() -> Result<i32, String> {
|
|||||||
// carries. Leaked as a 'static because a Jurisdiction is otherwise a
|
// carries. Leaked as a 'static because a Jurisdiction is otherwise a
|
||||||
// compile-time constant, and one override per run is not worth threading
|
// compile-time constant, and one override per run is not worth threading
|
||||||
// a lifetime through the whole library for.
|
// 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,
|
None => base.tradition,
|
||||||
Some(t) => jurisdictions::Tradition::parse(t)
|
Some(t) => jurisdictions::Tradition::parse(t)
|
||||||
.ok_or_else(|| format!("unknown tradition {t:?}; try slavic or greek"))?,
|
.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)
|
Some(jurisdictions::RuleKind::Orthodox)
|
||||||
} else {
|
} else {
|
||||||
base.discipline
|
base.discipline
|
||||||
@@ -399,7 +423,11 @@ fn run() -> Result<i32, String> {
|
|||||||
|
|
||||||
// The first run should say what it assumed, and then stop saying it.
|
// The first run should say what it assumed, and then stop saying it.
|
||||||
if source == Source::Default {
|
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!(" Reckoning by the {}, written to {}.", jur.name.to_lowercase(), p.display());
|
||||||
println!(" Change it with `typikon --set-default <jurisdiction>`, or see `typikon --list`.\n");
|
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);
|
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