mtg_search_parser 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.
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