11use std:: process:: ExitCode ;
22
3+ use anyhow:: { bail, Result } ;
4+ use lexopt:: { Arg , ValueExt } ;
5+
36use crate :: { command_surface, dependency_contract} ;
47
8+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
9+ enum Command {
10+ Help ,
11+ Setup ,
12+ Mcp ,
13+ Hooks ,
14+ Sync ,
15+ }
16+
517pub fn run < I > ( args : I ) -> ExitCode
18+ where
19+ I : IntoIterator < Item = String > ,
20+ {
21+ match try_run ( args) {
22+ Ok ( ( ) ) => ExitCode :: SUCCESS ,
23+ Err ( error) => {
24+ eprintln ! ( "Error: {error}" ) ;
25+ ExitCode :: from ( 2 )
26+ }
27+ }
28+ }
29+
30+ fn try_run < I > ( args : I ) -> Result < ( ) >
631where
732 I : IntoIterator < Item = String > ,
833{
934 let _ = dependency_contract:: dependency_contract_snapshot ( ) ;
35+ let command = parse_command ( args) ?;
36+ dispatch ( command)
37+ }
1038
39+ fn parse_command < I > ( args : I ) -> Result < Command >
40+ where
41+ I : IntoIterator < Item = String > ,
42+ {
1143 let mut args = args. into_iter ( ) ;
1244 let _program = args. next ( ) ;
1345
14- match args. next ( ) . as_deref ( ) {
15- None | Some ( "--help" ) | Some ( "-h" ) | Some ( "help" ) => {
16- println ! ( "{}" , command_surface:: help_text( ) ) ;
17- ExitCode :: SUCCESS
46+ let mut parser = lexopt:: Parser :: from_args ( args) ;
47+ let mut command = None ;
48+
49+ while let Some ( arg) = parser. next ( ) ? {
50+ match arg {
51+ Arg :: Long ( "help" ) | Arg :: Short ( 'h' ) => {
52+ if command. is_some ( ) {
53+ bail ! ( "'--help' must be used by itself. Run 'sce --help'." ) ;
54+ }
55+ command = Some ( Command :: Help ) ;
56+ }
57+ Arg :: Value ( value) => {
58+ let value = value. string ( ) ?;
59+
60+ if command. is_some ( ) {
61+ bail ! (
62+ "Unexpected extra argument '{}'. Run 'sce --help' to see valid usage." ,
63+ value
64+ ) ;
65+ }
66+
67+ command = Some ( parse_subcommand ( & value) ?) ;
68+ }
69+ Arg :: Long ( option) => {
70+ bail ! (
71+ "Unknown option '--{}'. Run 'sce --help' to see valid usage." ,
72+ option
73+ ) ;
74+ }
75+ Arg :: Short ( option) => {
76+ bail ! (
77+ "Unknown option '-{}'. Run 'sce --help' to see valid usage." ,
78+ option
79+ ) ;
80+ }
1881 }
19- Some ( cmd) => {
20- if command_surface:: is_known_command ( cmd) {
21- println ! (
22- "'{}' is a planned placeholder command in this foundation slice." ,
23- cmd
82+ }
83+
84+ Ok ( command. unwrap_or ( Command :: Help ) )
85+ }
86+
87+ fn parse_subcommand ( value : & str ) -> Result < Command > {
88+ match value {
89+ "help" => Ok ( Command :: Help ) ,
90+ "setup" => Ok ( Command :: Setup ) ,
91+ "mcp" => Ok ( Command :: Mcp ) ,
92+ "hooks" => Ok ( Command :: Hooks ) ,
93+ "sync" => Ok ( Command :: Sync ) ,
94+ _ => {
95+ if command_surface:: is_known_command ( value) {
96+ bail ! (
97+ "Command '{}' is currently unavailable in this build." ,
98+ value
2499 ) ;
25- ExitCode :: SUCCESS
26- } else {
27- eprintln ! ( "Unknown command: {}" , cmd) ;
28- eprintln ! ( "Run 'sce --help' to see the current placeholder command surface." ) ;
29- ExitCode :: from ( 2 )
30100 }
101+
102+ bail ! (
103+ "Unknown command '{}'. Run 'sce --help' to see the current command surface." ,
104+ value
105+ ) ;
31106 }
32107 }
33108}
34109
110+ fn dispatch ( command : Command ) -> Result < ( ) > {
111+ match command {
112+ Command :: Help => println ! ( "{}" , command_surface:: help_text( ) ) ,
113+ Command :: Setup => println ! ( "TODO: 'setup' is planned and not implemented yet." ) ,
114+ Command :: Mcp => println ! ( "TODO: 'mcp' is planned and not implemented yet." ) ,
115+ Command :: Hooks => println ! ( "TODO: 'hooks' is planned and not implemented yet." ) ,
116+ Command :: Sync => println ! ( "TODO: 'sync' is planned and not implemented yet." ) ,
117+ }
118+
119+ Ok ( ( ) )
120+ }
121+
35122#[ cfg( test) ]
36123mod tests {
37124 use std:: process:: ExitCode ;
38125
39- use super :: run;
126+ use super :: { parse_command , run, Command } ;
40127
41128 #[ test]
42129 fn help_path_exits_success ( ) {
@@ -55,4 +142,51 @@ mod tests {
55142 let code = run ( vec ! [ "sce" . to_string( ) , "does-not-exist" . to_string( ) ] ) ;
56143 assert_eq ! ( code, ExitCode :: from( 2 ) ) ;
57144 }
145+
146+ #[ test]
147+ fn parser_defaults_to_help_without_command ( ) {
148+ let command = parse_command ( vec ! [ "sce" . to_string( ) ] ) . expect ( "command should parse" ) ;
149+ assert_eq ! ( command, Command :: Help ) ;
150+ }
151+
152+ #[ test]
153+ fn parser_routes_placeholder_command ( ) {
154+ let command = parse_command ( vec ! [ "sce" . to_string( ) , "hooks" . to_string( ) ] )
155+ . expect ( "command should parse" ) ;
156+ assert_eq ! ( command, Command :: Hooks ) ;
157+ }
158+
159+ #[ test]
160+ fn parser_rejects_unknown_command ( ) {
161+ let error = parse_command ( vec ! [ "sce" . to_string( ) , "nope" . to_string( ) ] )
162+ . expect_err ( "unknown command should fail" ) ;
163+ assert_eq ! (
164+ error. to_string( ) ,
165+ "Unknown command 'nope'. Run 'sce --help' to see the current command surface."
166+ ) ;
167+ }
168+
169+ #[ test]
170+ fn parser_rejects_unknown_option ( ) {
171+ let error = parse_command ( vec ! [ "sce" . to_string( ) , "--verbose" . to_string( ) ] )
172+ . expect_err ( "unknown option should fail" ) ;
173+ assert_eq ! (
174+ error. to_string( ) ,
175+ "Unknown option '--verbose'. Run 'sce --help' to see valid usage."
176+ ) ;
177+ }
178+
179+ #[ test]
180+ fn parser_rejects_extra_arguments ( ) {
181+ let error = parse_command ( vec ! [
182+ "sce" . to_string( ) ,
183+ "setup" . to_string( ) ,
184+ "extra" . to_string( ) ,
185+ ] )
186+ . expect_err ( "extra argument should fail" ) ;
187+ assert_eq ! (
188+ error. to_string( ) ,
189+ "Unexpected extra argument 'extra'. Run 'sce --help' to see valid usage."
190+ ) ;
191+ }
58192}
0 commit comments