tagformula 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f6dd975b3faff6b1ecf509af6d3099c4a435043
4
+ data.tar.gz: a97caeec73a791266dbc8fe103dd0b4684ed2dc5
5
+ SHA512:
6
+ metadata.gz: c47f0b580bf0961ea0703522b5e1024c6d1ab35ef0e3c36269f2edc808abbf965a55596a5cf8df521b41cb7c86ee88d2d892f43d24e03c6576c030d18a130ae5
7
+ data.tar.gz: 8c7ccd4638de10725bdd450595cd99bb79f643dc0cf6d15519022282ec0d2266231fb7a4394914dea6e7dce61d5de2d59e39ea97b68b8106f70a09b2c1419674
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tagformula.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2014 Geoff Youngs
2
+
3
+
4
+
5
+
6
+ MIT License
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Tagformula::Parser
2
+
3
+ Match tag formulas with simple boolean operations against lists of strings. e.g. the formula "foo & ! bar" would match ['foo'], but not ['foo', 'bar']
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'tagformula'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install tagformula
18
+
19
+ ## Usage
20
+
21
+ Tagformula::Parser matches arrays of strings against formulas which require the presence or absence of strings in the array. Conditions can be nested (in brackets), or negated (with a !) and can be 'and' ('&') or 'or' ('|') conditions.
22
+
23
+
24
+ require 'tagformula/parser'
25
+
26
+ formula = Tagformula::Parser.parse("alpha & beta")
27
+
28
+ formula.matches?(%w[alpha beta]) # => true
29
+ formula.matches?(%w[alpha gamma]) # => false
30
+
31
+ formula = Tagformula::Parser.parse("alpha & (beta | gamma)")
32
+
33
+ formula.matches?(%w[alpha beta]) # => true
34
+ formula.matches?(%w[alpha gamma]) # => true
35
+
36
+ formula = Tagformula::Parser.parse("alpha & !beta")
37
+
38
+ formula.matches?(%w[alpha beta]) # => false
39
+ formula.matches?(%w[alpha gamma]) # => true
40
+
41
+ Operators:
42
+
43
+ expr & expr requires both expressions to be present
44
+ expr | expr requires either expression to be present
45
+ expr1 & ! expr2 requires the expr1 to be present and expr2 to be absent.
46
+
47
+ Special words:
48
+
49
+ "true" and "yes" will always match as true.
50
+
51
+ "false" and "no" will always match as false.
52
+
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( http://github.com/geoffyoungs/tagformula/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
data/lib/tagformula.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "tagformula/version"
2
+
3
+ module Tagformula
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,138 @@
1
+ require 'tagformula'
2
+ require 'strscan'
3
+
4
+ module Tagformula::Parser
5
+ class Group
6
+ def initialize()
7
+ @contents = []
8
+ end
9
+ def empty?
10
+ @contents.empty?
11
+ end
12
+ def last_entry
13
+ @contents.last
14
+ end
15
+ def push_condition(what)
16
+ if [:and,:or].include?(what) && (! @contents.last.is_a?(Symbol))
17
+ @contents << what
18
+ end
19
+ end
20
+ def push_entry(entry)
21
+ case entry
22
+ when Tag, Group
23
+ @contents << entry
24
+ end
25
+ end
26
+ def required_tags
27
+ @contents.map do |content|
28
+ if content.is_a?(Tag) && content.needed
29
+ content.tagname
30
+ elsif content.is_a?(Group)
31
+ content.required_tags
32
+ else
33
+ nil
34
+ end
35
+ end.flatten.compact
36
+ end
37
+ def matches?(taglist)
38
+ condition, op, i = nil, nil, 0
39
+ while @contents[i]
40
+ case @contents[i]
41
+ when Symbol
42
+ op = @contents[i]
43
+ when Group, Tag
44
+ res = @contents[i].matches?(taglist)
45
+ raise "Epic fail - res is neither true nor false" unless [true,false].include?(res)
46
+ case op
47
+ when :and
48
+ condition &&= res
49
+ when :or
50
+ condition ||= res
51
+ when nil
52
+ if i.zero?
53
+ condition = res
54
+ else
55
+ raise "Epic fail - apparently no operator between tags. How does this even happen?"
56
+ end
57
+ end
58
+ else
59
+ raise "Epic fail - invalid group entry" # epic fail
60
+ end
61
+ i += 1
62
+ end
63
+ condition
64
+ end
65
+ end
66
+ class Tag
67
+ attr_reader :tagname, :needed
68
+ def initialize(tagname, needed)
69
+ @tagname, @needed = tagname, needed
70
+ end
71
+ def matches?(taglist)
72
+ case @tagname
73
+ when 'true', 'false', 'yes', 'no'
74
+ @needed
75
+ else
76
+ taglist.include?(@tagname) ? @needed : ! @needed
77
+ end
78
+ end
79
+ end
80
+ module_function
81
+ def parse(str)
82
+ groups = [Group.new]
83
+ scanner = StringScanner.new(str)
84
+ until scanner.eos?
85
+ last = groups.last.last_entry
86
+
87
+ if scanner.scan(/\(/)
88
+ raise SyntaxError, "Unexpected tag at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" unless last.nil? or last.is_a?(Symbol)
89
+ push_group(groups)
90
+ elsif scanner.scan(/\)/)
91
+ raise SyntaxError, "Unexpected group end at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if last.nil? or last.is_a?(Symbol)
92
+ grp = pop_group(groups)
93
+ raise SyntaxError, "Empty group at offset #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if grp.empty?
94
+ raise SyntaxError, "Unmatched braces at offset #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if groups.empty?
95
+ elsif scanner.scan(/([-0-9a-zA-Z.]+)/)
96
+ raise SyntaxError, "Unexpected tag at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" unless last.nil? or last.is_a?(Symbol)
97
+ push_tag(groups, scanner[1])
98
+ elsif scanner.scan(/[!~]\s*([-0-9a-zA-Z.]+)/)
99
+ raise SyntaxError, "Unexpected tag at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" unless last.nil? or last.is_a?(Symbol)
100
+ push_not_tag(groups, scanner[1])
101
+ elsif scanner.scan(/[,|]/)
102
+ raise SyntaxError, "Unexpected operator at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if last.is_a?(Symbol)
103
+ push_or(groups)
104
+ elsif scanner.scan(/[&+]/)
105
+ raise SyntaxError, "Unexpected operator at #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if last.is_a?(Symbol)
106
+ push_and(groups)
107
+ elsif scanner.scan(/\s+/)
108
+ else
109
+ raise SyntaxError, "Error at offset #{scanner.pos} - #{scanner.peek(scanner.rest_size)}"
110
+ end
111
+ end
112
+ raise SyntaxError, "Unmatched braces at offset #{scanner.pos} - #{scanner.peek(scanner.rest_size)}" if groups.size != 1
113
+ groups[0]
114
+ end
115
+ class << self
116
+ private
117
+ def push_group(groups)
118
+ group = Group.new
119
+ groups.last.push_entry(group)
120
+ groups.push(group)
121
+ end
122
+ def pop_group(groups)
123
+ groups.pop()
124
+ end
125
+ def push_tag groups, tag
126
+ groups.last.push_entry(Tag.new(tag, true))
127
+ end
128
+ def push_not_tag groups, tag
129
+ groups.last.push_entry(Tag.new(tag, false))
130
+ end
131
+ def push_or groups
132
+ groups.last.push_condition(:or)
133
+ end
134
+ def push_and groups
135
+ groups.last.push_condition(:and)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,3 @@
1
+ module Tagformula
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,39 @@
1
+ $: << File.dirname(__FILE__)+'/../lib'
2
+ require 'tagformula/parser'
3
+
4
+ describe Tagformula::Parser do
5
+ describe '#parse' do
6
+
7
+ it "parses simple formulas" do
8
+ formula = Tagformula::Parser.parse("alpha & beta")
9
+ expect(formula.required_tags).to include('alpha', 'beta')
10
+ expect(formula.required_tags).not_to include('gamma')
11
+ expect(formula.matches?(['alpha'])).to be_false
12
+ expect(formula.matches?(['alpha', 'beta'])).to be_true
13
+ end
14
+
15
+ it "parses more negated tag formulas" do
16
+ formula = Tagformula::Parser.parse("alpha & ! beta")
17
+ expect(formula.required_tags).to include('alpha')
18
+ expect(formula.required_tags).not_to include('beta')
19
+ expect(formula.matches?(['alpha', 'beta'])).to be_false
20
+ expect(formula.matches?(['alpha'])).to be_true
21
+ end
22
+
23
+ it "fails on invalid formulas" do
24
+ # Unclosed bracket
25
+ expect { Tagformula::Parser.parse("(alpha & beta") }.to raise_error
26
+ # Missing operator
27
+ expect { Tagformula::Parser.parse("alpha & beta gamma") }.to raise_error
28
+ # Empty group
29
+ expect { Tagformula::Parser.parse("() & alpha & beta") }.to raise_error
30
+ end
31
+
32
+ it "parses tags with periods" do
33
+ formula = Tagformula::Parser.parse("alpha.beta | gamma.delta")
34
+ expect(formula.required_tags).to include('alpha.beta')
35
+ expect(formula.matches?(['alpha.beta'])).to be_true
36
+ expect(formula.matches?(['epsilon'])).to be_false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tagformula/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tagformula"
8
+ spec.version = Tagformula::VERSION
9
+ spec.authors = ["Geoff Youngs\n\n\n"]
10
+ spec.email = ["git@intersect-uk.co.uk"]
11
+ spec.summary = %q{Simple formula parser & matcher}
12
+ spec.description = %q{Match tag formulas with simple boolean operations against lists of strings. e.g. the formula "foo & ! bar" would match ['foo'], but not ['foo', 'bar'] }
13
+ spec.homepage = "http://github.com/geoffyoungs/tagformula-parser"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake", '~> 10.1'
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tagformula
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - |+
8
+ Geoff Youngs
9
+
10
+
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2014-02-25 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bundler
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '1.5'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: '10.1'
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '10.1'
44
+ - !ruby/object:Gem::Dependency
45
+ name: rspec
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ~>
49
+ - !ruby/object:Gem::Version
50
+ version: '2.14'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: '2.14'
58
+ description: 'Match tag formulas with simple boolean operations against lists of strings. e.g.
59
+ the formula "foo & ! bar" would match [''foo''], but not [''foo'', ''bar''] '
60
+ email:
61
+ - git@intersect-uk.co.uk
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - lib/tagformula.rb
72
+ - lib/tagformula/parser.rb
73
+ - lib/tagformula/version.rb
74
+ - spec/formula_spec.rb
75
+ - tagformula.gemspec
76
+ homepage: http://github.com/geoffyoungs/tagformula-parser
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.2.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Simple formula parser & matcher
100
+ test_files:
101
+ - spec/formula_spec.rb