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 +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:
|