@@ -7,6 +7,7 @@ import yargs, { CommandModule } from "yargs";
77import { TextDocument , TextEdit } from "vscode-languageserver-textdocument" ;
88import * as fs from "fs" ;
99import * as path from "path" ;
10+ import { glob } from "glob" ;
1011import { SepticCnfg } from "../cnfg" ;
1112import { SepticCnfgFormatter } from "../formatter" ;
1213import { createDocumentFromFile } from "../configProvider" ;
@@ -74,67 +75,131 @@ function checkFormatting(
7475 return originalContent === formattedContent ;
7576}
7677
77- async function handler ( options : FormatOptions ) : Promise < void > {
78- const document = await createDocumentFromFile ( options . file ) ;
78+ async function formatSingleFile (
79+ filePath : string ,
80+ check : boolean ,
81+ outputPath ?: string ,
82+ ) : Promise < { needsFormatting : boolean ; filePath : string } > {
83+ const document = await createDocumentFromFile ( filePath ) ;
7984 const originalContent = document . getText ( ) ;
8085
8186 const edits = formatSepticConfig ( document ) ;
8287 const formattedContent = applyTextEdits ( originalContent , edits ) ;
8388
84- if ( options . check ) {
85- const isFormatted = checkFormatting ( originalContent , formattedContent ) ;
89+ const isFormatted = checkFormatting ( originalContent , formattedContent ) ;
90+
91+ if ( check ) {
8692 if ( isFormatted ) {
87- console . log ( `✓ ${ options . file } is formatted correctly` ) ;
88- process . exit ( 0 ) ;
93+ console . log ( `✓ ${ filePath } is formatted correctly` ) ;
8994 } else {
90- console . log ( `✗ ${ options . file } needs formatting` ) ;
91- process . exit ( 1 ) ;
95+ console . log ( `✗ ${ filePath } needs formatting` ) ;
9296 }
97+ return { needsFormatting : ! isFormatted , filePath } ;
9398 }
9499
95- const outputPath = options . output || options . file ;
96-
97- const outputDir = path . dirname ( outputPath ) ;
100+ const actualOutputPath = outputPath || filePath ;
101+ const outputDir = path . dirname ( actualOutputPath ) ;
98102 if ( ! fs . existsSync ( outputDir ) ) {
99103 fs . mkdirSync ( outputDir , { recursive : true } ) ;
100104 }
101105
102- fs . writeFileSync ( outputPath , formattedContent , "utf-8" ) ;
106+ fs . writeFileSync ( actualOutputPath , formattedContent , "utf-8" ) ;
103107
104- if ( options . output ) {
105- console . log ( `✓ Formatted file saved to: ${ outputPath } ` ) ;
108+ if ( outputPath ) {
109+ console . log ( `✓ Formatted file saved to: ${ actualOutputPath } ` ) ;
106110 } else {
107- console . log ( `✓ Formatted: ${ options . file } ` ) ;
111+ console . log ( `✓ Formatted: ${ filePath } ` ) ;
112+ }
113+
114+ return { needsFormatting : false , filePath } ;
115+ }
116+
117+ async function handler ( options : FormatOptions ) : Promise < void > {
118+ // Ensure the pattern includes .cnfg extension
119+ let pattern = options . file ;
120+ if ( ! pattern . endsWith ( ".cnfg" ) ) {
121+ pattern = pattern + ".cnfg" ;
122+ }
123+
124+ // Find all files matching the pattern
125+ const files = await glob ( pattern , {
126+ absolute : true ,
127+ nodir : true ,
128+ windowsPathsNoEscape : true ,
129+ } ) ;
130+
131+ if ( files . length === 0 ) {
132+ // Try treating it as a literal file path
133+ if ( fs . existsSync ( pattern ) ) {
134+ files . push ( path . resolve ( pattern ) ) ;
135+ } else {
136+ console . error ( `No files found matching pattern: ${ pattern } ` ) ;
137+ process . exit ( 1 ) ;
138+ }
139+ }
140+
141+ // If output is specified and multiple files matched, this is an error
142+ if ( options . output && files . length > 1 ) {
143+ console . error (
144+ `Error: Cannot specify --output when multiple files match the pattern` ,
145+ ) ;
146+ process . exit ( 1 ) ;
147+ }
148+
149+ const results = await Promise . all (
150+ files . map ( ( file ) =>
151+ formatSingleFile ( file , options . check || false , options . output ) ,
152+ ) ,
153+ ) ;
154+
155+ if ( options . check ) {
156+ const filesNeedingFormatting = results . filter ( ( r ) => r . needsFormatting ) ;
157+ if ( filesNeedingFormatting . length > 0 ) {
158+ console . log (
159+ `\n${ filesNeedingFormatting . length } file(s) need formatting` ,
160+ ) ;
161+ process . exit ( 1 ) ;
162+ } else {
163+ console . log (
164+ `\nAll ${ files . length } file(s) are formatted correctly` ,
165+ ) ;
166+ process . exit ( 0 ) ;
167+ }
108168 }
109169}
110170
111171export const formatCommand : CommandModule < object , FormatOptions > = {
112172 command : "format <file>" ,
113- describe : "Format a Septic config file" ,
173+ describe : "Format Septic config file(s) matching a pattern " ,
114174 builder : ( yargs ) => {
115175 return yargs
116176 . positional ( "file" , {
117177 type : "string" ,
118- description : "Path to Septic config file to format" ,
178+ description :
179+ "Path or glob pattern to Septic config file(s) to format" ,
119180 demandOption : true ,
120181 } )
121182 . option ( "check" , {
122183 alias : "c" ,
123184 type : "boolean" ,
124185 description :
125- "Check if the file is formatted without modifying it " ,
186+ "Check if file(s) are formatted without modifying them " ,
126187 default : false ,
127188 } )
128189 . option ( "output" , {
129190 alias : "o" ,
130191 type : "string" ,
131192 description :
132- "Output path for formatted file (default: overwrites input file)" ,
193+ "Output path for formatted file (only works with single file)" ,
133194 } )
134- . example ( "$0 format config.cnfg" , "Format a config file" )
195+ . example ( "$0 format config.cnfg" , "Format a single config file" )
196+ . example (
197+ "$0 format '**/*.cnfg'" ,
198+ "Format all .cnfg files recursively" ,
199+ )
135200 . example (
136- "$0 format config .cnfg --check" ,
137- "Check if a config file is formatted" ,
201+ "$0 format '**/* .cnfg' --check" ,
202+ "Check if all .cnfg files are formatted" ,
138203 )
139204 . example (
140205 "$0 format config.cnfg --output formatted.cnfg" ,
0 commit comments