mtg_search_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +125 -0
  7. data/Rakefile +1 -0
  8. data/lib/mtg_search_parser.rb +92 -0
  9. data/lib/mtg_search_parser/lexer.rb +80 -0
  10. data/lib/mtg_search_parser/nodes/and.rb +6 -0
  11. data/lib/mtg_search_parser/nodes/left_paren.rb +6 -0
  12. data/lib/mtg_search_parser/nodes/node.rb +13 -0
  13. data/lib/mtg_search_parser/nodes/not.rb +6 -0
  14. data/lib/mtg_search_parser/nodes/or.rb +6 -0
  15. data/lib/mtg_search_parser/nodes/query.rb +19 -0
  16. data/lib/mtg_search_parser/nodes/right_paren.rb +6 -0
  17. data/lib/mtg_search_parser/not_group.rb +13 -0
  18. data/lib/mtg_search_parser/or_group.rb +13 -0
  19. data/lib/mtg_search_parser/parsed/any_color.rb +7 -0
  20. data/lib/mtg_search_parser/parsed/artist.rb +7 -0
  21. data/lib/mtg_search_parser/parsed/banned.rb +7 -0
  22. data/lib/mtg_search_parser/parsed/base.rb +15 -0
  23. data/lib/mtg_search_parser/parsed/card_type.rb +7 -0
  24. data/lib/mtg_search_parser/parsed/color_identity.rb +7 -0
  25. data/lib/mtg_search_parser/parsed/edition.rb +7 -0
  26. data/lib/mtg_search_parser/parsed/exact_color.rb +7 -0
  27. data/lib/mtg_search_parser/parsed/exact_name.rb +7 -0
  28. data/lib/mtg_search_parser/parsed/language.rb +7 -0
  29. data/lib/mtg_search_parser/parsed/legal.rb +7 -0
  30. data/lib/mtg_search_parser/parsed/mana_cost.rb +18 -0
  31. data/lib/mtg_search_parser/parsed/name.rb +7 -0
  32. data/lib/mtg_search_parser/parsed/power_tough_compare.rb +20 -0
  33. data/lib/mtg_search_parser/parsed/quality.rb +7 -0
  34. data/lib/mtg_search_parser/parsed/rarity.rb +7 -0
  35. data/lib/mtg_search_parser/parsed/release_year.rb +18 -0
  36. data/lib/mtg_search_parser/parsed/rules_text.rb +7 -0
  37. data/lib/mtg_search_parser/parser.rb +60 -0
  38. data/lib/mtg_search_parser/version.rb +3 -0
  39. data/mtg_search_parser.gemspec +23 -0
  40. data/spec/lexer_spec.rb +94 -0
  41. data/spec/mtg_search_parser_spec.rb +62 -0
  42. data/spec/parser_spec.rb +256 -0
  43. data/spec/spec_helper.rb +80 -0
  44. metadata +132 -0
