git_same/cli.rs
1//! CLI argument parsing using clap.
2//!
3//! This module defines the command-line interface for git-same,
4//! including all subcommands and their options.
5
6use clap::{Args, Parser, Subcommand};
7use std::path::PathBuf;
8
9/// Git-Same - Mirror GitHub structure /orgs/repos/ to local file system
10///
11/// Available as: git-same (primary), gitsame, gitsa, gisa (symlink aliases)
12/// Alias list: see toolkit/packaging/binary-aliases.txt
13/// Also works as: git same (git subcommand)
14#[derive(Parser, Debug)]
15#[command(name = "git-same")]
16#[command(version, about, long_about = None)]
17#[command(propagate_version = true)]
18pub struct Cli {
19 /// Increase verbosity (-v, -vv, -vvv)
20 #[arg(short, long, action = clap::ArgAction::Count, global = true)]
21 pub verbose: u8,
22
23 /// Suppress all output except errors
24 #[arg(short, long, global = true)]
25 pub quiet: bool,
26
27 /// Output in JSON format
28 #[arg(long, global = true)]
29 pub json: bool,
30
31 /// Path to config file
32 #[arg(short = 'C', long, global = true)]
33 pub config: Option<PathBuf>,
34
35 #[command(subcommand)]
36 pub command: Option<Command>,
37}
38
39/// Git-Same subcommands
40#[derive(Subcommand, Debug)]
41pub enum Command {
42 /// Initialize git-same configuration
43 Init(InitArgs),
44
45 /// Configure a workspace (interactive wizard)
46 Setup(SetupArgs),
47
48 /// Sync repositories (discover, clone new, fetch/pull existing)
49 Sync(SyncCmdArgs),
50
51 /// Show status of local repositories
52 Status(StatusArgs),
53
54 /// Manage workspaces (list, set default)
55 Workspace(WorkspaceArgs),
56
57 /// Reset gisa — remove all config, workspaces, and cache
58 Reset(ResetArgs),
59
60 /// Scan a directory tree for unregistered workspaces (.git-same/ folders)
61 Scan(ScanArgs),
62}
63
64/// Arguments for the init command
65#[derive(Args, Debug)]
66pub struct InitArgs {
67 /// Force overwrite existing config
68 #[arg(short, long)]
69 pub force: bool,
70
71 /// Path for config file (default: ~/.config/git-same/config.toml)
72 #[arg(short, long)]
73 pub path: Option<PathBuf>,
74}
75
76/// Arguments for the setup command
77#[derive(Args, Debug)]
78pub struct SetupArgs {
79 /// Workspace name (auto-derived from base path if omitted)
80 #[arg(short, long)]
81 pub name: Option<String>,
82}
83
84/// Arguments for the sync command
85#[derive(Args, Debug)]
86pub struct SyncCmdArgs {
87 /// Workspace path or folder name to sync
88 #[arg(short, long)]
89 pub workspace: Option<String>,
90
91 /// Use pull instead of fetch for existing repos
92 #[arg(long)]
93 pub pull: bool,
94
95 /// Perform a dry run (show what would be done)
96 #[arg(short = 'n', long)]
97 pub dry_run: bool,
98
99 /// Maximum number of concurrent operations
100 #[arg(short, long)]
101 pub concurrency: Option<usize>,
102
103 /// Force re-discovery (ignore cache)
104 #[arg(long)]
105 pub refresh: bool,
106
107 /// Don't skip repositories with uncommitted changes
108 #[arg(long)]
109 pub no_skip_uncommitted: bool,
110}
111
112/// Arguments for the status command
113#[derive(Args, Debug)]
114pub struct StatusArgs {
115 /// Workspace path or folder name
116 #[arg(short, long)]
117 pub workspace: Option<String>,
118
119 /// Show only repositories with changes
120 #[arg(short = 'd', long)]
121 pub uncommitted: bool,
122
123 /// Show only repositories behind upstream
124 #[arg(short, long)]
125 pub behind: bool,
126
127 /// Show detailed status for each repository
128 #[arg(long)]
129 pub detailed: bool,
130
131 /// Filter to specific organizations (can be repeated)
132 #[arg(short, long)]
133 pub org: Vec<String>,
134}
135
136/// Arguments for the workspace command
137#[derive(Args, Debug)]
138pub struct WorkspaceArgs {
139 #[command(subcommand)]
140 pub command: WorkspaceCommand,
141}
142
143/// Workspace subcommands
144#[derive(Subcommand, Debug)]
145pub enum WorkspaceCommand {
146 /// List configured workspaces
147 List,
148 /// Set or show the default workspace
149 Default(WorkspaceDefaultArgs),
150}
151
152/// Arguments for the workspace default subcommand
153#[derive(Args, Debug)]
154pub struct WorkspaceDefaultArgs {
155 /// Workspace path or folder name to set as default (omit to show current)
156 #[arg(value_name = "WORKSPACE")]
157 pub name: Option<String>,
158
159 /// Clear the default workspace
160 #[arg(long)]
161 pub clear: bool,
162}
163
164/// Arguments for the reset command
165#[derive(Args, Debug)]
166pub struct ResetArgs {
167 /// Skip confirmation prompt
168 #[arg(short, long)]
169 pub force: bool,
170}
171
172/// Arguments for the scan command
173#[derive(Args, Debug)]
174pub struct ScanArgs {
175 /// Root directory to scan (default: current directory)
176 pub path: Option<PathBuf>,
177
178 /// Maximum directory depth to search (default: 5)
179 #[arg(short, long, default_value = "5")]
180 pub depth: usize,
181
182 /// Register discovered workspaces automatically
183 #[arg(long)]
184 pub register: bool,
185}
186
187impl Cli {
188 /// Parse command line arguments.
189 pub fn parse_args() -> Self {
190 Self::parse()
191 }
192
193 /// Get the effective verbosity level (0-3).
194 pub fn verbosity(&self) -> u8 {
195 if self.quiet {
196 0
197 } else {
198 self.verbose.min(3)
199 }
200 }
201
202 /// Check if output should be suppressed.
203 pub fn is_quiet(&self) -> bool {
204 self.quiet
205 }
206
207 /// Check if JSON output is requested.
208 pub fn is_json(&self) -> bool {
209 self.json
210 }
211}
212
213#[cfg(test)]
214#[path = "cli_tests.rs"]
215mod tests;