parspec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +238 -0
- data/Rakefile +2 -0
- data/bin/parspec +3 -0
- data/examples/parspec_spec.parspec +14 -0
- data/examples/toml_spec.parspec +66 -0
- data/lib/parspec/cli.rb +110 -0
- data/lib/parspec/parser.rb +76 -0
- data/lib/parspec/parser_spec_transform.rb +50 -0
- data/lib/parspec/shared_transform.rb +18 -0
- data/lib/parspec/transformer_spec_transform.rb +43 -0
- data/lib/parspec/version.rb +3 -0
- data/lib/parspec.rb +79 -0
- data/parspec.gemspec +26 -0
- data/spec/parspec/parser_spec.rb +324 -0
- data/spec/spec_helper.rb +16 -0
- metadata +125 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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
data/bin/parspec
ADDED
@@ -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'}"
|
data/lib/parspec/cli.rb
ADDED
@@ -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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|