@@ -0,0 +1,13 @@
1
+ module MtgSearchParser
2
+ class OrGroup
3
+ attr_reader :tokens
4
+
5
+ def initialize(tokens)
6
+ @tokens = tokens
7
+ end
8
+
9
+ def ==(o)
10
+ o.class == self.class && o.tokens == tokens
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class AnyColor < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Artist < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Banned < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Base
4
+ attr_reader :contents
5
+
6
+ def initialize(contents)
7
+ @contents = contents
8
+ end
9
+
10
+ def ==(o)
11
+ o.class == self.class && o.contents == contents
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class CardType < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class ColorIdentity < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Edition < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class ExactColor < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class ExactName < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Language < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Legal < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class ManaCost < Base
4
+ attr_reader :operator, :mana_cost
5
+
6
+ def initialize(operator, mana_cost)
7
+ @operator = operator
8
+ @mana_cost = mana_cost
9
+ end
10
+
11
+ def ==(o)
12
+ o.class == self.class &&
13
+ o.operator == operator &&
14
+ o.mana_cost == mana_cost
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Name < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class PowerToughCompare < Base
4
+ attr_reader :left, :op, :right
5
+
6
+ def initialize(left, op, right)
7
+ @left = left
8
+ @op = op
9
+ @right = right
10
+ end
11
+
12
+ def ==(o)
13
+ o.class == self.class &&
14
+ o.left == left &&
15
+ o.op == op &&
16
+ o.right == right
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Quality < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class Rarity < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class ReleaseYear < Base
4
+ attr_reader :operator, :year
5
+
6
+ def initialize(operator, year)
7
+ @operator = operator
8
+ @year = year
9
+ end
10
+
11
+ def ==(o)
12
+ o.class == self.class &&
13
+ o.operator == operator &&
14
+ o.year == year
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module MtgSearchParser
2
+ module Parsed
3
+ class RulesText < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,60 @@
1
+ module MtgSearchParser
2
+ class Parser
3
+ def parse(contents)
4
+ case contents
5
+
6
+ when /\Ac!([wubrgmlc]+)\z/i
7
+ MtgSearchParser::Parsed::ExactColor.new($1)
8
+
9
+ when /\Ac:([wubrgmlc]+)\z/i
10
+ MtgSearchParser::Parsed::AnyColor.new($1)
11
+
12
+ when /\Aci:([wubrgmlc]+)\z/i
13
+ MtgSearchParser::Parsed::ColorIdentity.new($1)
14
+
15
+ when /\At:"([^"]+)"\z/i, /\At:(.+)\z/i
16
+ MtgSearchParser::Parsed::CardType.new($1)
17
+
18
+ when /\Ao:"?([^"]+)"?\z/i
19
+ MtgSearchParser::Parsed::RulesText.new($1)
20
+
21
+ when /\Amana(=|>=|<=|>|<)(.+)\z/i
22
+ MtgSearchParser::Parsed::ManaCost.new($1, $2)
23
+
24
+ when /\A(pow|tou|cmc)(=|>=|<=|>|<)(pow|tou|cmc|\*|[0-9]+)\z/i
25
+ MtgSearchParser::Parsed::PowerToughCompare.new($1, $2, $3)
26
+
27
+ when /\Ar:(mythic|uncommon|common|rare|promo)\z/i
28
+ MtgSearchParser::Parsed::Rarity.new($1)
29
+
30
+ when /\Aa:"(.+)"\z/i, /\Aa:(.+)\z/i
31
+ MtgSearchParser::Parsed::Artist.new($1)
32
+
33
+ when /\Af:(standard|block|extended|vintage|classic|legacy|modern|commander)\z/i
34
+ MtgSearchParser::Parsed::Legal.new($1)
35
+
36
+ when /\Abanned:(standard|block|extended|vintage|classic|legacy|modern|commander)\z/i
37
+ MtgSearchParser::Parsed::Banned.new($1)
38
+
39
+ when /\Ae:(.+)\z/i
40
+ MtgSearchParser::Parsed::Edition.new($1)
41
+
42
+ when /\Al:(.+)\z/i
43
+ MtgSearchParser::Parsed::Language.new($1)
44
+
45
+ when /\Ayear(=|>=|<=|>|<)(.+)\z/i
46
+ MtgSearchParser::Parsed::ReleaseYear.new($1, $2)
47
+
48
+ when /\Ais:(.+)\z/i
49
+ MtgSearchParser::Parsed::Quality.new($1)
50
+
51
+ when /\A!(.+)\z/
52
+ MtgSearchParser::Parsed::ExactName.new($1)
53
+ when /\A"([^"]+)"\z/
54
+ MtgSearchParser::Parsed::Name.new($1)
55
+ else
56
+ MtgSearchParser::Parsed::Name.new(contents)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module MtgSearchParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mtg_search_parser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mtg_search_parser"
8
+ spec.version = MtgSearchParser::VERSION
9
+ spec.authors = ["Donald Plummer"]
10
+ spec.email = ["donald.plummer@gmail.com"]
11
+ spec.summary = %q{A ruby implementation of a MTG search string parser.}
12
+ spec.homepage = "https://github.com/dplummer/mtg_search_parser"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ module MtgSearchParser
4
+ describe Lexer do
5
+ it "lexes a word" do
6
+ expect(subject.lex("word")).to eq([Nodes::Query.new("word")])
7
+ end
8
+
9
+ it "splits two words" do
10
+ expect(subject.lex("word foo")).
11
+ to eq([Nodes::Query.new("word"), Nodes::Query.new("foo")])
12
+ end
13
+
14
+ it "keeps characters in words" do
15
+ expect(subject.lex("foo-bar cat")).
16
+ to eq([Nodes::Query.new("foo-bar"), Nodes::Query.new("cat")])
17
+ end
18
+
19
+ it "allows apostrophes" do
20
+ expect(subject.lex("foo's bar")).
21
+ to eq([Nodes::Query.new("foo's"), Nodes::Query.new("bar")])
22
+ end
23
+
24
+ it "keeps : in the word" do
25
+ expect(subject.lex("foo:bar cat")).
26
+ to eq([Nodes::Query.new("foo:bar"), Nodes::Query.new("cat")])
27
+ end
28
+
29
+ it "keeps ! in the word" do
30
+ expect(subject.lex("foo!bar cat")).
31
+ to eq([Nodes::Query.new("foo!bar"), Nodes::Query.new("cat")])
32
+ end
33
+
34
+ it "keeps it all together if the word starts with a bang" do
35
+ expect(subject.lex("!foo bar baz")).
36
+ to eq([Nodes::Query.new("!foo bar baz")])
37
+ end
38
+
39
+ it "keeps tokens together with quotes" do
40
+ expect(subject.lex('"foo bar" baz')).
41
+ to eq([Nodes::Query.new('"foo bar"'), Nodes::Query.new("baz")])
42
+ end
43
+
44
+ it "allows quotes in the middle of the term" do
45
+ expect(subject.lex('o:"foo bar" baz')).
46
+ to eq([Nodes::Query.new('o:"foo bar"'), Nodes::Query.new("baz")])
47
+ end
48
+
49
+ it "recognizes left and right parens" do
50
+ expect(subject.lex("(foo)")).
51
+ to eq([Nodes::LeftParen.new, Nodes::Query.new("foo"), Nodes::RightParen.new])
52
+ end
53
+
54
+ it "recognizes left and right parens ignoring spaces" do
55
+ expect(subject.lex(" ( foo ) ")).
56
+ to eq([Nodes::LeftParen.new, Nodes::Query.new("foo"), Nodes::RightParen.new])
57
+ end
58
+
59
+ it "parses AND as an AndNode" do
60
+ expect(subject.lex("foo AND bar")).
61
+ to eq([Nodes::Query.new("foo"), Nodes::And.new, Nodes::Query.new("bar")])
62
+ end
63
+
64
+ it "parses and as an AndNode" do
65
+ expect(subject.lex("foo and bar")).
66
+ to eq([Nodes::Query.new("foo"), Nodes::And.new, Nodes::Query.new("bar")])
67
+ end
68
+
69
+ it "parses OR as an OrNode" do
70
+ expect(subject.lex("foo OR bar")).
71
+ to eq([Nodes::Query.new("foo"), Nodes::Or.new, Nodes::Query.new("bar")])
72
+ end
73
+
74
+ it "parses or as an OrNode" do
75
+ expect(subject.lex("foo or bar")).
76
+ to eq([Nodes::Query.new("foo"), Nodes::Or.new, Nodes::Query.new("bar")])
77
+ end
78
+
79
+ it "parses NOT as an NotNode" do
80
+ expect(subject.lex("foo NOT bar")).
81
+ to eq([Nodes::Query.new("foo"), Nodes::Not.new, Nodes::Query.new("bar")])
82
+ end
83
+
84
+ it "parses not as an NotNode" do
85
+ expect(subject.lex("foo not bar")).
86
+ to eq([Nodes::Query.new("foo"), Nodes::Not.new, Nodes::Query.new("bar")])
87
+ end
88
+
89
+ it "parses - as an NotNode" do
90
+ expect(subject.lex("foo -bar")).
91
+ to eq([Nodes::Query.new("foo"), Nodes::Not.new, Nodes::Query.new("bar")])
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe MtgSearchParser do
4
+ describe ".parse" do
5
+ it "parses a big long string of lots of tokens" do
6
+ expect(MtgSearchParser.parse("\"Foo Bar\" o:Rule t:Type c:WU mana>=3G "\
7
+ "pow<=10 tou=5 cmc>2 f:Modern a:\"Hank Hill\" "\
8
+ "e:avr year=1999")).
9
+ to eq([
10
+ MtgSearchParser::Parsed::Name.new("Foo Bar"),
11
+ MtgSearchParser::Parsed::RulesText.new("Rule"),
12
+ MtgSearchParser::Parsed::CardType.new("Type"),
13
+ MtgSearchParser::Parsed::AnyColor.new("WU"),
14
+ MtgSearchParser::Parsed::ManaCost.new(">=", "3G"),
15
+ MtgSearchParser::Parsed::PowerToughCompare.new("pow", "<=", "10"),
16
+ MtgSearchParser::Parsed::PowerToughCompare.new("tou", "=", "5"),
17
+ MtgSearchParser::Parsed::PowerToughCompare.new("cmc", ">", "2"),
18
+ MtgSearchParser::Parsed::Legal.new("Modern"),
19
+ MtgSearchParser::Parsed::Artist.new("Hank Hill"),
20
+ MtgSearchParser::Parsed::Edition.new("avr"),
21
+ MtgSearchParser::Parsed::ReleaseYear.new("=", "1999"),
22
+ ])
23
+ end
24
+
25
+ it "groups items OR'd" do
26
+ expect(MtgSearchParser.parse("t:angel OR t:demon")).
27
+ to eq([
28
+ MtgSearchParser::OrGroup.new([
29
+ MtgSearchParser::Parsed::CardType.new("angel"),
30
+ MtgSearchParser::Parsed::CardType.new("demon"),
31
+ ])
32
+ ])
33
+ end
34
+
35
+ it "not" do
36
+ expect(MtgSearchParser.parse("NOT t:angel -t:demon")).
37
+ to eq([
38
+ MtgSearchParser::NotGroup.new(
39
+ [MtgSearchParser::Parsed::CardType.new("angel")]),
40
+ MtgSearchParser::NotGroup.new(
41
+ [MtgSearchParser::Parsed::CardType.new("demon")])
42
+ ])
43
+ end
44
+
45
+ it "parenthesis" do
46
+ expect(MtgSearchParser.parse("t:demon NOT (t:legendary t:artifact)")).
47
+ to eq([
48
+ MtgSearchParser::Parsed::CardType.new("demon"),
49
+ MtgSearchParser::NotGroup.new([MtgSearchParser::Parsed::CardType.new("legendary"),
50
+ MtgSearchParser::Parsed::CardType.new("artifact")]),
51
+ ])
52
+ end
53
+
54
+ it "strips unneccessary parenthesis" do
55
+ expect(MtgSearchParser.parse("(((t:legendary t:artifact)))")).
56
+ to eq([
57
+ MtgSearchParser::Parsed::CardType.new("legendary"),
58
+ MtgSearchParser::Parsed::CardType.new("artifact")
59
+ ])
60
+ end
61
+ end
62
+ end