Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
khash committed Apr 12, 2020
0 parents commit 39b3056
Show file tree
Hide file tree
Showing 16 changed files with 387 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
1 change: 1 addition & 0 deletions .ruby-gemset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
glob_matcher
2 changes: 2 additions & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ruby-2.5.8

4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source "https://rubygems.org"

# Specify your gem's dependencies in glob_matcher.gemspec
gemspec
35 changes: 35 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
PATH
remote: .
specs:
glob_matcher (0.1.0)

GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.3)
rake (10.5.0)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-support (3.9.2)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 2.0)
glob_matcher!
rake (~> 10.0)
rspec (~> 3.9)

BUNDLED WITH
2.1.4
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<img src="http://cdn2-cloud66-com.s3.amazonaws.com/images/oss-sponsorship.png" width=150/>

# GlobMatcher

GlobMatcher is a string matcher for Ruby. You can use it to check for matches using wildcards. It supports multiple patterns as well as negative patterns.

GlobMatcher is used in [Cloud 66 Skycap](https://www.cloud66.com/containers/skycap/) to filter git branches before deployment.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'glob_matcher'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install glob_matcher

## Usage

GlobMatcher checks strings against a pattern. A pattern is a space delimited list of single patterns to check against. Here are some patterns:

```
abc -> Matches abc
a* -> Matches ab, abc, azyz, ...
foo*bar -> Matches fooxbar, fooxxxxbar, ...
!abc -> Matches any value except for abc
!ab* -> Matches any value except for anything that matches ab*
foo{abc,xyz}bar -> Matches fooabcbar and fooxyzbar
```

You can use multiple patterns:

```
abc xyz -> Matches abc or xyz (OR)
!abc !xyz -> Matches anything expcet abc and xyz (AND)
foo bar !fuzz !buzz -> Matches foo or bar but not fuzz and buzz
```

```ruby
matcher = ::GlobMatcher::Matcher.new('hel*')
matcher.is_match? 'hello' # true

matcher = ::GlobMatcher::Matcher.new('fo* !bar')
matcher.is_match? 'foo' # true
matcher.is_match? 'bar' # false
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/cloud66-oss/glob_matcher.
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "bundler/gem_tasks"
task :default => :spec
14 changes: 14 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "glob_matcher"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
38 changes: 38 additions & 0 deletions glob_matcher.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "glob_matcher/version"

Gem::Specification.new do |spec|
spec.name = "glob_matcher"
spec.version = GlobMatcher::VERSION
spec.authors = ["Khash Sajadi"]
spec.email = ["[email protected]"]

spec.summary = %q{This is a simple string matcher for glob patterns}
spec.description = %q{Use wildcard placeholders to find string matches as well as using ! as a negative matcher}
spec.homepage = "https://www.cloud66.com/oss/"

# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/cloud66-oss/glob_matcher"
else
raise "RubyGems 2.0 or newer is required to protect against " \
"public gem pushes."
end

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 2.0"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.9"
end
6 changes: 6 additions & 0 deletions lib/glob_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "glob_matcher/version"
require "glob_matcher/matcher"

module GlobMatcher
class Error < StandardError; end
end
60 changes: 60 additions & 0 deletions lib/glob_matcher/matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module GlobMatcher
class Matcher
attr_reader :glob

def initialize(glob)
@glob = glob
end

# checks if the given value matches the pattern
def is_match?(value)
pattern_groups = group_patterns
if pattern_groups[:negatives].empty?
negatives = true
else
negatives = pattern_groups[:negatives].all? { |x| is_single_match?(x, value) }
end

if pattern_groups[:positives].empty?
positives = true
else
positives = pattern_groups[:positives].any? { |x| is_single_match?(x, value) }
end

return false if pattern_groups[:positives].empty? && pattern_groups[:negatives].empty?
return positives && negatives
end

private

# groups patterns into 2 groups: negatives
# and positives.
def group_patterns
patterns = @glob.split(' ')
negatives = []
positives = []
patterns.each do |x|
if is_negative?(x)
negatives << x
else
positives << x
end
end

return { negatives: negatives, positives: positives }
end

def is_negative?(pattern)
pattern.start_with? '!'
end

def is_single_match?(pattern, value)
if is_negative?(pattern)
# this will allow escaping of !
!File.fnmatch?(pattern[1..-1], value, File::FNM_EXTGLOB)
else
File.fnmatch?(pattern, value, File::FNM_EXTGLOB)
end
end
end
end
3 changes: 3 additions & 0 deletions lib/glob_matcher/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module GlobMatcher
VERSION = "0.1.0"
end
41 changes: 41 additions & 0 deletions spec/matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
RSpec.describe ::GlobMatcher::Matcher do
context 'with a glob_matcher' do
it 'should match a single pattern' do
expect(::GlobMatcher::Matcher.new('').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('*').is_match?('abc')).to be_truthy
expect(::GlobMatcher::Matcher.new('xy*').is_match?('xyzpq')).to be_truthy
expect(::GlobMatcher::Matcher.new('xy*').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('a*d').is_match?('abcd')).to be_truthy
expect(::GlobMatcher::Matcher.new('a\*d').is_match?('a*d')).to be_truthy
expect(::GlobMatcher::Matcher.new('a{x,y}d').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('a{x,y}d').is_match?('axc')).not_to be_truthy
end

it 'should match multiple patterns' do
expect(::GlobMatcher::Matcher.new('dev master').is_match?('dev')).to be_truthy
expect(::GlobMatcher::Matcher.new('dev').is_match?('DEV')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('dev master').is_match?('master')).to be_truthy
expect(::GlobMatcher::Matcher.new('dev master feature/*').is_match?('master')).to be_truthy
expect(::GlobMatcher::Matcher.new('dev master feature/*').is_match?('feature/foo')).to be_truthy
expect(::GlobMatcher::Matcher.new('dev master feature/*').is_match?('hotfix/foo')).not_to be_truthy
end

it 'should match single negative' do
expect(::GlobMatcher::Matcher.new('!master').is_match?('master')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('!master').is_match?('dev')).to be_truthy
expect(::GlobMatcher::Matcher.new('!a*').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('!a*').is_match?('xyz')).to be_truthy
expect(::GlobMatcher::Matcher.new('!master').is_match?('!master')).to be_truthy
expect(::GlobMatcher::Matcher.new('\!master').is_match?('!master')).to be_truthy
end

it 'should match multiple mixed' do
expect(::GlobMatcher::Matcher.new('!master !dev !abc').is_match?('xyz')).to be_truthy
expect(::GlobMatcher::Matcher.new('!master !dev !abc').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('master feature/* !dev !abc').is_match?('abc')).not_to be_truthy
expect(::GlobMatcher::Matcher.new('master feature/* !dev !abc').is_match?('master')).to be_truthy
expect(::GlobMatcher::Matcher.new('master feature/* !dev !abc').is_match?('feature/foo')).to be_truthy
end

end
end
Loading

0 comments on commit 39b3056

Please sign in to comment.