gitwatch_rs/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Parser, ValueEnum};
4use clap_complete::Shell;
5use log::LevelFilter;
6use regex::Regex;
7use serde::Deserialize;
8
9#[derive(Parser)]
10#[command(about = "CLI to watch a git repo and automatically commit changes")]
11pub struct Cli {
12    #[command(subcommand)]
13    pub command: Commands,
14}
15
16#[derive(Parser)]
17pub enum Commands {
18    /// Watch a repository and commit changes
19    Watch(CliOptions),
20
21    /// Generate shell completion scripts
22    Completion {
23        /// The shell to generate completions for
24        #[arg(value_enum)]
25        shell: Shell,
26    },
27}
28
29#[derive(Parser)]
30pub struct CliOptions {
31    /// Path to the Git repository to monitor for changes
32    #[clap(default_value = ".")]
33    pub repository: PathBuf,
34
35    #[clap(flatten)]
36    pub commit_message: CommitMessageOptions,
37
38    /// Automatically commit any existing changes on start
39    #[clap(long = "commit-on-start", default_value = "true")]
40    pub commit_on_start: std::primitive::bool,
41
42    /// Number of seconds to wait before processing multiple changes to the same file.
43    /// Higher values reduce commit frequency but group more changes together.
44    #[clap(long = "debounce-seconds", default_value = "1", verbatim_doc_comment)]
45    pub debounce_seconds: u64,
46
47    /// Run without performing actual Git operations (staging, committing, etc.)
48    #[clap(long = "dry-run", default_value = "false")]
49    pub dry_run: bool,
50
51    /// Regular expression pattern for files to exclude from watching.
52    /// Matches are performed against repository-relative file paths.
53    /// Note: the .git folder & gitignored files are ignored by default.
54    /// Example: "\.tmp$" to ignore temporary files.
55    #[clap(short = 'i', long = "ignore-regex", verbatim_doc_comment)]
56    pub ignore_regex: Option<Regex>,
57
58    /// Set the log level
59    #[arg(long, value_enum, default_value_t = LogLevel::Info)]
60    pub log_level: LogLevel,
61
62    /// Name of the remote to push to (if specified).
63    /// Example: "origin".
64    #[clap(short = 'r', long = "remote", verbatim_doc_comment)]
65    pub remote: Option<String>,
66
67    /// Number of retry attempts when errors occur.
68    /// Use -1 for infinite retries.
69    #[clap(long = "retries", default_value = "3", verbatim_doc_comment)]
70    pub retries: i32,
71
72    /// Enable continuous monitoring of filesystem changes.
73    /// Set to false for one-time commit of current changes.
74    #[clap(
75        short = 'w',
76        long = "watch",
77        default_value = "true",
78        verbatim_doc_comment
79    )]
80    pub watch: std::primitive::bool,
81}
82
83#[derive(Clone, Debug, clap::Args)]
84#[group(multiple = false)]
85pub struct CommitMessageOptions {
86    #[clap(short = 'm', long = "commit-message")]
87    /// Static commit message to use for all commits
88    pub message: Option<String>,
89
90    /// Path to executable script that generates commit messages.
91    /// The path can be absolute or relative to the repository.
92    /// The script is executed with the repository as working directory
93    /// and must output the message to stdout.
94    #[clap(long = "commit-message-script", verbatim_doc_comment)]
95    pub script: Option<PathBuf>,
96}
97
98#[derive(Copy, Clone, Debug, Default, Deserialize, ValueEnum)]
99pub enum LogLevel {
100    Trace,
101    Debug,
102    #[default]
103    Info,
104    Warn,
105    Error,
106}
107
108impl From<LogLevel> for LevelFilter {
109    fn from(level: LogLevel) -> Self {
110        match level {
111            LogLevel::Trace => LevelFilter::Trace,
112            LogLevel::Debug => LevelFilter::Debug,
113            LogLevel::Info => LevelFilter::Info,
114            LogLevel::Warn => LevelFilter::Warn,
115            LogLevel::Error => LevelFilter::Error,
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_loglevel_conversion() {
126        let conversions = [
127            (LogLevel::Trace, LevelFilter::Trace),
128            (LogLevel::Debug, LevelFilter::Debug),
129            (LogLevel::Info, LevelFilter::Info),
130            (LogLevel::Warn, LevelFilter::Warn),
131            (LogLevel::Error, LevelFilter::Error),
132        ];
133
134        for (input, expected) in conversions {
135            assert_eq!(LevelFilter::from(input), expected);
136        }
137    }
138}