parspec 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.
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: