11use std:: path:: { Path , PathBuf } ;
22
33use anyhow:: { anyhow, bail, Context , Result } ;
4- use lexopt:: { Arg , ValueExt } ;
54use serde_json:: { json, Value } ;
65
76use crate :: services:: output_format:: OutputFormat ;
@@ -73,7 +72,6 @@ impl ValueSource {
7372
7473#[ derive( Clone , Debug , Eq , PartialEq ) ]
7574pub enum ConfigSubcommand {
76- Help ,
7775 Show ( ConfigRequest ) ,
7876 Validate ( ConfigRequest ) ,
7977}
@@ -136,108 +134,8 @@ struct FileConfigValue<T> {
136134 source : ConfigPathSource ,
137135}
138136
139- pub fn parse_config_subcommand ( mut args : Vec < String > ) -> Result < ConfigSubcommand > {
140- if args. is_empty ( ) {
141- bail ! ( "Missing config subcommand. Run 'sce config --help' to see valid usage." ) ;
142- }
143-
144- if let [ only] = args. as_slice ( ) {
145- if only == "--help" || only == "-h" {
146- return Ok ( ConfigSubcommand :: Help ) ;
147- }
148- }
149-
150- let subcommand = args. remove ( 0 ) ;
151- let tail = args;
152- match subcommand. as_str ( ) {
153- "show" => Ok ( ConfigSubcommand :: Show ( parse_config_request ( tail) ?) ) ,
154- "validate" => Ok ( ConfigSubcommand :: Validate ( parse_config_request ( tail) ?) ) ,
155- _ => bail ! (
156- "Unknown config subcommand '{}'. Run 'sce config --help' to see valid usage." ,
157- subcommand
158- ) ,
159- }
160- }
161-
162- fn parse_config_request ( args : Vec < String > ) -> Result < ConfigRequest > {
163- let mut parser = lexopt:: Parser :: from_args ( args) ;
164- let mut request = ConfigRequest {
165- report_format : ReportFormat :: Text ,
166- config_path : None ,
167- log_level : None ,
168- timeout_ms : None ,
169- } ;
170-
171- while let Some ( arg) = parser. next ( ) ? {
172- match arg {
173- Arg :: Long ( "format" ) => {
174- let value = parser
175- . value ( )
176- . context ( "Option '--format' requires a value" ) ?;
177- let raw = value. string ( ) ?;
178- request. report_format = ReportFormat :: parse ( & raw , "sce config --help" ) ?;
179- }
180- Arg :: Long ( "config" ) => {
181- let value = parser
182- . value ( )
183- . context ( "Option '--config' requires a path value" ) ?;
184- if request. config_path . is_some ( ) {
185- bail ! (
186- "Option '--config' may only be provided once. Run 'sce config --help' to see valid usage."
187- ) ;
188- }
189- request. config_path = Some ( PathBuf :: from ( value. string ( ) ?) ) ;
190- }
191- Arg :: Long ( "log-level" ) => {
192- let value = parser
193- . value ( )
194- . context ( "Option '--log-level' requires a value" ) ?;
195- let raw = value. string ( ) ?;
196- request. log_level = Some ( LogLevel :: parse ( & raw , "--log-level" ) ?) ;
197- }
198- Arg :: Long ( "timeout-ms" ) => {
199- let value = parser
200- . value ( )
201- . context ( "Option '--timeout-ms' requires a numeric value" ) ?;
202- let raw = value. string ( ) ?;
203- let timeout = raw
204- . parse :: < u64 > ( )
205- . map_err ( |_| anyhow ! ( "Invalid timeout '{}' from --timeout-ms." , raw) ) ?;
206- request. timeout_ms = Some ( timeout) ;
207- }
208- Arg :: Long ( "help" ) | Arg :: Short ( 'h' ) => {
209- bail ! (
210- "Use 'sce config --help' for config usage. Command-local help does not accept additional arguments."
211- ) ;
212- }
213- Arg :: Long ( option) => {
214- bail ! (
215- "Unknown config option '--{}'. Run 'sce config --help' to see valid usage." ,
216- option
217- ) ;
218- }
219- Arg :: Short ( option) => {
220- bail ! (
221- "Unknown config option '-{}'. Run 'sce config --help' to see valid usage." ,
222- option
223- ) ;
224- }
225- Arg :: Value ( value) => {
226- let raw = value. string ( ) ?;
227- bail ! (
228- "Unexpected config argument '{}'. Run 'sce config --help' to see valid usage." ,
229- raw
230- ) ;
231- }
232- }
233- }
234-
235- Ok ( request)
236- }
237-
238137pub fn run_config_subcommand ( subcommand : ConfigSubcommand ) -> Result < String > {
239138 match subcommand {
240- ConfigSubcommand :: Help => Ok ( config_usage_text ( ) . to_string ( ) ) ,
241139 ConfigSubcommand :: Show ( request) => {
242140 let cwd = std:: env:: current_dir ( ) . context ( "Failed to determine current directory" ) ?;
243141 let runtime = resolve_runtime_config ( & request, & cwd) ?;
@@ -251,10 +149,6 @@ pub fn run_config_subcommand(subcommand: ConfigSubcommand) -> Result<String> {
251149 }
252150}
253151
254- pub fn config_usage_text ( ) -> & ' static str {
255- "Usage:\n sce config show [--config <path>] [--log-level <error|warn|info|debug>] [--timeout-ms <value>] [--format <text|json>]\n sce config validate [--config <path>] [--log-level <error|warn|info|debug>] [--timeout-ms <value>] [--format <text|json>]\n \n Resolution precedence: flags > env > config file > defaults\n Config discovery order: --config, SCE_CONFIG_FILE, then discovered global+local defaults (global merged first, local overrides per key)\n Environment keys: SCE_CONFIG_FILE, SCE_LOG_LEVEL, SCE_TIMEOUT_MS"
256- }
257-
258152fn resolve_runtime_config ( request : & ConfigRequest , cwd : & Path ) -> Result < RuntimeConfig > {
259153 resolve_runtime_config_with (
260154 request,
@@ -606,9 +500,9 @@ fn format_resolved_value_text(key: &str, value: &str, source: ValueSource) -> St
606500#[ cfg( test) ]
607501mod tests {
608502 use super :: {
609- format_show_output, format_validate_output, parse_config_subcommand ,
610- resolve_runtime_config_with , ConfigPathSource , ConfigRequest , ConfigSubcommand ,
611- LoadedConfigPath , LogLevel , ReportFormat , ResolvedValue , RuntimeConfig , ValueSource ,
503+ format_show_output, format_validate_output, resolve_runtime_config_with , ConfigPathSource ,
504+ ConfigRequest , ConfigSubcommand , LoadedConfigPath , LogLevel , ReportFormat , ResolvedValue ,
505+ RuntimeConfig , ValueSource ,
612506 } ;
613507 use anyhow:: Result ;
614508 use serde_json:: Value ;
@@ -623,52 +517,6 @@ mod tests {
623517 }
624518 }
625519
626- #[ test]
627- fn parser_routes_show_subcommand ( ) -> Result < ( ) > {
628- let parsed = parse_config_subcommand ( vec ! [ "show" . to_string( ) ] ) ?;
629- assert_eq ! ( parsed, ConfigSubcommand :: Show ( request( ) ) ) ;
630- Ok ( ( ) )
631- }
632-
633- #[ test]
634- fn parser_routes_validate_subcommand_with_options ( ) -> Result < ( ) > {
635- let parsed = parse_config_subcommand ( vec ! [
636- "validate" . to_string( ) ,
637- "--format" . to_string( ) ,
638- "json" . to_string( ) ,
639- "--log-level" . to_string( ) ,
640- "debug" . to_string( ) ,
641- "--timeout-ms" . to_string( ) ,
642- "100" . to_string( ) ,
643- "--config" . to_string( ) ,
644- "./demo.json" . to_string( ) ,
645- ] ) ?;
646- assert_eq ! (
647- parsed,
648- ConfigSubcommand :: Validate ( ConfigRequest {
649- report_format: ReportFormat :: Json ,
650- config_path: Some ( PathBuf :: from( "./demo.json" ) ) ,
651- log_level: Some ( LogLevel :: Debug ) ,
652- timeout_ms: Some ( 100 ) ,
653- } )
654- ) ;
655- Ok ( ( ) )
656- }
657-
658- #[ test]
659- fn parser_rejects_invalid_format_with_help_guidance ( ) {
660- let error = parse_config_subcommand ( vec ! [
661- "show" . to_string( ) ,
662- "--format" . to_string( ) ,
663- "yaml" . to_string( ) ,
664- ] )
665- . expect_err ( "invalid format should fail" ) ;
666- assert_eq ! (
667- error. to_string( ) ,
668- "Invalid --format value 'yaml'. Valid values: text, json. Run 'sce config --help' to see valid usage."
669- ) ;
670- }
671-
672520 #[ test]
673521 fn resolver_applies_precedence_flag_then_env_then_config_then_default ( ) -> Result < ( ) > {
674522 let req = ConfigRequest {
0 commit comments