Packages
ex_minimatch
0.0.1
Globbing paths without walking the tree! Elixir and Erlang provide `wildcard` functions in the stdlib. But these will walk the directory tree. If you simply want to test whether a file path matches a glob, ExMinimatch is for you.
Current section
Files
Jump to
Current section
Files
lib/ex_minimatch.ex
defmodule ExMinimatch do
@moduledoc """
ExMinimatch
===========
Globbing paths without walking the tree! Elixir and Erlang provide `wildcard`
functions in the stdlib. But these will walk the directory tree. If you simply
want to test whether a file path matches a glob, ExMinimatch is for you.
Quick examples:
iex> import ExMinimatch
nil
iex> match("**/*{1..2}{a,b}.{png,jpg}", "asdf/pic2a.jpg")
true
iex> match("*.+(bar|foo)", "bar.foo")
true
iex> ["me.jpg", "images/me.png", "images/you.svg"] |> filter("**/*.{png,jpg}")
["me.jpg", "images/me.png"]
Compiled forms below allows us to cache the %ExMinimatcher{} struct when used
against large number of files repeated.
iex> compile("**/*{1..2}{a,b}.{png,jpg}") |> match("asdf/pic2a.jpg")
true
iex> ["me.jpg", "images/me.png", "images/you.svg"] |> filter(compile("**/*.{png,jpg}"))
["me.jpg", "images/me.png"]
ExMinimatch is a port of the [minimatch](https://github.com/isaacs/minimatch)
javascript project. It is a close port but not exactly the same. See
"Comparison to minimatch.js" section below.
## Glob Patterns
Supports these glob features:
- [Brace Expansion](https://github.com/gniquil/ex_brace_expansion)
- Extended glob matching
- "Globstar" ** matching
See:
- man sh
- man bash
- man 3 match
- man 5 gitignore
## Options
`compile`, `match`, and `filter` all have forms that take an options argument (as a map %{}).
The following are the explanations. By default, all fo these are false.
### log
Possible values are `:info`, and `:debug`. If set, will dump information into
repl. `:debug` dumps more.
### nobrace
Do not expand `{a,b}` and `{1..3}` brace sets.
### noglobstar
Disable `**` matching against multiple folder names.
### dot
Allow patterns to match filenames starting with a period, even if the pattern
does not explicitly have a period in that spot.
Note that by default, `a/**/b` will not match `a/.d/b`, unless dot is set, e.g.
`match("a/**/b", "a/.d/b", %{dot: true})`
### noext
Disable "extglob" style patterns like `+(a|b)`.
### nocase
Perform a case-insensitive match.
### match_base
If set, then patterns without slashes will be matched against the basename of
the path if it contains slashes. For example, `a?b` would match the path
`/xyz/123/acb`, but not `/xyz/acb/123`.
### nocomment
Suppress the behavior of treating `#` at the start of a pattern as a comment.
### nonegate
Suppress the behavior of treating leading `!` character as negation.
## Comparison to minimatch.js
`minimatch.js` converts a glob into a list of regular expressions. However, when
it comes to matching, one can choose to use `minimatch.match` or use the complete
regular expression generated by `minimatch.makeRe` to test it against a file
pattern. Unfortunately, the 2 approaches are __inconsistent__. Notably the full regular
expression based approach has a few important pieces missing. Therefore, here
we implement the first approach. For detail, take a look at `ExMinimatcher.Matcher`.
"""
@doc """
Return a compiled %ExMinimatcher{} struct, which can be used in conjunction
with `match/2`, 'match/3', or `filter/2` to match files.
This purpose of this function is to save time by precompiling the glob
pattern.
For possible glob patterns and available options, please refer to moduledoc.
"""
def compile(glob), do: compile(glob, %{})
def compile(glob, options) do
options = %{
dot: false,
nocase: false,
match_base: false,
nonegate: false,
noext: false,
noglobstar: false,
nocomment: false,
nobrace: false,
log: nil
} |> Dict.merge(options)
ExMinimatch.Compiler.compile_matcher(glob, options)
end
@doc ~S"""
Returns true when file matches the compiled %ExMinimatcher{} struct.
This is intended to be used with `compile`
## Examples
iex> compile("**/*.{png,jpg}") |> match("me.jpg")
true
iex> compile("**/*.{png,jpg}") |> match("images/me.png")
true
iex> compile("**/*.{png,jpg}") |> match("images/you.svg")
false
iex> ["me.jpg", "images/me.png", "images/you.svg"] |> filter(compile("**/*.{png,jpg}"))
["me.jpg", "images/me.png"]
"""
def match(%ExMinimatcher{pattern: pattern}, file) when pattern == [] and file == "", do: true
def match(%ExMinimatcher{pattern: pattern}, _file) when pattern == [], do: false
def match(%ExMinimatcher{} = matcher, file), do: ExMinimatch.Matcher.match_file(file, matcher)
@doc """
Return true if the file matches the glob. This is a convenience function that
is literally `glob |> compile(options) |> match(file)`
Use this for one off matching, as the glob is recompiled every time this is
called.
For possible glob patterns and available options, please refer to moduledoc.
## Examples
iex> match("**/*.png", "qwer.png")
true
iex> match("**/*.png", "qwer/qwer.png")
true
"""
def match(glob, file) when is_binary(glob), do: match(glob, file, %{})
def match(glob, file, options) when is_binary(glob), do: glob |> compile(options) |> match(file)
@doc """
Returns a list of files filtered by the compiled %ExMinimatcher{} struct.
Note the collection argument comes first, different from `match`. This is
more suitable for piping collections.
## Examples
iex> ["me.jpg", "images/me.png", "images/you.svg"] |> filter(compile("**/*.{png,jpg}"))
["me.jpg", "images/me.png"]
"""
def filter(files, %ExMinimatcher{} = matcher), do: files |> Enum.filter(&match(matcher, &1))
@doc """
return a list of files that match the given pattern. This is a convenience
function
For possible glob patterns and available options, please refer to moduledoc.
## Examples
iex> filter(["qwer.png", "asdf/qwer.png"], "**/*.png")
["qwer.png", "asdf/qwer.png"]
iex> filter(["qwer/pic1a.png", "qwer/asdf/pic2a.png", "asdf/pic2c.jpg"], "**/*{1..2}{a,b}.{png,jpg}")
["qwer/pic1a.png", "qwer/asdf/pic2a.png"]
"""
def filter(files, pattern) when is_binary(pattern), do: filter(files, pattern, %{})
def filter(files, pattern, options) when is_binary(pattern), do: files |> filter(compile(pattern, options))
end