@@ -3,6 +3,7 @@ use crate::validation::{validate_file_path, validate_output_path};
33use langcodec:: {
44 Codec , FormatType , KeyStyle , NormalizeOptions as EngineNormalizeOptions , normalize_codec,
55} ;
6+ use std:: collections:: HashSet ;
67use std:: path:: Path ;
78
89#[ derive( Debug , Clone ) ]
@@ -13,6 +14,7 @@ pub struct NormalizeCliOptions {
1314 pub check : bool ,
1415 pub no_placeholders : bool ,
1516 pub key_style : String ,
17+ pub continue_on_error : bool ,
1618}
1719
1820fn parse_key_style ( input : & str ) -> Result < KeyStyle , String > {
@@ -68,73 +70,163 @@ fn has_distinct_output_path(input_path: &str, output_path: &Option<String>) -> b
6870 . is_some_and ( |output| Path :: new ( output) != Path :: new ( input_path) )
6971}
7072
71- pub fn run_normalize_command ( opts : NormalizeCliOptions ) -> Result < ( ) , String > {
72- let expanded = path_glob:: expand_input_globs ( & opts. inputs )
73- . map_err ( |e| format ! ( "Failed to expand input patterns: {}" , e) ) ?;
74- if expanded. is_empty ( ) {
75- return Err ( "No input files matched the provided patterns" . to_string ( ) ) ;
76- }
77- if expanded. len ( ) > 1 {
78- return Err ( "Normalize currently supports exactly one input file" . to_string ( ) ) ;
79- }
73+ fn has_glob_meta ( input : & str ) -> bool {
74+ input
75+ . bytes ( )
76+ . any ( |b| matches ! ( b, b'*' | b'?' | b'[' | b'{' ) )
77+ }
8078
81- let input = & expanded[ 0 ] ;
79+ fn run_normalize_for_file (
80+ input : & str ,
81+ output : & Option < String > ,
82+ dry_run : bool ,
83+ check : bool ,
84+ no_placeholders : bool ,
85+ key_style : & KeyStyle ,
86+ ) -> Result < bool , String > {
8287 validate_file_path ( input) ?;
8388
8489 let mut codec = Codec :: new ( ) ;
8590 codec
8691 . read_file_by_extension ( input, None )
8792 . map_err ( |e| format ! ( "Failed to read input '{}': {}" , input, e) ) ?;
8893
89- let key_style = parse_key_style ( & opts. key_style ) ?;
9094 let report = normalize_codec (
9195 & mut codec,
9296 & EngineNormalizeOptions {
93- normalize_placeholders : !opts . no_placeholders ,
94- key_style,
97+ normalize_placeholders : !no_placeholders,
98+ key_style : key_style . clone ( ) ,
9599 } ,
96100 )
97101 . map_err ( |e| e. to_string ( ) ) ?;
98102
99- if opts . check {
103+ if check {
100104 if report. changed {
101105 println ! ( "would change: {}" , input) ;
102106 return Err ( format ! ( "would change: {}" , input) ) ;
103107 }
104108
105109 println ! ( "No changes needed: {}" , input) ;
106- return Ok ( ( ) ) ;
110+ return Ok ( false ) ;
107111 }
108112
109- if opts . dry_run {
113+ if dry_run {
110114 if report. changed {
111115 println ! ( "DRY-RUN: would change {}" , input) ;
116+ return Ok ( true ) ;
112117 } else {
113118 println ! ( "No changes needed: {}" , input) ;
114119 }
115- return Ok ( ( ) ) ;
120+ return Ok ( false ) ;
116121 }
117122
118123 if !report. changed {
119- if has_distinct_output_path ( input, & opts . output ) {
120- if let Some ( output) = & opts . output {
124+ if has_distinct_output_path ( input, output) {
125+ if let Some ( output) = output {
121126 validate_output_path ( output) ?;
122127 }
123- write_back ( & codec, input, & opts . output ) ?;
128+ write_back ( & codec, input, output) ?;
124129 println ! ( "No changes needed: {}" , input) ;
125- println ! ( "✅ Wrote output: {}" , opts . output. as_deref( ) . unwrap_or( input) ) ;
126- return Ok ( ( ) ) ;
130+ println ! ( "✅ Wrote output: {}" , output. as_deref( ) . unwrap_or( input) ) ;
131+ return Ok ( false ) ;
127132 }
128133
129134 println ! ( "No changes needed: {}" , input) ;
130- return Ok ( ( ) ) ;
135+ return Ok ( false ) ;
131136 }
132137
133- if let Some ( output) = & opts . output {
138+ if let Some ( output) = output {
134139 validate_output_path ( output) ?;
135140 }
136141
137- write_back ( & codec, input, & opts. output ) ?;
138- println ! ( "✅ Normalized: {}" , opts. output. as_deref( ) . unwrap_or( input) ) ;
139- Ok ( ( ) )
142+ write_back ( & codec, input, output) ?;
143+ println ! ( "✅ Normalized: {}" , output. as_deref( ) . unwrap_or( input) ) ;
144+ Ok ( true )
145+ }
146+
147+ pub fn run_normalize_command ( opts : NormalizeCliOptions ) -> Result < ( ) , String > {
148+ let expanded = path_glob:: expand_input_globs ( & opts. inputs )
149+ . map_err ( |e| format ! ( "Failed to expand input patterns: {}" , e) ) ?;
150+ if expanded. is_empty ( ) {
151+ return Err ( "No input files matched the provided patterns" . to_string ( ) ) ;
152+ }
153+
154+ if expanded. len ( ) > 1 && opts. output . is_some ( ) {
155+ return Err ( "--output cannot be used with multiple input files" . to_string ( ) ) ;
156+ }
157+
158+ let key_style = parse_key_style ( & opts. key_style ) ?;
159+
160+ let mut skip_missing: HashSet < String > = HashSet :: new ( ) ;
161+ let mut failures: Vec < String > = Vec :: new ( ) ;
162+ let mut processed_count: usize = 0 ;
163+ let mut success_count: usize = 0 ;
164+ let mut failed_count: usize = 0 ;
165+ let mut changed_count: usize = 0 ;
166+
167+ for original in & opts. inputs {
168+ if !has_glob_meta ( original) && !Path :: new ( original) . is_file ( ) {
169+ let msg = format ! ( "Input file does not exist: {}" , original) ;
170+ if opts. continue_on_error {
171+ eprintln ! ( "❌ {}" , msg) ;
172+ failures. push ( msg) ;
173+ failed_count += 1 ;
174+ skip_missing. insert ( original. clone ( ) ) ;
175+ continue ;
176+ }
177+ return Err ( msg) ;
178+ }
179+ }
180+
181+ for input in expanded {
182+ if skip_missing. contains ( & input) {
183+ continue ;
184+ }
185+
186+ processed_count += 1 ;
187+
188+ match run_normalize_for_file (
189+ & input,
190+ & opts. output ,
191+ opts. dry_run ,
192+ opts. check ,
193+ opts. no_placeholders ,
194+ & key_style,
195+ ) {
196+ Ok ( changed) => {
197+ success_count += 1 ;
198+ if changed {
199+ changed_count += 1 ;
200+ }
201+ }
202+ Err ( err) => {
203+ failed_count += 1 ;
204+ if opts. continue_on_error {
205+ eprintln ! ( "❌ {}" , err) ;
206+ failures. push ( err) ;
207+ continue ;
208+ }
209+
210+ println ! (
211+ "Summary: processed {}; success: {}; failed: {}; changed: {}" ,
212+ processed_count, success_count, failed_count, changed_count
213+ ) ;
214+ return Err ( err) ;
215+ }
216+ }
217+ }
218+
219+ println ! (
220+ "Summary: processed {}; success: {}; failed: {}; changed: {}" ,
221+ processed_count, success_count, failed_count, changed_count
222+ ) ;
223+
224+ if failures. is_empty ( ) {
225+ return Ok ( ( ) ) ;
226+ }
227+
228+ Err ( format ! (
229+ "{} file(s) failed. See errors above." ,
230+ failures. len( )
231+ ) )
140232}
0 commit comments