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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4349da876dab5eb2960f7c0643e33c724c709452
4
+ data.tar.gz: 34776d0a4144c9092dc1c89ef6607ec05b02fd3e
5
+ SHA512:
6
+ metadata.gz: e43996e7cb2d06131489ca3d5ba57f1c4d084fd8ec54d989e1ff38c0b5cbc984c245c74ee4de78aaaede7de63a294367246190f286b9bfeab778087b6bac1d2f
7
+ data.tar.gz: 7f8e114dda062a54e866bfc20f7cae63b1b4e0aaef33c1ead6aea8c9308681014a4b6921eea35e3ac8d6da750119e61b0b7df9fb79482a01a46c0573a5eb04f6
@@ -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/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mtg_search_parser.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -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,6 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class And < Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class LeftParen < Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class Node
4
+ def blank?
5
+ false
6
+ end
7
+
8
+ def ==(o)
9
+ o.class == self.class
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class Not < Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class Or < Node
4
+ end
5
+ end
6
+ 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
@@ -0,0 +1,6 @@
1
+ module MtgSearchParser
2
+ module Nodes
3
+ class RightParen < Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module MtgSearchParser
2
+ class NotGroup
3
+ attr_reader :token
4
+
5
+ def initialize(token)
6
+ @token = token
7
+ end
8
+
9
+ def ==(o)
10
+ o.class == self.class && o.token == token
11
+ end
12
+ end
13
+ end