@@ -17,6 +17,12 @@ import (
1717
1818const version = "1.0.1"
1919
20+ type options struct {
21+ ConfigPath string
22+ ShowVersion bool
23+ ShowHelp bool
24+ }
25+
2026func usage () {
2127 fmt .Fprintf (os .Stderr , `git-scope v%s — A fast TUI to see the status of all git repositories
2228
@@ -44,111 +50,125 @@ Flags:
4450 flag .PrintDefaults ()
4551}
4652
53+ func printVersion () {
54+ fmt .Printf ("git-scope v%s\n " , version )
55+ }
56+
4757func main () {
4858 flag .Usage = usage
49- configPath := flag .String ("config" , config .DefaultConfigPath (), "Path to config file" )
50- showVersion := flag .Bool ("v" , false , "Show version" )
51- flag .Bool ("version" , false , "Show version" )
52- flag .Parse ()
5359
54- // Handle version flag
55- if * showVersion || isFlagPassed ( "version" ) {
56- fmt . Printf ( "git-scope v%s \n " , version )
60+ opts := parseFlags ()
61+ if opts . ShowVersion {
62+ printVersion ( )
5763 return
5864 }
59-
60- args := flag .Args ()
61- cmd := ""
62- dirs := []string {}
63-
64- // Parse command and directories
65- if len (args ) >= 1 {
66- switch args [0 ] {
67- case "scan" , "tui" , "help" , "init" , "scan-all" , "issue" , "-h" , "--help" , "-v" , "--version" :
68- cmd = args [0 ]
69- dirs = args [1 :]
70- default :
71- // Assume it's a directory
72- cmd = "tui"
73- dirs = args
74- }
65+ if opts .ShowHelp {
66+ usage ()
67+ return
7568 }
7669
77- // Handle help early
78- if cmd == "help" || cmd == "-h" || cmd == "--help" {
70+ cmd , dirs := parseCommand (flag .Args ())
71+ // Handle help subcommand (e.g. `git-scope help`)
72+ if cmd == "help" {
7973 usage ()
8074 return
8175 }
8276
83- // Handle version
84- if cmd == "-v" || cmd == "--version" {
85- fmt .Printf ("git-scope v%s\n " , version )
86- return
77+ if err := run (cmd , dirs , opts .ConfigPath ); err != nil {
78+ log .Fatal (err )
8779 }
80+ }
8881
89- // Handle init command
90- if cmd == "init" {
91- runInit ()
92- return
82+ // parseFlags defines and parses all supported CLI flags and returns
83+ // the resolved options. It is responsible only for flag handling and
84+ // does not perform any command execution. So if a user runs
85+ // `git-scope -foo bar`, parseFlags only parses the `-foo` flag, if it
86+ // is supported by git-scope.
87+ func parseFlags () options {
88+ configPath := flag .String ("config" , config .DefaultConfigPath (), "Path to config file" )
89+
90+ var showVersion bool
91+ flag .BoolVar (& showVersion , "v" , false , "Show version" )
92+ flag .BoolVar (& showVersion , "version" , false , "Show version" )
93+
94+ var showHelp bool
95+ flag .BoolVar (& showHelp , "h" , false , "Help" )
96+ flag .BoolVar (& showHelp , "help" , false , "Help" )
97+
98+ flag .Parse ()
99+
100+ return options {
101+ ConfigPath : * configPath ,
102+ ShowVersion : showVersion ,
103+ ShowHelp : showHelp ,
93104 }
105+ }
94106
95- // Handle issue command
96- if cmd == "issue" {
107+ // parseCommand determines the command and directories from positional
108+ // arguments.
109+ func parseCommand (args []string ) (cmd string , dirs []string ) {
110+ if len (args ) == 0 {
111+ return "" , nil
112+ }
113+
114+ switch args [0 ] {
115+ case "scan" , "tui" , "help" , "init" , "scan-all" , "issue" :
116+ return args [0 ], args [1 :]
117+ default :
118+ return "tui" , args // assume it's a directory
119+ }
120+ }
121+
122+ // run executes the requested command using the provided configuration path
123+ // and directories.
124+ func run (cmd string , dirs []string , configPath string ) error {
125+ switch cmd {
126+ case "init" :
127+ runInit ()
128+ return nil
129+ case "issue" :
97130 runIssue ()
98- return
131+ return nil
132+ case "scan-all" :
133+ runScanAll ()
134+ return nil
99135 }
100136
101- // Load configuration
102- cfg , err := config .Load (* configPath )
137+ // Only commands below need config
138+ cfg , err := config .Load (configPath )
103139 if err != nil {
104- log . Fatalf ("failed to load config: %v " , err )
140+ return fmt . Errorf ("failed to load config: %w " , err )
105141 }
106142
107- // Override roots if directories provided via CLI
108143 if len (dirs ) > 0 {
109144 cfg .Roots = expandDirs (dirs )
110- } else if ! config .ConfigExists (* configPath ) {
111- // No config file and no CLI dirs - use smart defaults
145+ } else if ! config .ConfigExists (configPath ) {
112146 cfg .Roots = getSmartDefaults ()
113147 }
114148
115149 switch cmd {
116150 case "scan" :
117151 repos , err := scan .ScanRoots (cfg .Roots , cfg .Ignore )
118152 if err != nil {
119- log . Fatalf ("scan error: %v " , err )
153+ return fmt . Errorf ("scan error: %w " , err )
120154 }
121155 if err := scan .PrintJSON (os .Stdout , repos ); err != nil {
122- log . Fatalf ("print error: %v " , err )
156+ return fmt . Errorf ("print error: %w " , err )
123157 }
124-
125- case "scan-all" :
126- runScanAll ()
127- return
158+ return nil
128159
129160 case "tui" , "" :
130161 if err := tui .Run (cfg ); err != nil {
131- log . Fatalf ("tui error: %v " , err )
162+ return fmt . Errorf ("tui error: %w " , err )
132163 }
164+ return nil
133165
134166 default :
135- fmt .Fprintf (os .Stderr , "unknown command: %s\n \n " , cmd )
136167 usage ()
137- os . Exit ( 1 )
168+ return fmt . Errorf ( "unknown command: %s" , cmd )
138169 }
139170}
140171
141- // isFlagPassed checks if a flag was explicitly passed on the command line
142- func isFlagPassed (name string ) bool {
143- found := false
144- flag .Visit (func (f * flag.Flag ) {
145- if f .Name == name {
146- found = true
147- }
148- })
149- return found
150- }
151-
152172// expandDirs converts relative paths and ~ to absolute paths
153173func expandDirs (dirs []string ) []string {
154174 result := make ([]string , 0 , len (dirs ))
@@ -217,10 +237,10 @@ func getSmartDefaults() []string {
217237// runInit creates a config file interactively
218238func runInit () {
219239 configPath := config .DefaultConfigPath ()
220-
240+
221241 fmt .Println ("git-scope init — Setup your configuration" )
222242 fmt .Println ()
223-
243+
224244 // Check if config already exists
225245 if config .ConfigExists (configPath ) {
226246 fmt .Printf ("Config file already exists at: %s\n " , configPath )
@@ -235,7 +255,7 @@ func runInit() {
235255 }
236256
237257 reader := bufio .NewReader (os .Stdin )
238-
258+
239259 // Get directories
240260 fmt .Println ("Enter directories to scan for git repos (one per line, empty line to finish):" )
241261 fmt .Println ()
@@ -246,7 +266,7 @@ func runInit() {
246266 fmt .Println ()
247267 fmt .Println ("Examples: ~/code, ~/projects, ~/work" )
248268 fmt .Println ()
249-
269+
250270 dirs := []string {}
251271 for {
252272 fmt .Print ("> " )
0 commit comments