parspec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Y2RjNGMzNzc3MDhmMDU5OGJjMTQyOGIyNWJjNjNmODc5NmY0ZGVmMQ==
5
+ data.tar.gz: !binary |-
6
+ ZjJmOTAzZDM3OGMyNmYxMDkyZGNlODFiMjllNGFhZTVlYjEyYzY0Nw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTViMWMxNTJmYmRhMjljZDZkNGEzZjc1OTlhNmVmOTA2ODYwNWU2Y2I4N2My
10
+ ZjdmMDA1NWZhODQ1ZDM0YWNmMTkwODg2MGNiMjYyODNhNTlmY2ZhZmRlNzlj
11
+ ZjU4MGNhOTY1ODcyMDEyNzRlN2E5MmM0NjA2NWE4Y2E5YjNiNGQ=
12
+ data.tar.gz: !binary |-
13
+ MjBhMThmMWNiNjY4NzcxMmEyNGIxMzM3ODhlZDFhMmU1ZjIzZjE4ODlmYjE3
14
+ YTYxOGY4Y2MxN2MwNzA2ZTEyNzJjMmEyZTllMTQzYWRmYjY4ODUzMDdlM2E0
15
+ MjU0ODEyMWM5ZTRhZDkwMTIxYWFhNGFkMzBjMTZkZjgyYmVjNzI=
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parspec.gemspec
4
+ gemspec
5
+
6
+ group :test, :development do
7
+ gem 'guard-rspec'
8
+ gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
9
+ end
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+
3
+ format = 'doc' # 'doc' for more verbose, 'progress' for less
4
+ tags = %w[ ] #
5
+
6
+ guard 'rspec',
7
+ cmd: "bundle exec rspec --format #{format} #{tags.map{|tag| "--tag #{tag}"}.join(' ')}",
8
+ all_after_pass: false do
9
+ watch(%r{^spec/.+_spec\.rb$})
10
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch('lib/parspec.rb') { 'spec' }
12
+ watch('spec/spec_helper.rb') { 'spec' }
13
+ watch(/spec\/support\/(.+)\.rb/) { 'spec' }
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Marcel Otto
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,238 @@
1
+ # Parspec
2
+
3
+ A [gUnit](https://theantlrguy.atlassian.net/wiki/display/ANTLR3/gUnit+-+Grammar+Unit+Testing)-like specification language for [Parslet](http://kschiess.github.io/parslet/) parsers and transformers, which translates to [RSpec](http://rspec.info/).
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'parspec', group: :test
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install with gem:
19
+
20
+ $ gem install parspec
21
+
22
+
23
+ ## Specification language
24
+
25
+ ### Example
26
+
27
+ The following example shows parts of the [TOML spec](https://github.com/zerowidth/toml-parslet/blob/master/spec/toml/parser_spec.rb) translated to Parspec:
28
+
29
+ parser TOML::Parser
30
+
31
+ value:
32
+ "120381" OK
33
+ "0181" FAIL
34
+ "3.14159" OK
35
+ ".1" FAIL
36
+ "true" OK
37
+ "truefalse" FAIL
38
+ "1979-05-27T07:32:00Z" OK
39
+ "1979l05-27 07:32:00" FAIL
40
+ "\"hello world\"" OK
41
+ "\"hello\nworld\"" FAIL
42
+ "\"hello/world\"" FAIL
43
+
44
+ "1234" -> ":integer => '1234'"
45
+ "-0.123" -> ":float => '-0.123'"
46
+ "true" -> ":boolean => 'true'"
47
+ "1979-05-27T07:32:00Z" -> ":datetime => '1979-05-27T07:32:00Z'"
48
+ "\"hello world\"" -> ":string => 'hello world'"
49
+ "\"hello\nworld\"" -> ":string => \"hello\nworld\""
50
+
51
+ array:
52
+ "[]" OK
53
+ "[1]" OK
54
+ "[0.1, -0.1, 3.14159]" OK
55
+ "[ true, false, true, true ]" OK
56
+ "[1979-05-27T07:32:00Z]" OK # [2013-02-24T17:26:21Z]
57
+ "[\n1\n,\n2\n]" OK
58
+ "[\n\n\t1 , 2, 3\\t,4\n]" OK
59
+ "[1, 2, \"three\"]" FAIL
60
+ "[1,2,]" OK
61
+ "[1,2\n,\\t]" OK
62
+
63
+ "[1,2]" -> ":array => [ {:integer => '1'}, {:integer => '2'}]"
64
+ "[]" -> ":array => '[]'"
65
+ "[ [1,2] ]"
66
+ -> ":array => [
67
+ {:array => [ {:integer => '1'}, {:integer => '2'}]}
68
+ ]"
69
+
70
+ key:
71
+ "foobar" OK
72
+ "lolwhat.noWAY" OK
73
+ "no white\\tspace" FAIL
74
+ "noequal=thing" FAIL
75
+
76
+ assignment:
77
+ "key=3.14" OK
78
+ "key = 10" OK
79
+ "key = true" OK
80
+ "key = \"value\"" OK
81
+ "#comment=1" FAIL
82
+ "thing = 1" -> ":key => 'thing', :value => {:integer => '1'}"
83
+
84
+
85
+ ### Description
86
+
87
+ #### Header
88
+
89
+ A Parspec specification starts with a definition of the subject:
90
+
91
+ <type> <instantiation expression>
92
+
93
+ There are two types of specifications: `parser` and `transformer`. A parser specification describes a `Parslet::Parser`, a transformer specification describes a `Parslet::Transform`. Syntactically these types of specifications are equal, but the generated RSpec descriptions will differ.
94
+
95
+ The instantiation expression is used to create a RSpec test subject. It usually consists of a constant for a `Parslet::Parser` or `Parslet::Transform` class, according to the type of specification, but can be any valid Ruby expression, which responds to `new` with a `Parslet::Parser` or `Parslet::Tranform` instance.
96
+
97
+
98
+ #### Rule examples
99
+
100
+ After the definition, a series of examples for the grammar rules follows. Rules start with a rule name followed by a colon.
101
+
102
+ There are two types of examples: simple validations and mapping examples.
103
+
104
+
105
+ ##### Validations
106
+
107
+ A validation is a string in double-quotes followed either by the keyword `OK` or `FAIL`, according to the expected outcome of parsing the given string under the given rule. Currently, it is supported in parser specifications only.
108
+
109
+ For example, the following validation:
110
+
111
+ some_rule:
112
+ "some input" OK
113
+ "another input" FAIL
114
+
115
+ will translate to this RSpec description:
116
+
117
+ ```ruby
118
+ context 'some_rule parsing' do
119
+ subject { parser.some_rule }
120
+
121
+ it { should parse 'some input' }
122
+ it { should_not parse 'another input' }
123
+ end
124
+ ```
125
+
126
+
127
+ ##### Mapping examples
128
+
129
+ Mapping examples describe the input-output-behaviour of a rule. Syntactically they consist of two strings separated by `->`. Since the semantics of parser and transformer specifications differ, let's discuss them separately, starting with the parser case:
130
+
131
+ While the input string on the left side is simply some sample text as in a validity example, the output string on the right must contain a valid Ruby expression, which should evaluate to the expected outcome of the respective rule parsing.
132
+
133
+ For example, the following mapping:
134
+
135
+ some_rule:
136
+ "some input" -> "42"
137
+ "another input" -> "{ foo: 'bar' }"
138
+
139
+ will be translated to the following RSpec parser specification:
140
+
141
+ ```ruby
142
+ context 'some_rule parsing' do
143
+ subject { parser.some_rule }
144
+
145
+ it "should parse 'some input' to 42" do
146
+ expect(subject.parse('some input')).to eq 42
147
+ end
148
+
149
+ it "should parse 'another input' to { foo: 'bar' }" do
150
+ expect(subject.parse('another input')).to eq { foo: 'bar' }
151
+ end
152
+ end
153
+ ```
154
+
155
+ In the case of a transformer specification, both sides must contain Ruby expressions.
156
+
157
+
158
+ #### Shared examples
159
+
160
+ The examples of a rule can be reused inside other rules with the `include` keyword:
161
+
162
+ some_rule:
163
+ "some input" -> "42"
164
+ "another input" -> "{ foo: 'bar' }"
165
+
166
+ another_rule:
167
+ include some_rule
168
+
169
+
170
+ #### String escaping
171
+
172
+ Parspec strings in general support the following escape sequences: `\t`, `\n`, `\r`, `\"`, `\\`.
173
+
174
+
175
+ #### Comments
176
+
177
+ One-line comments are supported in the `#`-style.
178
+
179
+
180
+ ## Usage
181
+
182
+ ### Command-line interface
183
+
184
+ Parspec comes with a command-line interface through the `parspec` command.
185
+
186
+ For a full description of the available parameters, run:
187
+
188
+ $ parspec --help
189
+ Usage: parspec [options] PARSPEC_FILE
190
+ -h, --help Display this information
191
+ -v, --version Print version information
192
+ -s, --stdout Print the translation to stdout only
193
+ -o, --out OUTPUT_FILE Path where translated RSpec file should be stored
194
+ -b, --beside-input Put the output file into the same directory as the input
195
+ -e, --header HEADER A block of code to be put in front of the translation
196
+ --no-debug-parse Don't print the whole Parslet ascii_tree on errors
197
+
198
+ Unless specified otherwise, the default header is:
199
+
200
+ ```ruby
201
+ # coding: utf-8
202
+ require 'spec_helper'
203
+ require 'parslet/convenience'
204
+ require 'parslet/rig/rspec'
205
+ ```
206
+
207
+
208
+ ### `load_parspec`
209
+
210
+ You can use the command-line interface to integrate Parspec in your testing tool chain, e.g. via Rake or Guard.
211
+
212
+ But you can also load your Parspec spec from a normal Ruby file in your spec directory with the `load_parspec` command:
213
+
214
+ ```ruby
215
+ require 'spec_helper'
216
+ require 'parspec'
217
+ require 'my_parser'
218
+
219
+ load_parspec __FILE__
220
+ ```
221
+
222
+ If the `load_parspec` command gets a filename with the extension `.rb`, it looks for a file with the same name, but the extension `.parspec`. For example, if the former Ruby file would be at `spec/my_parser/my_parser_spec.rb`, the `load_parspec` command would try to load a Parspec spec from a file `spec/my_parser/my_parser_spec.parspec`.
223
+
224
+
225
+ Note: This feature is currently implemented via `eval`, till I find a way to include specs from other RSpec files or another alternative. If you have any advice, please share it in issue #1.
226
+
227
+
228
+ ## Contributing
229
+
230
+ 1. Fork it ( https://github.com/marcelotto/parspec/fork )
231
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
232
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
233
+ 4. Push to the branch (`git push origin my-new-feature`)
234
+ 5. Create a new Pull Request
235
+
236
+ ## Author
237
+
238
+ - Marcel Otto
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
data/bin/parspec ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'parspec'
3
+ Parspec::CLI.run
@@ -0,0 +1,14 @@
1
+ parser Parspec::Parser
2
+
3
+ string:
4
+ "\"test\"" OK
5
+ "\"test
6
+ on multiple lines\"" OK
7
+ "test" FAIL
8
+
9
+ "\"test\"" -> "string: 'test'"
10
+ # TODO: "\"\\\"test\\\"\"" -> "string: '\"test\"'"
11
+
12
+ validity_example:
13
+ "\"test\" OK" OK
14
+ "\"test\" FAIL" OK
@@ -0,0 +1,66 @@
1
+ parser TOML::Parser
2
+
3
+ value:
4
+ "1" OK
5
+ "-123" OK
6
+ "120381" OK
7
+ "181" OK
8
+ "0181" FAIL
9
+ "0.1" OK
10
+ "3.14159" OK
11
+ "-0.00001" OK
12
+ ".1" FAIL
13
+ "true" OK
14
+ "false" OK
15
+ "truefalse" FAIL
16
+ "1979-05-27T07:32:00Z" OK
17
+ "2013-02-24T17:26:21Z" OK
18
+ "1979l05-27 07:32:00" FAIL
19
+ "\"\"" OK
20
+ "\"hello world\"" OK
21
+ "\"no way, jos\\u00E9\"" OK
22
+ "\"hello\nworld\"" FAIL
23
+ "\"hello/world\"" FAIL
24
+ "\"\\u001F\"" OK
25
+
26
+ "1234" -> ":integer => '1234'"
27
+ "-0.123" -> ":float => '-0.123'"
28
+ "true" -> ":boolean => 'true'"
29
+ "1979-05-27T07:32:00Z" -> ":datetime => '1979-05-27T07:32:00Z'"
30
+ "\"hello world\"" -> ":string => 'hello world'"
31
+ "\"hello\nworld\"" -> ":string => \"hello\nworld\""
32
+
33
+ array:
34
+ "[]" OK
35
+ "[1]" OK
36
+ "[1, 2, 3, 4, 5]" OK
37
+ "[0.1, -0.1, 3.14159]" OK
38
+ "[ true, false, true, true ]" OK
39
+ "[1979-05-27T07:32:00Z]" OK # [2013-02-24T17:26:21Z]
40
+ "[\n1\n,\n2\n]" OK
41
+ "[\n\n\t1 , 2, 3\\t,4\n]" OK
42
+ "[1, 2, \"three\"]" FAIL
43
+ "[1,2,]" OK
44
+ "[1,2\n,\\t]" OK
45
+
46
+ "[1,2]" -> ":array => [ {:integer => '1'}, {:integer => '2'}]"
47
+ "[]" -> ":array => '[]'"
48
+ "[ [1,2] ]"
49
+ -> ":array => [
50
+ {:array => [ {:integer => '1'}, {:integer => '2'}]}
51
+ ]"
52
+
53
+ key:
54
+ "foobar" OK
55
+ "lolwhat.noWAY" OK
56
+ "no white\\tspace" FAIL
57
+ "noequal=thing" FAIL
58
+
59
+ assignment:
60
+ "key=3.14" OK
61
+ "key = 10" OK
62
+ "key = 10.10" OK
63
+ "key = true" OK
64
+ "key = \"value\"" OK
65
+ "#comment=1" FAIL
66
+ "thing = 1" -> ":key => 'thing', :value => {:integer => '1'}"
@@ -0,0 +1,110 @@
1
+ # coding: utf-8
2
+ require 'singleton'
3
+ require 'optparse'
4
+ require 'pathname'
5
+
6
+ module Parspec
7
+ class Cli
8
+ include Singleton
9
+
10
+ def run(options = {})
11
+ @options = options
12
+ parse_command_line!
13
+ init_output_dir
14
+ puts "Translating #@input #{@print_only ? ' ': "to #@output " }..."
15
+ translation = header + Parspec.translate(@input, @options)
16
+ if @print_only
17
+ puts translation
18
+ exit
19
+ end
20
+ File.open(@output, 'w') { |file| file.write(translation) }
21
+ self
22
+ end
23
+
24
+ def header
25
+ @header || <<HEADER
26
+ # coding: utf-8
27
+ require 'spec_helper'
28
+ require 'parslet/convenience'
29
+ require 'parslet/rig/rspec'
30
+
31
+ HEADER
32
+ end
33
+
34
+ private
35
+
36
+ def parse_command_line!
37
+ optparse = OptionParser.new do |opts|
38
+ opts.banner = 'Usage: parspec [options] PARSPEC_FILE'
39
+
40
+ opts.on('-h', '--help', 'Display this information') do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on('-v', '--version', 'Print version information') do
46
+ puts "Parspec #{VERSION}"
47
+ exit
48
+ end
49
+
50
+ opts.on('-s', '--stdout', 'Print the translation to stdout only') do
51
+ @print_only = true
52
+ end
53
+
54
+ opts.on('-o', '--out OUTPUT_FILE', 'Path where translated RSpec file should be stored') do |file|
55
+ @output = file
56
+ end
57
+
58
+ opts.on('-b', '--beside-input', 'Put the output file into the same directory as the input') do
59
+ @beside_input = true
60
+ end
61
+
62
+ opts.on('-e', '--header HEADER', 'A block of code to be put in front of the translation') do |header|
63
+ @header = header
64
+ end
65
+
66
+ opts.on('--no-debug-parse', "Don't print the whole Parslet ascii_tree on errors") do
67
+ @options[:no_debug_parse] = true
68
+ end
69
+
70
+ end
71
+
72
+ optparse.parse!
73
+ if not input = ARGV.shift
74
+ puts 'Error: no input file given'
75
+ puts optparse
76
+ exit(1)
77
+ end
78
+ if not File.exist?(input)
79
+ puts "Error: couldn't find input file #{input}"
80
+ exit(1)
81
+ end
82
+ @input = input
83
+ end
84
+
85
+ def init_output_dir
86
+ default_dir = File.dirname(@input)
87
+ default_basename = File.basename(@input, File.extname(@input)) + '.rb'
88
+
89
+ # no output defined
90
+ @output = if not @output
91
+ File.join (@beside_input ? default_dir : '.'), default_basename
92
+ # only path given
93
+ elsif Dir.exist?(@output) or @output.end_with?(File::SEPARATOR) or
94
+ (File::ALT_SEPARATOR and @output.end_with?(File::ALT_SEPARATOR))
95
+ warn('Ignoring -b, since directory specified') if @beside_input
96
+ File.join @output, default_basename
97
+ # directory and name given
98
+ elsif File.dirname(@output) != '.'
99
+ warn('Ignoring -b, since directory specified') if @beside_input
100
+ @output
101
+ # only name given
102
+ else
103
+ File.join (@beside_input ? default_dir : '.'), @output
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ CLI = Cli.instance
110
+ end
@@ -0,0 +1,76 @@
1
+ module Parspec
2
+ class Parser < Parslet::Parser
3
+ rule(:eof) { any.absent? }
4
+ rule(:space) { match[' \t'].repeat(1) }
5
+ rule(:newline) { match['\n'] }
6
+ rule(:newlines) { newline.repeat(1) | eof }
7
+ rule(:comment) { str('#') >> match_till(:newlines).maybe >> (newline | eof) }
8
+ rule(:ws) { (space | newline | comment).repeat(1) }
9
+ rule(:ws?) { ws.maybe }
10
+
11
+ rule(:special) { match['"\\\\'] }
12
+ rule(:escaped_special) { str("\\") >> match['tnr"\\\\'] }
13
+ rule(:string) do
14
+ str('"') >>
15
+ ((escaped_special | special.absent? >> any).repeat).as(:string) >>
16
+ str('"') >> ws?
17
+ end
18
+
19
+ rule(:rule_name) do
20
+ match['A-Za-z_'] >> match['\w'].repeat >> match['!?'].maybe
21
+ end
22
+
23
+ rule(:validity) { (stri('OK') | stri('FAIL')).as(:status) >> ws? }
24
+ rule(:validity_example) { string.as(:input) >> validity.as(:validity) }
25
+
26
+ rule(:mapping_example) do
27
+ string.as(:input) >> ws? >> str('->') >> ws? >> string.as(:output)
28
+ end
29
+
30
+ rule(:include_example) do
31
+ str('include') >> space >> rule_name.as(:include_rule)
32
+ end
33
+
34
+ rule(:description_example) do
35
+ (validity_example | mapping_example | include_example) >> ws?
36
+ end
37
+ rule(:description_examples) { description_example.repeat }
38
+
39
+ rule(:rule_description) do
40
+ rule_name.as(:rule_name) >> str(':') >> ws? >>
41
+ description_examples.as(:examples)
42
+ end
43
+ rule(:rule_descriptions) { rule_description.repeat }
44
+
45
+ rule(:spec_header) do
46
+ (str('parser') | str('transformer')).as(:type) >> space >>
47
+ match_till { newline | comment }.as(:subject_class) >> ws?
48
+ end
49
+
50
+ rule(:spec) do
51
+ ws? >> (spec_header.as(:header) >> rule_descriptions.as(:rules) | eof)
52
+ end
53
+
54
+ root :spec
55
+
56
+ private
57
+
58
+ def match_till(ending = nil)
59
+ case ending
60
+ when String then (str(ending).absent? >> any).repeat(1)
61
+ when Symbol then (send(ending).absent? >> any).repeat(1)
62
+ when nil
63
+ raise ParserError, 'expected a block' unless block_given?
64
+ (yield.absent? >> any).repeat(1)
65
+ end
66
+ end
67
+
68
+ def stri(str)
69
+ key_chars = str.split(//)
70
+ key_chars
71
+ .collect! { |char| match["#{char.upcase}#{char.downcase}"] }
72
+ .reduce(:>>)
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,50 @@
1
+ module Parspec
2
+ class ParserSpecTransform < Parslet::Transform
3
+
4
+ class << self
5
+ attr_accessor :no_debug_parse
6
+ end
7
+
8
+ rule(input: simple(:input), validity: simple(:valid)) do <<RSPEC_TEMPLATE
9
+ it { should#{ valid ? '' : '_not' } parse '#{input.gsub("'", "\\''")}' }
10
+ RSPEC_TEMPLATE
11
+ end
12
+
13
+ rule(input: simple(:input), output: simple(:output)) do <<RSPEC_TEMPLATE
14
+ it "should parse '#{input.gsub('"', '\\"')}' to #{output.gsub('"', '\\"')}" do
15
+ expect(subject.#{ParserSpecTransform.no_debug_parse ? 'parse' : 'parse_with_debug'}('#{input.gsub("'", "\\''")}')).to eq #{output}
16
+ end
17
+ RSPEC_TEMPLATE
18
+ end
19
+
20
+ rule(include_rule: simple(:rule_name)) do <<RSPEC_TEMPLATE
21
+ it_behaves_like 'every #{rule_name} parsing'
22
+ RSPEC_TEMPLATE
23
+ end
24
+
25
+ rule(rule_name: simple(:rule_name), examples: sequence(:examples)) do <<RSPEC_TEMPLATE
26
+ shared_examples 'every #{rule_name} parsing' do
27
+
28
+ #{examples.join("\n")}
29
+ end
30
+
31
+ context '#{rule_name} parsing' do
32
+ subject { parser.#{rule_name} }
33
+
34
+ it_behaves_like 'every #{rule_name} parsing'
35
+ end
36
+ RSPEC_TEMPLATE
37
+ end
38
+
39
+ rule(header: {type: simple(:type), subject_class: simple(:subject_class)},
40
+ rules: sequence(:rules)) do <<RSPEC_TEMPLATE
41
+ describe #{subject_class} do
42
+ let(:parser) { #{subject_class}.new }
43
+
44
+ #{rules.join("\n")}
45
+ end
46
+ RSPEC_TEMPLATE
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module Parspec
2
+ class SharedTransform < Parslet::Transform
3
+
4
+ rule(status: simple(:status)) { status.to_s.upcase == 'OK' }
5
+ rule(string: sequence(:empty)) { '' }
6
+ rule(string: simple(:string)) do
7
+ string.to_s.gsub(
8
+ /\\[tnr"\/\\]/,
9
+ "\\t" => "\t",
10
+ "\\n" => "\n",
11
+ "\\r" => "\r",
12
+ '\\"' => '"',
13
+ "\\\\" => "\\"
14
+ )
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Parspec
2
+ class TransformerSpecTransform < Parslet::Transform
3
+
4
+ rule(input: simple(:input), validity: simple(:valid)) do
5
+ raise NotImplementedError
6
+ end
7
+
8
+ rule(input: simple(:input), output: simple(:output)) do <<RSPEC_TEMPLATE
9
+ it "should parse '#{input.gsub('"', '\\"')}' to #{output.gsub('"', '\\"')}" do
10
+ expect(transformer.apply(#{input})).to eq #{output}
11
+ end
12
+ RSPEC_TEMPLATE
13
+ end
14
+
15
+ rule(include_rule: simple(:rule_name)) do <<RSPEC_TEMPLATE
16
+ it_behaves_like 'every #{rule_name} transformation'
17
+ RSPEC_TEMPLATE
18
+ end
19
+
20
+ rule(rule_name: simple(:rule_name), examples: sequence(:examples)) do <<RSPEC_TEMPLATE
21
+ shared_examples 'every #{rule_name} transformation' do
22
+
23
+ #{examples.join("\n")}
24
+ end
25
+
26
+ context '#{rule_name} tranformation' do
27
+ it_behaves_like 'every #{rule_name} transformation'
28
+ end
29
+ RSPEC_TEMPLATE
30
+ end
31
+
32
+ rule(header: {type: simple(:type), subject_class: simple(:subject_class)},
33
+ rules: sequence(:rules)) do <<RSPEC_TEMPLATE
34
+ describe #{subject_class} do
35
+ let(:transformer) { #{subject_class}.new }
36
+
37
+ #{rules.join("\n")}
38
+ end
39
+ RSPEC_TEMPLATE
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Parspec
2
+ VERSION = '0.0.1'
3
+ end
data/lib/parspec.rb ADDED
@@ -0,0 +1,79 @@
1
+ # coding: utf-8
2
+ require 'parslet'
3
+ require 'parspec/version'
4
+ require 'parspec/parser'
5
+ require 'parspec/shared_transform'
6
+ require 'parspec/parser_spec_transform'
7
+ require 'parspec/transformer_spec_transform'
8
+ require 'parspec/cli'
9
+
10
+ module Parspec
11
+
12
+ Error = Class.new StandardError
13
+ ParseError = Class.new Error
14
+ TransformError = Class.new Error
15
+
16
+ def self.translate(file_or_str, options = {})
17
+ if File.exist?(file_or_str)
18
+ translate_file(file_or_str, options)
19
+ else
20
+ translate_string(file_or_str, options)
21
+ end
22
+ end
23
+
24
+ def self.translate_file(filename, options = {})
25
+ translate_string(File.read(filename), options)
26
+ end
27
+
28
+ def self.translate_string(str, options = {})
29
+ ParserSpecTransform.no_debug_parse = options[:no_debug_parse]
30
+ tree = Parser.new.parse(str)
31
+ tree = SharedTransform.new.apply(tree)
32
+ translation = Parslet::Transform.new do
33
+ spec = ->(type) {
34
+ { header: {type: type, subject_class: simple(:subject_class) },
35
+ rules: subtree(:rules) } }
36
+ rule(spec['parser']) { ParserSpecTransform.new.apply(tree) }
37
+ rule(spec['transformer']) { TransformerSpecTransform.new.apply(tree) }
38
+ end.apply(tree)
39
+ raise Error, "unexpected translation: #{translation}" unless translation.is_a? String
40
+ translation
41
+ rescue Parslet::ParseFailed => e
42
+ deepest = deepest_cause e.cause
43
+ line, column = deepest.source.line_and_column(deepest.pos)
44
+ raise ParseError, "unexpected input at line #{line} column #{column}"
45
+ end
46
+
47
+ # Internal: helper for finding the deepest cause for a parse error
48
+ def self.deepest_cause(cause)
49
+ if cause.children.any?
50
+ deepest_cause(cause.children.first)
51
+ else
52
+ cause
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ def load_parspec(file, options = {})
59
+ parspec_file = case File.extname(file)
60
+ when '.rb' then file.sub(/rb$/, 'parspec')
61
+ when '' then "#{file}.parspec"
62
+ end
63
+
64
+ if not File.exists?(parspec_file)
65
+ # TODO: try to find the file in the load_path
66
+ raise LoadError, "cannot load such file -- #{parspec_file}"
67
+ end
68
+
69
+ require 'parslet/convenience'
70
+ require 'parslet/rig/rspec'
71
+
72
+ parspec = Parspec.translate_file(parspec_file, options)
73
+ # TODO: These are not detected as examples by RSpec ...
74
+ #tmp_file = Tempfile.new([File.basename(parspec_file), '.rb'])
75
+ #tmp_file.write(parspec)
76
+ #require tmp_file.path
77
+ # only as a fallback or if options[:force_eval]
78
+ eval(parspec)
79
+ end
data/parspec.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'parspec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'parspec'
8
+ spec.version = Parspec::VERSION
9
+ spec.authors = ['Marcel Otto']
10
+ spec.email = ['marcelotto.de@gmail.com']
11
+ spec.summary = %q{Testing of Parslet grammars and transformations}
12
+ spec.description = %q{A specification language for Parslet grammars and transformers, which gets translated to RSpec.}
13
+ spec.homepage = 'http://github.com/marcelotto/parspec'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'parslet'
22
+ spec.add_dependency 'rspec', '~> 3.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ end
@@ -0,0 +1,324 @@
1
+ require 'spec_helper'
2
+ require 'parslet/rig/rspec'
3
+
4
+ describe Parspec::Parser do
5
+ let(:parser) { Parspec::Parser.new }
6
+
7
+ context 'string parsing' do
8
+ let(:string_parser) { parser.string }
9
+ subject { string_parser }
10
+ it { should parse('"test"')}
11
+ it { should parse("\"test\n on multiple lines\"")}
12
+
13
+ it { should_not parse('test')}
14
+
15
+ it 'should interpret the empty string correctly' do
16
+ expect(string_parser.parse('""')).to eq string: [] # TODO: should be ''
17
+ end
18
+
19
+ it 'should preserve escaped double-quotes' do
20
+ expect(string_parser.parse('"\\"test\\""')).to eq string: '\\"test\\"'
21
+ end
22
+
23
+ it 'should preserve escape-sequences' do
24
+ expect(string_parser.parse('"\\n"')).to eq string: '\n'
25
+ end
26
+
27
+ it 'should preserve special characters' do
28
+ expect(string_parser.parse("\"test with:\n\tspecial characters\n\tesp. \\\", \\\\\""))
29
+ .to eq string: "test with:\n\tspecial characters\n\tesp. \\\", \\\\"
30
+ end
31
+ end
32
+
33
+ context 'validity example parsing' do
34
+ let(:validity_example_parser) { parser.validity_example }
35
+ subject { validity_example_parser }
36
+ it { should parse('"test" OK') }
37
+ it { should parse('"test" ok') }
38
+ it { should parse('"test" FAIL') }
39
+ it { should parse('"test" Fail') }
40
+ it { should parse("\"test\n on multiple lines\" OK") }
41
+
42
+ it { should_not parse('"test"') }
43
+ it { should_not parse('"test" OTHER') }
44
+ it { should_not parse('"test" -> OTHER') }
45
+
46
+ it 'should be parsed to {input: {string: "example"}, validity: {status: "OK"}}, when validity is OK' do
47
+ expect(validity_example_parser.parse('"test" OK'))
48
+ .to eq input: { string: 'test' }, validity: {status: 'OK'}
49
+ expect(validity_example_parser.parse('"test" OK '))
50
+ .to eq input: { string: 'test' }, validity: {status: 'OK'}
51
+ end
52
+
53
+ it 'should be parsed to {input: {string: "example"}, validity: {status: "FAIL"}}, when validity is FAIL' do
54
+ expect(validity_example_parser.parse('"test" FAIL'))
55
+ .to eq input: { string: 'test' }, validity: {status: 'FAIL'}
56
+ end
57
+ end
58
+
59
+ context 'mapping example parsing' do
60
+ let(:mapping_example_parser) { parser.mapping_example }
61
+ subject { mapping_example_parser }
62
+ it { should parse('"test" -> ":foo => :bar"') }
63
+ it { should parse('"test" -> "{ foo: 42, bar: \'baz\'} "') }
64
+ it { should parse("\"test\n on multiple lines\" -> \"{\n\tfoo: 42,\tbar: 'baz'\n}\"") }
65
+
66
+ it { should_not parse('"test"') }
67
+ it { should_not parse('"test" OK') }
68
+ it { should_not parse('"test" -> OK') }
69
+
70
+ it 'should be parsed to {input: {string: "example"}, output: {string: "example"}}' do
71
+ expect(mapping_example_parser.parse('"test" -> ":foo => :bar"'))
72
+ .to eq input: { string: 'test' }, output: { string: ':foo => :bar' }
73
+ expect(mapping_example_parser.parse('"test" -> "{ foo: 42, bar: \'baz\'} "'))
74
+ .to eq input: { string: 'test' }, output: { string: "{ foo: 42, bar: 'baz'} " }
75
+ expect(mapping_example_parser.parse("\"test\n on multiple lines\" -> \"{\n\tfoo: 42,\tbar: 'baz'\n}\""))
76
+ .to eq input: { string: "test\n on multiple lines" }, output: { string: "{\n\tfoo: 42,\tbar: 'baz'\n}" }
77
+ end
78
+ end
79
+
80
+ context 'rule name parsing' do
81
+ let(:rule_name_parser) { parser.rule_name }
82
+ subject { rule_name_parser }
83
+ it { should parse 'a_rule_name' }
84
+ it { should parse 'rule1' }
85
+ it { should parse '_rule2' }
86
+ it { should parse 'a_rule_name?' }
87
+ it { should parse 'a_rule_name!' }
88
+ it { should_not parse '0a' }
89
+ end
90
+
91
+ context 'rule description parsing' do
92
+ let(:rule_description_parser) { parser.rule_description }
93
+ subject { rule_description_parser }
94
+
95
+ it { should parse "a_rule_name:\n" }
96
+ it { should parse "a_rule_name:\n\"test\" OK" }
97
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
98
+ a_rule_name:
99
+ "test" -> "foo: :bar"
100
+ PARSPEC_RULE_DESCRIPTION
101
+ }
102
+
103
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
104
+ a_rule_name:
105
+ "test" OK
106
+ "test" -> "foo: :bar"
107
+ PARSPEC_RULE_DESCRIPTION
108
+ }
109
+
110
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
111
+ a_rule_name:
112
+ "test" OK
113
+ "test" -> "foo: :bar"
114
+ PARSPEC_RULE_DESCRIPTION
115
+ }
116
+
117
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
118
+ a_rule_name:
119
+
120
+ "test" OK
121
+
122
+ "test" -> "foo: :bar"
123
+
124
+
125
+ PARSPEC_RULE_DESCRIPTION
126
+ }
127
+
128
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
129
+ a_rule_name:
130
+ "test"
131
+ -> "foo: :bar"
132
+ "test" ->
133
+ "foo: :bar"
134
+
135
+ "test"
136
+ ->
137
+ "foo: :bar"
138
+ PARSPEC_RULE_DESCRIPTION
139
+ }
140
+
141
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
142
+ a_rule_name:
143
+ # "test" OK
144
+ PARSPEC_RULE_DESCRIPTION
145
+ }
146
+
147
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
148
+ a_rule_name:
149
+ "test" OK
150
+ # "test" -> "foo: :bar"
151
+ PARSPEC_RULE_DESCRIPTION
152
+ }
153
+
154
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
155
+ a_rule_name: # this a comment
156
+ "test" OK
157
+ PARSPEC_RULE_DESCRIPTION
158
+ }
159
+
160
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
161
+ a_rule_name:
162
+ "test" OK # this is a comment
163
+ PARSPEC_RULE_DESCRIPTION
164
+ }
165
+
166
+
167
+ it { should parse <<PARSPEC_RULE_DESCRIPTION
168
+ a_rule_name: # comment #1
169
+
170
+ # comment #2
171
+
172
+ # comment #3
173
+
174
+ "test" OK # comment #4
175
+
176
+ # comment #5
177
+
178
+
179
+ "test" -> "foo: :bar" # comment #6
180
+ # comment #7
181
+
182
+ PARSPEC_RULE_DESCRIPTION
183
+ }
184
+
185
+ it 'should parse a rule without examples to {rule_name: "name", examples: []}' do
186
+ expect(rule_description_parser.parse("a_rule_name:\n#some comment\n\n"))
187
+ .to eq rule_name: 'a_rule_name', examples: []
188
+ end
189
+
190
+ it 'should be parsed to {rule_name: "name", examples: [...]}' do
191
+ tree = {
192
+ rule_name: 'a_rule_name',
193
+ examples: [
194
+ {input: {string: 'test'}, validity: {status: 'OK' }},
195
+ {input: {string: 'test'}, output: {string: 'foo: :bar'}}
196
+ ]
197
+ }
198
+
199
+ expect(rule_description_parser.parse(<<PARSPEC_RULE_DESCRIPTION
200
+ a_rule_name:
201
+ "test" OK
202
+ "test" -> "foo: :bar"
203
+ PARSPEC_RULE_DESCRIPTION
204
+ )).to eq tree
205
+
206
+ expect(rule_description_parser.parse(<<PARSPEC_RULE_DESCRIPTION
207
+ a_rule_name:
208
+
209
+ "test" OK
210
+
211
+ "test" -> "foo: :bar"
212
+
213
+
214
+ PARSPEC_RULE_DESCRIPTION
215
+ )).to eq tree
216
+
217
+ expect(rule_description_parser.parse(<<PARSPEC_RULE_DESCRIPTION
218
+ a_rule_name: # comment #1
219
+
220
+ # comment #2
221
+
222
+ # comment #3
223
+
224
+ "test" OK # comment #4
225
+
226
+ # comment #5
227
+
228
+
229
+ "test" -> "foo: :bar" # comment #6
230
+ # comment #7
231
+
232
+ PARSPEC_RULE_DESCRIPTION
233
+ )).to eq tree
234
+ end
235
+
236
+ end
237
+
238
+ context 'spec parsing' do
239
+ it { should parse '' }
240
+ it { should parse ' ' }
241
+ it { should parse "\n" }
242
+ it { should parse "\n# comment only\n" }
243
+ it { should parse 'parser Some::Parser' }
244
+ it { should parse ' parser Some::Parser' }
245
+
246
+ it { should parse <<PARSPEC
247
+ parser Some::Parser
248
+
249
+ rule1:
250
+ "test" OK
251
+ "test" -> "foo: :bar"
252
+
253
+ rule2:
254
+ "foo" OK
255
+ "foo" -> ":bar => :foo"
256
+ PARSPEC
257
+ }
258
+
259
+ it { should parse <<PARSPEC
260
+ parser Some::Parser # a comment
261
+
262
+ rule1:
263
+ "test" OK
264
+ "test" -> "foo: :bar"
265
+ PARSPEC
266
+ }
267
+
268
+ it { should parse <<PARSPEC
269
+ parser Some::Parser
270
+ # a comment
271
+ rule1:
272
+ "test" OK
273
+ "test" -> "foo: :bar"
274
+ PARSPEC
275
+ }
276
+
277
+ it { should parse <<PARSPEC
278
+ # a comment
279
+ parser Some::Parser
280
+
281
+ # a comment
282
+
283
+ rule1:
284
+ "test" OK
285
+ "test" -> "foo: :bar"
286
+ PARSPEC
287
+ }
288
+
289
+ it 'should be parsed to {spec_name: "name", rules: [...]}' do
290
+ tree = {
291
+ header: {
292
+ subject_class: 'Some::Parser',
293
+ type: 'parser'
294
+ },
295
+ rules: [
296
+ { rule_name: 'rule1',
297
+ examples: [
298
+ {input: {string: 'test'}, validity: {status: 'OK' }},
299
+ {input: {string: 'test'}, output: {string: 'foo: :bar'}}
300
+ ] },
301
+ { rule_name: 'rule2',
302
+ examples: [
303
+ {input: {string: 'foo'}, output: {string: ':bar => :foo'}}
304
+ ] }
305
+ ]
306
+ }
307
+
308
+ expect(parser.parse(<<PARSPEC
309
+ parser Some::Parser
310
+
311
+ rule1:
312
+ "test" OK
313
+ "test" -> "foo: :bar"
314
+
315
+ rule2:
316
+ "foo" -> ":bar => :foo"
317
+ PARSPEC
318
+
319
+ )).to eq tree
320
+ end
321
+
322
+ end
323
+
324
+ end
@@ -0,0 +1,16 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:test)
3
+
4
+ require 'parspec'
5
+
6
+ SPEC_DIR = File.dirname(__FILE__)
7
+ Dir[File.join(SPEC_DIR, 'support/**/*.rb')].each {|f| require f }
8
+
9
+ RSpec.configure do |config|
10
+ # Run specs in random order to surface order dependencies. If you find an
11
+ # order dependency and want to debug it, you can fix the order by providing
12
+ # the seed, which is printed after each run.
13
+ # --seed 1234
14
+ config.order = 'random'
15
+
16
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Marcel Otto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: A specification language for Parslet grammars and transformers, which
70
+ gets translated to RSpec.
71
+ email:
72
+ - marcelotto.de@gmail.com
73
+ executables:
74
+ - parspec
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - Gemfile
81
+ - Guardfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/parspec
86
+ - examples/parspec_spec.parspec
87
+ - examples/toml_spec.parspec
88
+ - lib/parspec.rb
89
+ - lib/parspec/cli.rb
90
+ - lib/parspec/parser.rb
91
+ - lib/parspec/parser_spec_transform.rb
92
+ - lib/parspec/shared_transform.rb
93
+ - lib/parspec/transformer_spec_transform.rb
94
+ - lib/parspec/version.rb
95
+ - parspec.gemspec
96
+ - spec/parspec/parser_spec.rb
97
+ - spec/spec_helper.rb
98
+ homepage: http://github.com/marcelotto/parspec
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.1.9
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Testing of Parslet grammars and transformations
122
+ test_files:
123
+ - spec/parspec/parser_spec.rb
124
+ - spec/spec_helper.rb
125
+ has_rdoc: