Search code examples
javascriptnode.jsglobminimatch

How to write a single minimatch glob that matches all js files not in a directory


I have a situation where I need a single glob pattern (that uses minimatch) to match all JavaScript files which are not in a certain directory. Unfortunately, I'm using another tool that doesn't expose any options (like an ignore glob), so it has to be a single glob to do the job.

Here's what I have so far

screenshot of globtester

Example input (it should not match the top, but it should match the bottom):

docs/foo/thing.js
docs/thing.js
client/docs/foo/thing.js
client/docs/thing.js

src/foo/thing.js
src/thing.js
docs-src/foo/thing.js
docs-src/thing.js
client/docs-src/foo/thing.js
client/docs-src/thing.js

And here's what I have for the glob pattern so far:

**/!(docs)/*.js

With that I'm matching docs/foo/thing.js and client/docs/foo/thing.js and not matching docs-src/thing.js or client/docs-src/thing.js. If I switch my glob to **/!(docs)/**/*.js then I can match client/docs-src/thing.js, but I also match client/docs/thing.js.

I'm not certain this is possible so I may need to find another solution for my problem :-/


Solution

  • I think you may be running into a limitation of minimatch (or any implementation of fnmatch(3)) and globstar. It's perhaps worth noting that no C implementation of fnmatch that I'm aware of actually does implement globstar, but since fnmatch impls (including minimatch) serve the interests of their globbers, this may vary.

    The glob that you think should work actually does work, when used as a glob.

    $ find . -type f
    ./docs/foo/thing.js
    ./docs/thing.js
    ./docs/nope.txt
    ./docs-src/foo/thing.js
    ./docs-src/thing.js
    ./x.sh
    ./client/docs/foo/thing.js
    ./client/docs/thing.js
    ./client/docs/nope.txt
    ./client/docs-src/foo/thing.js
    ./client/docs-src/thing.js
    ./client/docs-src/nope.txt
    ./client/nope.txt
    ./src/foo/thing.js
    ./src/thing.js
    
    $ for i in ./!(docs)/**/*.js; do echo $i; done
    ./client/docs-src/foo/thing.js
    ./client/docs-src/thing.js
    ./client/docs/foo/thing.js
    ./client/docs/thing.js
    ./docs-src/foo/thing.js
    ./docs-src/thing.js
    ./src/foo/thing.js
    ./src/thing.js
    
    $ node -p 'require("glob").sync("./!(docs)/**/*.js")'
    [ './client/docs-src/foo/thing.js',
      './client/docs-src/thing.js',
      './client/docs/foo/thing.js',
      './client/docs/thing.js',
      './docs-src/foo/thing.js',
      './docs-src/thing.js',
      './src/foo/thing.js',
      './src/thing.js' ]
    

    EDIT: Oh, I see, you want to only match things at any folder depth that do not have any docs path parts anywhere in the path. No, that is not possible to do in a way that supports arbitrary depth, as a glob or a minimatch pattern. You'll have to use excludes, or construct a glob that looks like: {!(docs),!(docs)/!(docs),!(docs)/!(docs)/!(docs),!(docs)/!(docs)/!(docs)/!(docs)}/*.js

    Otherwise, a path like x/docs/y/z.js will match against **/!(docs)/**/*.js by saying that the first ** matches nothing, the !(docs) matches against x, the next ** matches against docs/y and then *.js matches against z.js.