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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4349da876dab5eb2960f7c0643e33c724c709452
|
4
|
+
data.tar.gz: 34776d0a4144c9092dc1c89ef6607ec05b02fd3e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e43996e7cb2d06131489ca3d5ba57f1c4d084fd8ec54d989e1ff38c0b5cbc984c245c74ee4de78aaaede7de63a294367246190f286b9bfeab778087b6bac1d2f
|
7
|
+
data.tar.gz: 7f8e114dda062a54e866bfc20f7cae63b1b4e0aaef33c1ead6aea8c9308681014a4b6921eea35e3ac8d6da750119e61b0b7df9fb79482a01a46c0573a5eb04f6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Donald Plummer
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# MtgSearchParser
|
2
|
+
|
3
|
+
A parser for search strings for finding Magic the Gathering cards. Inspired by
|
4
|
+
[magiccards.info](http://magiccards.info).
|
5
|
+
|
6
|
+
## Syntax I want to support
|
7
|
+
|
8
|
+
Name:
|
9
|
+
|
10
|
+
* Birds of Paradise
|
11
|
+
* "Birds of Paradise"
|
12
|
+
* !Anger (Match the full name)
|
13
|
+
|
14
|
+
Rules Text (Oracle):
|
15
|
+
|
16
|
+
* o:Flying
|
17
|
+
* o:"First strike"
|
18
|
+
* o:{T} o:"add one mana of any color"
|
19
|
+
* o:"whenever ~ deals combat damage"
|
20
|
+
|
21
|
+
Types (Oracle):
|
22
|
+
|
23
|
+
* t:angel
|
24
|
+
* t:"legendary angel"
|
25
|
+
* t:basic
|
26
|
+
* t:"arcane instant"
|
27
|
+
|
28
|
+
Colors:
|
29
|
+
|
30
|
+
* c:w (Any card that is white)
|
31
|
+
* c:wu (Any card that is white or blue)
|
32
|
+
* c:wum (Any card that is white or blue, and multicolored)
|
33
|
+
* c!w (Cards that are only white)
|
34
|
+
* c!wu (Cards that are only white or blue, or both)
|
35
|
+
* c!wum (Cards that are only white and blue, and multicolored)
|
36
|
+
* c!wubrgm (Cards that are all five colors)
|
37
|
+
* c:m (Any multicolored card)
|
38
|
+
* c:l or c:c (Lands and colorless cards)
|
39
|
+
|
40
|
+
Color Identity:
|
41
|
+
|
42
|
+
* ci:wu (Any card that is white or blue, but does not contain any black, red or green mana symbols)
|
43
|
+
|
44
|
+
Color Indicator:
|
45
|
+
|
46
|
+
* in:wu (Any card that is white or blue according to the color indicator.)
|
47
|
+
|
48
|
+
Mana Cost:
|
49
|
+
|
50
|
+
* mana=3G (Spells that cost exactly 3G, or split cards that can be cast with 3G)
|
51
|
+
* mana>=2WW (Spells that cost at least two white and two colorless mana)
|
52
|
+
* mana<GGGGGG (Spells that can be cast with strictly less than six green mana)
|
53
|
+
* mana>=2RR mana<=6RR (Spells that cost two red mana and between two and six colorless mana)
|
54
|
+
* mana>={2/R}
|
55
|
+
* mana>={W/U}
|
56
|
+
* mana>={UP}
|
57
|
+
|
58
|
+
Power, Toughness, Converted Mana Cost:
|
59
|
+
|
60
|
+
* pow>=8
|
61
|
+
* tou<pow (All combinations are possible)
|
62
|
+
* cmc=7
|
63
|
+
* cmc>=*
|
64
|
+
|
65
|
+
Rarity:
|
66
|
+
|
67
|
+
* r:mythic
|
68
|
+
* Format:
|
69
|
+
* f:standard (or block, extended, vintage, classic, legacy, modern, commander)
|
70
|
+
* banned:extended (or legal, restricted)
|
71
|
+
|
72
|
+
Artist:
|
73
|
+
|
74
|
+
* a:"rk post"
|
75
|
+
|
76
|
+
Edition:
|
77
|
+
|
78
|
+
* e:al (Uses the abbreviations that are listed on the sitemap)
|
79
|
+
* e:al,be (Cards that appear in Alpha or Beta)
|
80
|
+
* e:al+be (Cards that appear in Alpha and Beta)
|
81
|
+
* e:al,be -e:al+be (Cards that appear in Alpha or Beta but not in both editions)
|
82
|
+
* year<=1995 (Cards printed in 1995 and earlier)
|
83
|
+
|
84
|
+
Is:
|
85
|
+
|
86
|
+
* is:split, is:flip
|
87
|
+
* is:vanilla (Creatures with no card text)
|
88
|
+
* is:old, is:new, is:future (Old/new/future card face)
|
89
|
+
* is:timeshifted
|
90
|
+
* is:funny, not:funny (Unglued/Unhinged/Happy Holidays Promos)
|
91
|
+
* is:promo (Promotional cards)
|
92
|
+
* is:promo is:old (Promotional cards with the original card face)
|
93
|
+
* is:permanent, is:spell
|
94
|
+
* is:black-bordered, is:white-bordered, is:silver-bordered
|
95
|
+
* has:foil
|
96
|
+
|
97
|
+
Language:
|
98
|
+
|
99
|
+
* l:de, l:it, l:jp (Uses the abbreviations that are listed on the sitemap)
|
100
|
+
|
101
|
+
## Installation
|
102
|
+
|
103
|
+
Add this line to your application's Gemfile:
|
104
|
+
|
105
|
+
gem 'mtg_search_parser'
|
106
|
+
|
107
|
+
And then execute:
|
108
|
+
|
109
|
+
$ bundle
|
110
|
+
|
111
|
+
Or install it yourself as:
|
112
|
+
|
113
|
+
$ gem install mtg_search_parser
|
114
|
+
|
115
|
+
## Usage
|
116
|
+
|
117
|
+
TODO: Write usage instructions here
|
118
|
+
|
119
|
+
## Contributing
|
120
|
+
|
121
|
+
1. Fork it ( http://github.com/<my-github-username>/mtg_search_parser/fork )
|
122
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
123
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
124
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
125
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module MtgSearchParser
|
2
|
+
end
|
3
|
+
|
4
|
+
require "mtg_search_parser/version"
|
5
|
+
require "mtg_search_parser/nodes/node"
|
6
|
+
require "mtg_search_parser/nodes/and"
|
7
|
+
require "mtg_search_parser/nodes/left_paren"
|
8
|
+
require "mtg_search_parser/nodes/not"
|
9
|
+
require "mtg_search_parser/nodes/or"
|
10
|
+
require "mtg_search_parser/nodes/query"
|
11
|
+
require "mtg_search_parser/nodes/right_paren"
|
12
|
+
|
13
|
+
require "mtg_search_parser/parsed/base"
|
14
|
+
require "mtg_search_parser/parsed/exact_name"
|
15
|
+
require "mtg_search_parser/parsed/name"
|
16
|
+
require "mtg_search_parser/parsed/rules_text"
|
17
|
+
require "mtg_search_parser/parsed/card_type"
|
18
|
+
require "mtg_search_parser/parsed/any_color"
|
19
|
+
require "mtg_search_parser/parsed/exact_color"
|
20
|
+
require "mtg_search_parser/parsed/color_identity"
|
21
|
+
require "mtg_search_parser/parsed/mana_cost"
|
22
|
+
require "mtg_search_parser/parsed/power_tough_compare"
|
23
|
+
require "mtg_search_parser/parsed/legal"
|
24
|
+
require "mtg_search_parser/parsed/banned"
|
25
|
+
require "mtg_search_parser/parsed/artist"
|
26
|
+
require "mtg_search_parser/parsed/rarity"
|
27
|
+
require "mtg_search_parser/parsed/edition"
|
28
|
+
require "mtg_search_parser/parsed/release_year"
|
29
|
+
require "mtg_search_parser/parsed/language"
|
30
|
+
require "mtg_search_parser/parsed/quality"
|
31
|
+
|
32
|
+
require "mtg_search_parser/not_group"
|
33
|
+
require "mtg_search_parser/or_group"
|
34
|
+
|
35
|
+
require "mtg_search_parser/parser"
|
36
|
+
require "mtg_search_parser/lexer"
|
37
|
+
|
38
|
+
|
39
|
+
module MtgSearchParser
|
40
|
+
def self.parse(string, max_count = Float::INFINITY)
|
41
|
+
tokens = Lexer.new.lex(string)
|
42
|
+
ParseExecutor.new.parse(tokens)
|
43
|
+
end
|
44
|
+
|
45
|
+
class ParseExecutor
|
46
|
+
attr_reader :parser
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@parser = MtgSearchParser::Parser.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse(tokens, max_count = Float::INFINITY)
|
53
|
+
token_count = 0
|
54
|
+
parsed = []
|
55
|
+
|
56
|
+
while token = tokens.shift
|
57
|
+
token_count += 1
|
58
|
+
|
59
|
+
case token
|
60
|
+
when MtgSearchParser::Nodes::RightParen
|
61
|
+
break
|
62
|
+
when MtgSearchParser::Nodes::LeftParen
|
63
|
+
parsed << parse(tokens)
|
64
|
+
when MtgSearchParser::Nodes::Query
|
65
|
+
parsed << parser.parse(token.contents)
|
66
|
+
when MtgSearchParser::Nodes::Or
|
67
|
+
parsed << MtgSearchParser::OrGroup.new([parsed.pop] + parse(tokens, 1))
|
68
|
+
when MtgSearchParser::Nodes::Not
|
69
|
+
parsed << MtgSearchParser::NotGroup.new(parse(tokens, 1))
|
70
|
+
else
|
71
|
+
parsed << token
|
72
|
+
end
|
73
|
+
|
74
|
+
if token_count == max_count
|
75
|
+
break
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
smart_flatten(parsed)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def smart_flatten(array)
|
85
|
+
if array.length == 1 && array.first.is_a?(Array)
|
86
|
+
array.first
|
87
|
+
else
|
88
|
+
array
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module MtgSearchParser
|
2
|
+
class Lexer
|
3
|
+
def lex(string)
|
4
|
+
state = :blank
|
5
|
+
current_letters = ""
|
6
|
+
completed_tokens = []
|
7
|
+
|
8
|
+
string.each_char do |letter|
|
9
|
+
case state
|
10
|
+
when :blank
|
11
|
+
if !blank?(letter)
|
12
|
+
case letter
|
13
|
+
when '('
|
14
|
+
completed_tokens << Nodes::LeftParen.new
|
15
|
+
when ')'
|
16
|
+
completed_tokens << Nodes::RightParen.new
|
17
|
+
when '-'
|
18
|
+
completed_tokens << Nodes::Not.new
|
19
|
+
else
|
20
|
+
current_letters << letter
|
21
|
+
if letter == '"'
|
22
|
+
state = :quoted_term
|
23
|
+
elsif letter == '!'
|
24
|
+
state = :exact_name
|
25
|
+
else
|
26
|
+
state = :term
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
when :exact_name
|
31
|
+
current_letters << letter
|
32
|
+
when :quoted_term
|
33
|
+
current_letters << letter
|
34
|
+
if letter == '"'
|
35
|
+
completed_tokens << Nodes::Query.new(current_letters)
|
36
|
+
state = :blank
|
37
|
+
current_letters = ""
|
38
|
+
end
|
39
|
+
when :term
|
40
|
+
case letter
|
41
|
+
when '"'
|
42
|
+
current_letters << letter
|
43
|
+
state = :quoted_term
|
44
|
+
when /^\s*$/
|
45
|
+
completed_tokens << complete_node(current_letters)
|
46
|
+
state = :blank
|
47
|
+
current_letters = ""
|
48
|
+
when ')'
|
49
|
+
completed_tokens << complete_node(current_letters) << Nodes::RightParen.new
|
50
|
+
state = :blank
|
51
|
+
current_letters = ""
|
52
|
+
else
|
53
|
+
current_letters << letter
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
completed_tokens << Nodes::Query.new(current_letters)
|
59
|
+
|
60
|
+
completed_tokens.reject(&:blank?)
|
61
|
+
end
|
62
|
+
|
63
|
+
def blank?(letter)
|
64
|
+
letter =~ /^\s*$/
|
65
|
+
end
|
66
|
+
|
67
|
+
def complete_node(letters)
|
68
|
+
case letters.downcase
|
69
|
+
when 'and'
|
70
|
+
Nodes::And.new
|
71
|
+
when 'or'
|
72
|
+
Nodes::Or.new
|
73
|
+
when 'not'
|
74
|
+
Nodes::Not.new
|
75
|
+
else
|
76
|
+
Nodes::Query.new(letters)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MtgSearchParser
|
2
|
+
module Nodes
|
3
|
+
class Query < Node
|
4
|
+
attr_reader :contents
|
5
|
+
|
6
|
+
def initialize(contents)
|
7
|
+
@contents = contents
|
8
|
+
end
|
9
|
+
|
10
|
+
def blank?
|
11
|
+
contents =~ /^\s*$/
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(o)
|
15
|
+
super && o.contents == contents
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|