mtg_search_parser 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/Rakefile +1 -0
- data/lib/mtg_search_parser.rb +92 -0
- data/lib/mtg_search_parser/lexer.rb +80 -0
- data/lib/mtg_search_parser/nodes/and.rb +6 -0
- data/lib/mtg_search_parser/nodes/left_paren.rb +6 -0
- data/lib/mtg_search_parser/nodes/node.rb +13 -0
- data/lib/mtg_search_parser/nodes/not.rb +6 -0
- data/lib/mtg_search_parser/nodes/or.rb +6 -0
- data/lib/mtg_search_parser/nodes/query.rb +19 -0
- data/lib/mtg_search_parser/nodes/right_paren.rb +6 -0
- data/lib/mtg_search_parser/not_group.rb +13 -0
- data/lib/mtg_search_parser/or_group.rb +13 -0
- data/lib/mtg_search_parser/parsed/any_color.rb +7 -0
- data/lib/mtg_search_parser/parsed/artist.rb +7 -0
- data/lib/mtg_search_parser/parsed/banned.rb +7 -0
- data/lib/mtg_search_parser/parsed/base.rb +15 -0
- data/lib/mtg_search_parser/parsed/card_type.rb +7 -0
- data/lib/mtg_search_parser/parsed/color_identity.rb +7 -0
- data/lib/mtg_search_parser/parsed/edition.rb +7 -0
- data/lib/mtg_search_parser/parsed/exact_color.rb +7 -0
- data/lib/mtg_search_parser/parsed/exact_name.rb +7 -0
- data/lib/mtg_search_parser/parsed/language.rb +7 -0
- data/lib/mtg_search_parser/parsed/legal.rb +7 -0
- data/lib/mtg_search_parser/parsed/mana_cost.rb +18 -0
- data/lib/mtg_search_parser/parsed/name.rb +7 -0
- data/lib/mtg_search_parser/parsed/power_tough_compare.rb +20 -0
- data/lib/mtg_search_parser/parsed/quality.rb +7 -0
- data/lib/mtg_search_parser/parsed/rarity.rb +7 -0
- data/lib/mtg_search_parser/parsed/release_year.rb +18 -0
- data/lib/mtg_search_parser/parsed/rules_text.rb +7 -0
- data/lib/mtg_search_parser/parser.rb +60 -0
- data/lib/mtg_search_parser/version.rb +3 -0
- data/mtg_search_parser.gemspec +23 -0
- data/spec/lexer_spec.rb +94 -0
- data/spec/mtg_search_parser_spec.rb +62 -0
- data/spec/parser_spec.rb +256 -0
- data/spec/spec_helper.rb +80 -0
- metadata +132 -0
@@ -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,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,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,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,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
|
data/spec/lexer_spec.rb
ADDED
@@ -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
|