pub struct GlobalVars {
pub config: OnceCell<Ini>,
pub config_path: OnceCell<String>,
obsidian_root_path_dir: OnceCell<PathBuf>,
obsidian_commit_path: OnceCell<PathBuf>,
template_commit_date_path: OnceCell<String>,
template_commit_datetime: OnceCell<String>,
excluded_repos: OnceCell<Vec<String>>,
}Expand description
Thread-safe global configuration container for Rusty Commit Saver.
This struct holds all runtime configuration loaded from the INI file,
using OnceCell for lazy initialization and thread safety. Configuration
values are set once during initialization and remain immutable thereafter.
§Usage Pattern
use rusty_commit_saver::config::GlobalVars;
// 1. Create instance
let global_vars = GlobalVars::new();
// 2. Load configuration from INI file
global_vars.set_all();
// 3. Access configuration values
let obsidian_root = global_vars.get_obsidian_root_path_dir();
let commit_path = global_vars.get_obsidian_commit_path();§See Also
GlobalVars::new()- Create new instanceGlobalVars::set_all()- Initialize from INI fileparse_ini_content()- Parse INI content
Fields§
§config: OnceCell<Ini>The parsed INI configuration file.
Stores the complete parsed configuration from the INI file.
Initialized once by set_all().
§Thread Safety
OnceCell ensures this is set exactly once and can be safely
accessed from multiple threads.
config_path: OnceCell<String>Path of the INI the configuration was read from.
Retained so a configuration error can name the file to edit. Set by
set_all(); a GlobalVars handed a config directly
(as the tests do) leaves it unset.
obsidian_root_path_dir: OnceCell<PathBuf>Root directory of the Obsidian vault.
The base directory where all Obsidian files are stored. All diary entries are created under this directory.
§Examples
/home/user/Documents/ObsidianC:\Users\username\Documents\Obsidian(Windows)
§Configuration
Loaded from INI file:
[obsidian]
root_path_dir = ~/Documents/Obsidianobsidian_commit_path: OnceCell<PathBuf>Subdirectory path for commit diary entries.
Relative path under obsidian_root_path_dir
where commit entries are organized.
§Examples
Diaries/CommitsJournal/Git
§Full Path Construction
Combined with root and date template:
{root_path_dir}/{commit_path}/{date_template}
/home/user/Obsidian/Diaries/Commits/2025/01-January/2025-01-14.md§Configuration
Loaded from INI file:
[obsidian]
commit_path = Diaries/Commitstemplate_commit_date_path: OnceCell<String>Chrono format string for date-based file paths.
Controls the directory structure and filename for diary entries. Uses Chrono format specifiers to create date-organized paths.
§Format Specifiers
%Y- Year (e.g.,2025)%m- Month number (e.g.,01)%B- Full month name (e.g.,January)%F- ISO 8601 date (e.g.,2025-01-14)%d- Day of month (e.g.,14)
§Examples
Format: %Y/%m-%B/%F.md
Result: 2025/01-January/2025-01-14.md
Format: %Y/week-%W/%F.md
Result: 2025/week-02/2025-01-14.md§Configuration
Loaded from INI file:
[templates]
commit_date_path = %Y/%m-%B/%F.mdtemplate_commit_datetime: OnceCell<String>Chrono format string for datetime display in diary entries.
Controls how commit timestamps appear in the diary table’s TIME column.
§Format Specifiers
%Y- Year (e.g.,2025)%m- Month (e.g.,01)%d- Day (e.g.,14)%H- Hour, 24-hour (e.g.,14)%M- Minute (e.g.,30)%S- Second (e.g.,45)%T- Time in HH:MM:SS format
§Examples
Format: %Y-%m-%d %H:%M:%S
Result: 2025-01-14 14:30:45
Format: %H:%M:%S
Result: 14:30:45§Configuration
Loaded from INI file:
[templates]
commit_datetime = %Y-%m-%d %H:%M:%Sexcluded_repos: OnceCell<Vec<String>>Repository names to exclude from commit capture.
When the current repository’s working-directory name matches an entry in
this list, the post-commit run skips cleanly and writes nothing to the
diary. Populated from the optional [exclude] section; empty when that
section is absent.
§Configuration
Loaded from the INI file (comma-separated, optional):
[exclude]
repos = claude-src, some-other-repoImplementations§
Source§impl GlobalVars
impl GlobalVars
Sourceconst KNOWN_SECTIONS: [&'static str; 3]
const KNOWN_SECTIONS: [&'static str; 3]
The sections this binary understands. Adding a section to
set_obsidian_vars’ dispatch means adding it here too, or the section
gets applied and reported as unrecognised.
Sourceconst KNOWN_KEYS: [(&'static str, &'static [&'static str]); 3]
const KNOWN_KEYS: [(&'static str, &'static [&'static str]); 3]
The keys each known section understands. Adding a key to a setter means adding it here too, or the key gets applied and reported as unrecognised.
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new uninitialized GlobalVars instance.
This constructor initializes all fields as empty OnceCell values.
Use set_all() to load configuration from the INI file.
§Thread Safety
GlobalVars uses OnceCell for thread-safe, lazy initialization.
Configuration values are set once and cannot be changed afterward.
§Returns
A new GlobalVars instance with all fields uninitialized
§Fields
config- The parsed INI configuration fileobsidian_root_path_dir- Root directory of Obsidian vaultobsidian_commit_path- Subdirectory path for commit entriestemplate_commit_date_path- Chrono format for date-based directory structuretemplate_commit_datetime- Chrono format for datetime strings
§Examples
use rusty_commit_saver::config::GlobalVars;
// Create new instance
let global_vars = GlobalVars::new();
// Now call set_all() to initialize from config file
// global_vars.set_all();Sourcepub fn set_all(&self) -> &Self
pub fn set_all(&self) -> &Self
Loads and initializes all configuration from the INI file.
This is the main entry point for configuration setup. It:
- Reads the INI configuration file from disk (or CLI argument)
- Parses it into the
configfield - Extracts and initializes all Obsidian and template variables
Configuration is loaded from (in order of preference):
--config-ini <PATH>CLI argument- Default:
~/.config/rusty-commit-saver/rusty-commit-saver.ini
§Panics
Panics if:
- Configuration file doesn’t exist
- Configuration file cannot be read
- Configuration file has invalid INI format
- Required sections or keys are missing
- Section count is not exactly 2 (obsidian + templates)
§Returns
Returns self for method chaining
§Required INI Structure
[obsidian]
root_path_dir = ~/Documents/Obsidian
commit_path = Diaries/Commits
[templates]
commit_date_path = %Y/%m-%B/%F.md
commit_datetime = %Y-%m-%d %H:%M:%S§Examples
use rusty_commit_saver::config::GlobalVars;
let global_vars = GlobalVars::new();
global_vars.set_all(); // Reads from default or CLI config
// Now all getters will return values
let root_path = global_vars.get_obsidian_root_path_dir();
let commit_path = global_vars.get_obsidian_commit_path();Sourcepub fn get_obsidian_root_path_dir(&self) -> PathBuf
pub fn get_obsidian_root_path_dir(&self) -> PathBuf
Returns the root directory of the Obsidian vault.
This is the base directory where all Obsidian vault files are stored. All diary entries are created under this directory according to the configured subdirectory structure.
§Panics
Panics if called before set_all() has been invoked
§Returns
A PathBuf representing the Obsidian vault root directory
§Examples
use rusty_commit_saver::config::GlobalVars;
let global_vars = GlobalVars::new();
global_vars.set_all();
let root = global_vars.get_obsidian_root_path_dir();
println!("Obsidian vault root: {}", root.display());
// Output: Obsidian vault root: /home/user/Documents/Obsidian§Configuration Source
Read from INI file:
[obsidian]
root_path_dir = ~/Documents/ObsidianSourcepub fn get_obsidian_commit_path(&self) -> PathBuf
pub fn get_obsidian_commit_path(&self) -> PathBuf
Returns the subdirectory path where commits are stored.
This is a relative path under get_obsidian_root_path_dir()
where commit diary entries will be organized. The full path is constructed by
combining this with the Obsidian root and the date-based directory structure.
§Panics
Panics if called before set_all() has been invoked
§Returns
A PathBuf representing the commits subdirectory (relative path)
§Examples
use rusty_commit_saver::config::GlobalVars;
let global_vars = GlobalVars::new();
global_vars.set_all();
let commit_path = global_vars.get_obsidian_commit_path();
println!("Commit subdirectory: {}", commit_path.display());
// Output: Commit subdirectory: Diaries/Commits
// Full path would be constructed as:
// /home/user/Documents/Obsidian/Diaries/Commits/2025/01-January/2025-01-14.md§Configuration Source
Read from INI file:
[obsidian]
commit_path = Diaries/CommitsSourcepub fn get_template_commit_date_path(&self) -> String
pub fn get_template_commit_date_path(&self) -> String
Returns the Chrono format string for diary file date hierarchies.
This format string is used to create the directory structure and filename for diary entries based on the commit timestamp. It controls how commits are organized by date.
§Chrono Format Specifiers
%Y- Full year (e.g.,2025)%m- Month as zero-padded number (e.g.,01)%B- Full month name (e.g.,January)%b- Abbreviated month (e.g.,Jan)%d- Day of month, zero-padded (e.g.,14)%F- ISO 8601 date (equivalent to%Y-%m-%d, e.g.,2025-01-14)%H- Hour in 24-hour format (e.g.,14)%M- Minute (e.g.,30)%S- Second (e.g.,45)
§Panics
Panics if called before set_all() has been invoked
§Returns
A String containing the Chrono format specifiers
§Examples
use rusty_commit_saver::config::GlobalVars;
let global_vars = GlobalVars::new();
global_vars.set_all();
let date_template = global_vars.get_template_commit_date_path();
println!("Date format: {}", date_template);
// Output: Date format: %Y/%m-%B/%F.md
// This creates paths like:
// /home/user/Obsidian/Diaries/Commits/2025/01-January/2025-01-14.md§Configuration Source
Read from INI file:
[templates]
commit_date_path = %Y/%m-%B/%F.mdSourcepub fn get_template_commit_datetime(&self) -> String
pub fn get_template_commit_datetime(&self) -> String
Returns the Chrono format string for commit timestamps in diary entries.
This format string is used to display the commit time in the diary table. It controls how timestamps appear in the commit entry rows.
§Chrono Format Specifiers
%Y- Full year (e.g.,2025)%m- Month as zero-padded number (e.g.,01)%B- Full month name (e.g.,January)%d- Day of month, zero-padded (e.g.,14)%H- Hour in 24-hour format (e.g.,14)%M- Minute, zero-padded (e.g.,30)%S- Second, zero-padded (e.g.,45)%T- Time in HH:MM:SS format (equivalent to%H:%M:%S)
§Panics
Panics if called before set_all() has been invoked
§Returns
A String containing the Chrono format specifiers for datetime
§Examples
use rusty_commit_saver::config::GlobalVars;
let global_vars = GlobalVars::new();
global_vars.set_all();
let datetime_template = global_vars.get_template_commit_datetime();
println!("Datetime format: {}", datetime_template);
// Output: Datetime format: %Y-%m-%d %H:%M:%S
// This renders timestamps like:
// 2025-01-14 14:30:45§Diary Table Usage
In the diary table, this format appears in the TIME column:
| FOLDER | TIME | COMMIT MESSAGE | REPOSITORY URL | BRANCH | COMMIT HASH |
|--------|------|----------------|----------------|--------|-------------|
| /work/project | 14:30:45 | feat: add feature | https://github.com/... | main | abc123... |§Configuration Source
Read from INI file:
[templates]
commit_datetime = %Y-%m-%d %H:%M:%SSourcefn get_config(&self) -> Ini
fn get_config(&self) -> Ini
Retrieves a clone of the parsed INI configuration.
This is a private helper method that returns a copy of the configuration object. Used internally by other helper methods to access sections and keys.
§Panics
Panics if called before set_all() has initialized the config.
§Returns
A cloned Ini configuration object
fn get_key_from_section_from_ini( &self, section: &str, key: &str, ) -> Option<String>
Sourcefn unrecognised_keys(&self) -> Vec<String>
fn unrecognised_keys(&self) -> Vec<String>
Lists the keys this binary does not understand, as sorted
[section] key labels.
Only keys in a known section are listed: an unrecognised section is
already reported whole by get_sections_from_config(),
and listing its keys as well would charge one mistake twice.
Sorted because the parser holds keys in a hash map, whose iteration order would otherwise vary from run to run.
Sourcefn unrecognised_keys_in(&self, section: &str) -> Vec<String>
fn unrecognised_keys_in(&self, section: &str) -> Vec<String>
The unrecognised keys of one known section, sorted, without the
[section] prefix. An unknown section has none by definition: the
binary has no idea what it should contain.
Sourcefn config_file_label(&self) -> String
fn config_file_label(&self) -> String
Names the configuration file for an error message.
Falls back to a plain description rather than a guessed path when the config was handed in directly instead of read from disk.
Sourcefn require_key(&self, section: &str, key: &str) -> String
fn require_key(&self, section: &str, key: &str) -> String
Reads a key the binary cannot work without.
Fatal by design. Without it there is no destination to write to, and a
hook that quietly journals nothing is indistinguishable from a quiet
day - the diary would stop for weeks before anyone noticed. What the
fatal path owes the user is a message they can act on: it names the
resolved config file, the [section] key, and any unrecognised key in
that same section, because a misspelt commit_paths is the usual
reason commit_path is missing and naming both at once saves reading
the source.
A present-but-blank value counts as missing: commit_path = used to
satisfy the old presence check and silently journal into the vault
root instead of the configured folder.
§Panics
Panics if the key is absent, or its value is empty or whitespace.
Sourcefn require_time_format(&self, section: &str, key: &str) -> String
fn require_time_format(&self, section: &str, key: &str) -> String
Reads a required key whose value must be a chrono format string.
A format chrono cannot render is config skew like any other, but it
used to surface from deep inside the writer as a formatting trait implementation returned an error when the underlying stream did not,
naming neither the file nor the key - and only after an empty diary
file had already been created. Checking it where the rest of the config
is checked keeps the message the same shape as every other config
fault, and stops the run before it writes anything.
§Panics
Panics if the key is missing (see require_key()),
or if chrono cannot render its value.
Sourcefn report_unrecognised_keys(&self)
fn report_unrecognised_keys(&self)
Reports every unrecognised key, then carries on.
An unknown key is never fatal, for the reason an unknown section is not:
one INI file is shared by every checkout on the machine, so a key
written for a newer release must not brick a binary that predates it.
Reporting it is what a silent skip failed to do - a misspelt
commit_datetimes used to apply nothing and say nothing.
fn get_sections_from_config(&self) -> Vec<String>
Sourcepub fn set_obsidian_vars(&self)
pub fn set_obsidian_vars(&self)
Loads all configuration variables from the “obsidian” and “templates” sections.
This method iterates through all sections returned by get_sections_from_config.
For each recognized section, it initializes the corresponding runtime variables
by calling their dedicated setters:
- For the “obsidian” section: calls
set_obsidian_root_path_dirandset_obsidian_commit_path. - For the “templates” section: calls
set_templates_commit_date_pathandset_templates_datetime. - For the “exclude” section: calls
set_excluded_repos.
Any other section is skipped. Keys the binary does not understand are reported on stderr and skipped too.
§Panics
Panics if the INI file is missing [obsidian] or [templates]; both are
required. An unrecognised section or key is not fatal.
§Logging
- Logs an info message when applying each section.
- Logs an error right before panicking on a missing required section.
§Examples
use rusty_commit_saver::config::GlobalVars;
let mut config = configparser::ini::Ini::new();
config.set("obsidian", "root_path_dir", Some("~/Obsidian".to_string()));
config.set("obsidian", "commit_path", Some("Diary/Commits".to_string()));
config.set("templates", "commit_date_path", Some("%Y-%m-%d.md".to_string()));
config.set("templates", "commit_datetime", Some("%Y-%m-%d %H:%M:%S".to_string()));
let global_vars = GlobalVars::new();
global_vars.config.set(config).unwrap();
global_vars.set_obsidian_vars();Sourcefn set_templates_datetime(&self, section: &str)
fn set_templates_datetime(&self, section: &str)
Sets the template_commit_datetime field from the [templates] section.
Reads the commit_datetime key from the INI file and stores it in the
template_commit_datetime OnceCell.
§Arguments
section- Should be"templates"(validated by caller)
§Panics
Panics if:
- The
commit_datetimekey is missing, or its value is blank, or it is not a formatchronocan render - The
OnceCellhas already been set (called multiple times)
§Expected INI Key
[templates]
commit_datetime = %Y-%m-%d %H:%M:%SSourcefn set_excluded_repos(&self, section: &str)
fn set_excluded_repos(&self, section: &str)
Sets the excluded_repos field from the optional [exclude] section.
Reads the repos key, parses it as a comma-separated list of repository
names, and stores the result. A missing repos key yields an empty list.
§Arguments
section- Should be"exclude"(validated by caller)
§Expected INI Key
[exclude]
repos = claude-src, some-other-repoSourcepub fn get_excluded_repos(&self) -> Vec<String>
pub fn get_excluded_repos(&self) -> Vec<String>
Returns the list of repository names excluded from commit capture.
Reads the value populated from the optional [exclude] section. When that
section (or its repos key) is absent, returns an empty list — meaning no
repository is excluded.
§Returns
A Vec<String> of excluded repository names (working-directory names).
§Configuration Source
Read from the INI file:
[exclude]
repos = claude-srcSourcefn set_templates_commit_date_path(&self, section: &str)
fn set_templates_commit_date_path(&self, section: &str)
Sets the template_commit_date_path field from the [templates] section.
Reads the commit_date_path key from the INI file and stores it in the
template_commit_date_path OnceCell.
§Arguments
section- Should be"templates"(validated by caller)
§Panics
Panics if:
- The
commit_date_pathkey is missing, or its value is blank, or it is not a formatchronocan render - The
OnceCellhas already been set (called multiple times)
§Expected INI Key
[templates]
commit_date_path = %Y/%m-%B/%F.mdSourcefn set_obsidian_commit_path(&self, section: &str)
fn set_obsidian_commit_path(&self, section: &str)
Sets the obsidian_commit_path field from the [obsidian] section.
Reads the commit_path key, expands tilde (~) to the home directory
if present, splits the path by /, and constructs a PathBuf.
§Arguments
section- Should be"obsidian"(validated by caller)
§Tilde Expansion
~/Diaries/Commits→/home/user/Diaries/Commits/absolute/path→/absolute/path(unchanged)
§Panics
Panics if:
- The
commit_pathkey is missing, or its value is blank - Home directory cannot be determined (when
~is used) - The
OnceCellhas already been set
§Expected INI Key
[obsidian]
commit_path = ~/Documents/Obsidian/Diaries/CommitsSourcefn set_obsidian_root_path_dir(&self, section: &str)
fn set_obsidian_root_path_dir(&self, section: &str)
Sets the obsidian_root_path_dir field from the [obsidian] section.
Reads the root_path_dir key, expands tilde (~) to the home directory
if present, prepends / for absolute paths, and constructs a PathBuf.
§Arguments
section- Should be"obsidian"(validated by caller)
§Path Construction
- Starts with
/to ensure absolute path - Expands
~to home directory - Splits by
/and constructsPathBuf
§Tilde Expansion Examples
~/Documents/Obsidian→/home/user/Documents/Obsidian/absolute/path→/absolute/path
§Panics
Panics if:
- The
root_path_dirkey is missing, or its value is blank - Home directory cannot be determined (when
~is used) - The
OnceCellhas already been set
§Expected INI Key
[obsidian]
root_path_dir = ~/Documents/Obsidian