@@ -47,12 +47,13 @@ function printUsage(filter?: string) {
4747 }
4848 if ( ! filter || filter === 'ingest' ) {
4949 console . log ( `
50- - ingest <vault> <source> [-p password]
50+ - ingest <vault> <source> [-i ignore]+ [- p password]
5151
5252 Encrypt and copy source folders/files into vault.
5353
5454 <vault> Vault directory
5555 <source> Source directory
56+ [-i ignore]+ Ignore files/folders that pattern (*, **, ? supported)
5657 [-p password] Encryption password` ) ;
5758 }
5859 if ( ! filter || filter === 'init' ) {
@@ -158,6 +159,37 @@ function treeChars(depth: number, last: boolean, folder: boolean, status?: strin
158159 return { prefix, postfix } ;
159160}
160161
162+ function glob ( pattern : string , path : string , name : string , dir : boolean ) : boolean {
163+ const neg = pattern . startsWith ( '!' ) ;
164+ if ( neg || pattern . startsWith ( '\\!' ) ) {
165+ pattern = pattern . substr ( 1 ) ;
166+ }
167+ const firstSep = pattern . indexOf ( '/' ) ;
168+ if ( firstSep >= 0 && firstSep < pattern . length - 1 ) {
169+ // matching full path
170+ name = path + '/' + name ;
171+ }
172+ if ( pattern . endsWith ( '/' ) ) {
173+ if ( ! dir ) {
174+ return false ;
175+ }
176+ pattern = pattern . substr ( 0 , pattern . length - 1 ) ;
177+ }
178+ const regex = new RegExp (
179+ '^' +
180+ pattern
181+ . split ( '**' )
182+ . map ( p => p
183+ . replace ( / [ . + ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
184+ . replace ( / \* / g, '[^/]*' )
185+ . replace ( / \? / g, '[^/]' ) )
186+ . join ( '.*' ) +
187+ '$'
188+ ) ;
189+ const result = regex . test ( name ) ;
190+ return neg ? ! result : result ;
191+ }
192+
161193async function cmdChpass ( args : string [ ] ) : Promise < number > {
162194 let target : string | null = null ;
163195 let password : string | null = null ;
@@ -304,6 +336,7 @@ async function cmdIngest(args: string[]): Promise<number> {
304336 let target : string | null = null ;
305337 let source : string | null = null ;
306338 let password : string | null = null ;
339+ const ignore : string [ ] = [ ] ;
307340 for ( ; ; ) {
308341 const arg = args . shift ( ) ;
309342 if ( typeof arg === 'undefined' ) break ;
@@ -321,6 +354,14 @@ async function cmdIngest(args: string[]): Promise<number> {
321354 console . error ( `\nCannot specify password more than once` ) ;
322355 return 1 ;
323356 }
357+ } else if ( arg === '-i' ) {
358+ const ig = args . shift ( ) ;
359+ if ( typeof ig === 'undefined' ) {
360+ printUsage ( 'ingest' ) ;
361+ console . error ( `\nMissing ignore pattern` ) ;
362+ return 1 ;
363+ }
364+ ignore . push ( ig ) ;
324365 } else if ( target === null ) {
325366 target = arg ;
326367 } else if ( source === null ) {
@@ -374,15 +415,25 @@ async function cmdIngest(args: string[]): Promise<number> {
374415 success : '(copied!)' ,
375416 linked : '(linked!)'
376417 } ;
418+ const shouldIgnore = ( name : string , dir : boolean ) =>
419+ ignore . some ( p => glob ( p , vault . getPath ( ) . substr ( 1 ) , name , dir ) ) ;
377420 for ( let i = 0 ; i < items . length ; i ++ ) {
378421 const { name, dir } = items [ i ] ;
379422 const full = path . join ( src , name ) ;
380423 let status = '?' ;
381424 if ( dir ) {
382- status = statusMap [ await vault . putFolder ( name ) ] ;
425+ if ( shouldIgnore ( name , true ) ) {
426+ status = '(ignored)' ;
427+ } else {
428+ status = statusMap [ await vault . putFolder ( name ) ] ;
429+ }
383430 } else {
384- const bytes = await srcIO . read ( full ) ;
385- status = statusMap [ await vault . putFile ( name , bytes ) ] ;
431+ if ( shouldIgnore ( name , false ) ) {
432+ status = '(ignored)' ;
433+ } else {
434+ const bytes = await srcIO . read ( full ) ;
435+ status = statusMap [ await vault . putFile ( name , bytes ) ] ;
436+ }
386437 }
387438 const { prefix, postfix } = treeChars ( depth , i >= items . length - 1 , dir , status ) ;
388439 console . log ( prefix + name + postfix ) ;
@@ -770,6 +821,60 @@ async function cmdTest(args: string[]): Promise<number> {
770821 }
771822 }
772823
824+ {
825+ const testGlob = ( result : boolean , pat : string , path : string , name : string , dir : boolean ) => {
826+ const r = glob ( pat , path , name , dir ) ;
827+ if ( r !== result ) {
828+ throw new Error ( `Expecting ${ result } : glob("${ pat } ", "${ path } ", "${ name } ", ${ dir } )` ) ;
829+ }
830+ }
831+ // Match by name anywhere
832+ testGlob ( true , '.DS_Store' , 'foo/bar' , '.DS_Store' , false ) ;
833+ testGlob ( false , '.DS_Store' , 'foo/bar' , 'not_DS_Store' , false ) ;
834+
835+ // Match full path
836+ testGlob ( true , 'foo/bar.txt' , 'foo' , 'bar.txt' , false ) ;
837+ testGlob ( true , 'foo/bar.txt' , 'foo' , 'bar.txt' , true ) ;
838+ testGlob ( false , 'foo/bar.txt' , 'foo' , 'baz.txt' , false ) ;
839+ testGlob ( false , 'foo/bar.txt' , 'other' , 'bar.txt' , false ) ;
840+
841+ // Match directories with trailing slash
842+ testGlob ( true , 'build/' , '' , 'build' , true ) ;
843+ testGlob ( false , 'build/' , '' , 'build' , false ) ;
844+ testGlob ( true , 'foo/bar/' , 'foo' , 'bar' , true ) ;
845+ testGlob ( false , 'foo/bar/' , 'foo' , 'bar' , false ) ;
846+
847+ // Match wildcard in name
848+ testGlob ( true , '*.log' , 'logs' , 'error.log' , false ) ;
849+ testGlob ( false , '*.log' , 'logs' , 'error.txt' , false ) ;
850+
851+ // Match wildcard in path
852+ testGlob ( true , '**/temp' , 'foo/bar' , 'temp' , true ) ;
853+ testGlob ( true , '**/temp' , '' , 'temp' , true ) ;
854+ testGlob ( false , '**/temp' , 'foo/bar' , 'not_temp' , true ) ;
855+
856+ // Match file deeply
857+ testGlob ( true , '**/*.bak' , 'foo/bar' , 'data.bak' , false ) ;
858+ testGlob ( false , '**/*.bak' , 'foo/bar' , 'data.txt' , false ) ;
859+
860+ // Single-char wildcard
861+ testGlob ( true , 'file?.txt' , '' , 'file1.txt' , false ) ;
862+ testGlob ( false , 'file?.txt' , '' , 'file10.txt' , false ) ;
863+
864+ // Negated pattern
865+ testGlob ( false , '!secret.txt' , '' , 'secret.txt' , false ) ;
866+ testGlob ( true , '!secret.txt' , '' , 'visible.txt' , false ) ;
867+ testGlob ( true , '\\!secret.txt' , '' , '!secret.txt' , false ) ;
868+
869+ // Leading slash (matches from root)
870+ testGlob ( true , '/root.txt' , '' , 'root.txt' , false ) ;
871+ testGlob ( false , '/root.txt' , 'subdir' , 'root.txt' , false ) ;
872+
873+ // Match nested folder/file
874+ testGlob ( true , 'a/**/z.js' , 'a/b/c' , 'z.js' , false ) ;
875+ testGlob ( false , 'a/**/z.js' , 'x/y/z' , 'z.js' , false ) ;
876+ }
877+
773878 console . log ( 'success!' ) ;
774879 return 0 ;
775880}
0 commit comments