Struct GlobalVars

Source
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

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/Obsidian
  • C:\Users\username\Documents\Obsidian (Windows)

§Configuration

Loaded from INI file:

[obsidian]
root_path_dir = ~/Documents/Obsidian
§obsidian_commit_path: OnceCell<PathBuf>

Subdirectory path for commit diary entries.

Relative path under obsidian_root_path_dir where commit entries are organized.

§Examples

  • Diaries/Commits
  • Journal/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/Commits
§template_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.md
§template_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:%S
§excluded_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-repo

Implementations§

Source§

impl GlobalVars

Source

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.

Source

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.

Source

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 file
  • obsidian_root_path_dir - Root directory of Obsidian vault
  • obsidian_commit_path - Subdirectory path for commit entries
  • template_commit_date_path - Chrono format for date-based directory structure
  • template_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();
Source

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:

  1. Reads the INI configuration file from disk (or CLI argument)
  2. Parses it into the config field
  3. 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();
Source

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/Obsidian
Source

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/Commits
Source

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.md
Source

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:%S
Source

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

Source

fn get_key_from_section_from_ini( &self, section: &str, key: &str, ) -> Option<String>

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

fn get_sections_from_config(&self) -> Vec<String>

Source

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_dir and set_obsidian_commit_path.
  • For the “templates” section: calls set_templates_commit_date_path and set_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();
Source

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_datetime key is missing, or its value is blank, or it is not a format chrono can render
  • The OnceCell has already been set (called multiple times)
§Expected INI Key
[templates]
commit_datetime = %Y-%m-%d %H:%M:%S
Source

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-repo
Source

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-src
Source

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_path key is missing, or its value is blank, or it is not a format chrono can render
  • The OnceCell has already been set (called multiple times)
§Expected INI Key
[templates]
commit_date_path = %Y/%m-%B/%F.md
Source

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_path key is missing, or its value is blank
  • Home directory cannot be determined (when ~ is used)
  • The OnceCell has already been set
§Expected INI Key
[obsidian]
commit_path = ~/Documents/Obsidian/Diaries/Commits
Source

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 constructs PathBuf
§Tilde Expansion Examples
  • ~/Documents/Obsidian/home/user/Documents/Obsidian
  • /absolute/path/absolute/path
§Panics

Panics if:

  • The root_path_dir key is missing, or its value is blank
  • Home directory cannot be determined (when ~ is used)
  • The OnceCell has already been set
§Expected INI Key
[obsidian]
root_path_dir = ~/Documents/Obsidian

Trait Implementations§

Source§

impl Debug for GlobalVars

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for GlobalVars

Source§

fn default() -> GlobalVars

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.