@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
99export interface CliOptions extends GenerateOptions {
1010 source : string ;
1111 fileFilter : string ;
12+ excludePatterns : string [ ] ;
1213 destination : string ;
1314 typeFilter ?: string ;
1415 watch : boolean ;
@@ -30,7 +31,7 @@ export async function run(args: string[]): Promise<void> {
3031}
3132
3233export async function convert ( options : CliOptions ) : Promise < void > {
33- const sourceFiles = await findCSharpFiles ( options . source , options . fileFilter ) ;
34+ const sourceFiles = await findCSharpFiles ( options . source , options . fileFilter , options . excludePatterns ) ;
3435 const parsed = [ ] ;
3536 for ( const path of sourceFiles ) {
3637 parsed . push ( parseSourceFile ( path , await readFile ( path , "utf8" ) ) ) ;
@@ -75,12 +76,14 @@ export function parseArgs(args: string[]): ParsedCliOptions {
7576 c : "config" ,
7677 s : "source" ,
7778 f : "file-filter" ,
79+ x : "exclude-pattern" ,
7880 t : "type-filter" ,
7981 d : "destination" ,
8082 w : "watch" ,
8183 m : "export-module" ,
8284 } ;
8385 const booleanFlags = new Set ( [ "watch" , "export-module" , "readonly-properties" , "semicolons" ] ) ;
86+ const arrayFlags = new Set ( [ "exclude-pattern" , "exclude-patterns" ] ) ;
8487
8588 for ( let i = 0 ; i < args . length ; i ++ ) {
8689 const raw = args [ i ] ;
@@ -94,6 +97,10 @@ export function parseArgs(args: string[]): ParsedCliOptions {
9497 const name = aliases [ flagName ] ?? flagName ;
9598 if ( booleanFlags . has ( name ) ) {
9699 values . set ( name , inlineValue === undefined ? true : parseBoolean ( inlineValue , name ) ) ;
100+ } else if ( arrayFlags . has ( name ) ) {
101+ const value = inlineValue ?? args [ ++ i ] ;
102+ if ( ! value || value . startsWith ( "-" ) ) throw new Error ( `Missing value for --${ name } ` ) ;
103+ appendListValue ( values , "exclude-patterns" , value ) ;
97104 } else {
98105 const value = inlineValue ?? args [ ++ i ] ;
99106 if ( ! value || value . startsWith ( "-" ) ) throw new Error ( `Missing value for --${ name } ` ) ;
@@ -141,6 +148,7 @@ function finalizeOptions(options: ConfigFileOptions): CliOptions {
141148 return {
142149 source : options . source ,
143150 fileFilter : options . fileFilter ?? "*.cs" ,
151+ excludePatterns : normalizeStringList ( options . excludePatterns , "excludePatterns" ) ,
144152 destination : options . destination ,
145153 typeFilter : options . typeFilter ,
146154 watch : options . watch ?? false ,
@@ -157,6 +165,7 @@ function mapRawOptions(values: Map<string, string | boolean>): ParsedCliOptions
157165 setString ( options , "configPath" , values . get ( "config" ) ) ;
158166 setString ( options , "source" , values . get ( "source" ) ) ;
159167 setString ( options , "fileFilter" , values . get ( "file-filter" ) ) ;
168+ setStringList ( options , "excludePatterns" , values . get ( "exclude-patterns" ) ) ;
160169 setString ( options , "typeFilter" , values . get ( "type-filter" ) ) ;
161170 setString ( options , "destination" , values . get ( "destination" ) ) ;
162171 setBoolean ( options , "watch" , values . get ( "watch" ) ) ;
@@ -168,6 +177,11 @@ function mapRawOptions(values: Map<string, string | boolean>): ParsedCliOptions
168177 return options ;
169178}
170179
180+ function appendListValue ( values : Map < string , string | boolean > , key : string , value : string ) : void {
181+ const existing = values . get ( key ) ;
182+ values . set ( key , [ typeof existing === "string" ? existing : "" , value ] . filter ( Boolean ) . join ( "," ) ) ;
183+ }
184+
171185function setString < T extends Record < string , unknown > > (
172186 target : T ,
173187 key : keyof T ,
@@ -176,6 +190,14 @@ function setString<T extends Record<string, unknown>>(
176190 if ( typeof value === "string" ) target [ key ] = value as T [ keyof T ] ;
177191}
178192
193+ function setStringList < T extends Record < string , unknown > > (
194+ target : T ,
195+ key : keyof T ,
196+ value : string | boolean | undefined ,
197+ ) : void {
198+ if ( typeof value === "string" ) target [ key ] = splitList ( value ) as T [ keyof T ] ;
199+ }
200+
179201function setBoolean < T extends Record < string , unknown > > (
180202 target : T ,
181203 key : keyof T ,
@@ -184,17 +206,35 @@ function setBoolean<T extends Record<string, unknown>>(
184206 if ( typeof value === "boolean" ) target [ key ] = value as T [ keyof T ] ;
185207}
186208
209+ function normalizeStringList ( value : unknown , optionName : string ) : string [ ] {
210+ if ( value === undefined ) return [ ] ;
211+ if ( typeof value === "string" ) return splitList ( value ) ;
212+ if ( Array . isArray ( value ) && value . every ( ( item ) => typeof item === "string" ) ) {
213+ return value . map ( ( item ) => item . trim ( ) ) . filter ( Boolean ) ;
214+ }
215+ throw new Error ( `${ optionName } must be an array of strings` ) ;
216+ }
217+
218+ function splitList ( value : string ) : string [ ] {
219+ return value . split ( "," ) . map ( ( item ) => item . trim ( ) ) . filter ( Boolean ) ;
220+ }
221+
187222function parseBoolean ( value : string , flag : string ) : boolean {
188223 if ( [ "true" , "1" , "yes" ] . includes ( value . toLowerCase ( ) ) ) return true ;
189224 if ( [ "false" , "0" , "no" ] . includes ( value . toLowerCase ( ) ) ) return false ;
190225 throw new Error ( `--${ flag } must be true or false` ) ;
191226}
192227
193- async function findCSharpFiles ( source : string , filter : string ) : Promise < string [ ] > {
228+ async function findCSharpFiles ( source : string , filter : string , excludePatterns : string [ ] = [ ] ) : Promise < string [ ] > {
194229 const files : string [ ] = [ ] ;
195230 const matcher = globToRegExp ( filter === "*" ? "*.cs" : filter ) ;
231+ const excludeMatchers = excludePatterns . map ( globToRegExp ) ;
196232 for await ( const entry of walk ( source ) ) {
197- if ( entry . isFile && entry . path . endsWith ( ".cs" ) && matcher . test ( basename ( entry . path ) ) ) {
233+ const relativePath = relativePathFrom ( source , entry . path ) ;
234+ const excluded = excludeMatchers . some ( ( excludeMatcher ) =>
235+ excludeMatcher . test ( basename ( entry . path ) ) || excludeMatcher . test ( relativePath )
236+ ) ;
237+ if ( entry . isFile && entry . path . endsWith ( ".cs" ) && matcher . test ( basename ( entry . path ) ) && ! excluded ) {
198238 files . push ( entry . path ) ;
199239 }
200240 }
@@ -267,6 +307,12 @@ function normalizePath(path: string): string {
267307 return normalized === "/" ? normalized : normalized . replace ( / \/ $ / , "" ) ;
268308}
269309
310+ function relativePathFrom ( root : string , path : string ) : string {
311+ const normalizedRoot = `${ normalizePath ( root ) } /` ;
312+ const normalizedPath = normalizePath ( path ) ;
313+ return normalizedPath . startsWith ( normalizedRoot ) ? normalizedPath . slice ( normalizedRoot . length ) : basename ( path ) ;
314+ }
315+
270316function resolvePath ( base : string , path : string ) : string {
271317 return normalizePath ( isAbsolute ( path ) ? path : resolve ( base , path ) ) ;
272318}
0 commit comments