gherkin 2.3.1-universal-dotnet → 2.3.2-universal-dotnet
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/README.rdoc +2 -2
- data/VERSION +1 -1
- data/features/json_formatter.feature +99 -0
- data/features/step_definitions/json_parser_steps.rb +1 -1
- data/features/step_definitions/pretty_formatter_steps.rb +2 -2
- data/lib/gherkin/formatter/colors.rb +3 -3
- data/lib/gherkin/formatter/escaping.rb +1 -1
- data/lib/gherkin/formatter/json_formatter.rb +1 -1
- data/lib/gherkin/formatter/pretty_formatter.rb +10 -4
- data/lib/gherkin/i18n.rb +5 -10
- data/lib/gherkin/json_parser.rb +1 -1
- data/ragel/lexer.c.rl.erb +20 -50
- data/ragel/lexer.java.rl.erb +20 -36
- data/ragel/lexer.rb.rl.erb +9 -14
- data/spec/gherkin/formatter/filter_formatter_spec.rb +1 -1
- data/spec/gherkin/formatter/pretty_formatter_spec.rb +11 -10
- data/spec/gherkin/shared/lexer_group.rb +43 -42
- data/spec/gherkin/shared/row_group.rb +5 -0
- metadata +4 -4
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
== 2.3.2 (2010-12-05)
|
2
|
+
|
3
|
+
(Somehow 2.3.1 was released improperly shortly after 2.3.0 - not sure what fixes went into that!)
|
4
|
+
|
5
|
+
=== Bugfixes
|
6
|
+
* Preserve whitespace in descriptions. Leading whitespace in descriptions are stripped upto preceding keyword + 2 spaces (#87 Matt Wynne, Gregory Hnatiuk, Aslak Hellesøy)
|
7
|
+
* Fix incorrect indentation of Examples descriptions (Gregory Hnatiuk)
|
8
|
+
* Can't define new line characters in Example Table Cell's Content. (#85 George Montana Harkin, Aslak Hellesøy)
|
9
|
+
|
1
10
|
== 2.3.0 (2010-11-12)
|
2
11
|
|
3
12
|
=== New Features
|
data/README.rdoc
CHANGED
@@ -39,8 +39,8 @@ Running RSpec and Cucumber tests
|
|
39
39
|
|
40
40
|
rake clean spec cucumber
|
41
41
|
|
42
|
-
If the
|
43
|
-
E.g. in Bash, export
|
42
|
+
If the RL_LANGS environment variable is set, only the parsers for the languages specified there will be built.
|
43
|
+
E.g. in Bash, export RL_LANGS="en,fr,no". This can be quite helpful when modifying the Ragel grammar.
|
44
44
|
|
45
45
|
See subsections for building for a specific platform.
|
46
46
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.3.
|
1
|
+
2.3.2
|
@@ -275,4 +275,103 @@ Feature: JSON formatter
|
|
275
275
|
]
|
276
276
|
}
|
277
277
|
"""
|
278
|
+
|
279
|
+
Scenario: Feature with a description
|
280
|
+
|
281
|
+
We want people to be able to put markdown formatting into their descriptions
|
282
|
+
but this means we need to respect whitespace at the start and end of lines
|
283
|
+
in the description.
|
284
|
+
|
285
|
+
Pay close attention to the whitespace in this example.
|
278
286
|
|
287
|
+
Given the following text is parsed:
|
288
|
+
"""
|
289
|
+
Feature: Foo
|
290
|
+
one line
|
291
|
+
another line
|
292
|
+
|
293
|
+
some pre-formatted stuff
|
294
|
+
|
295
|
+
Background: name
|
296
|
+
test
|
297
|
+
test
|
298
|
+
|
299
|
+
Scenario: name
|
300
|
+
test
|
301
|
+
test
|
302
|
+
|
303
|
+
Scenario Outline: name
|
304
|
+
test
|
305
|
+
test
|
306
|
+
|
307
|
+
Given <foo>
|
308
|
+
|
309
|
+
Examples: name
|
310
|
+
test
|
311
|
+
test
|
312
|
+
| foo |
|
313
|
+
| table |
|
314
|
+
"""
|
315
|
+
Then the outputted JSON should be:
|
316
|
+
"""
|
317
|
+
{
|
318
|
+
"keyword": "Feature",
|
319
|
+
"name": "Foo",
|
320
|
+
"description": "one line \nanother line \n\n some pre-formatted stuff",
|
321
|
+
"line": 1,
|
322
|
+
"elements": [
|
323
|
+
{
|
324
|
+
"description": " test \n test",
|
325
|
+
"keyword": "Background",
|
326
|
+
"line": 7,
|
327
|
+
"name": "name",
|
328
|
+
"type": "background"
|
329
|
+
},
|
330
|
+
{
|
331
|
+
"description": " test \n test",
|
332
|
+
"keyword": "Scenario",
|
333
|
+
"line": 11,
|
334
|
+
"name": "name",
|
335
|
+
"type": "scenario"
|
336
|
+
},
|
337
|
+
{
|
338
|
+
"description": " test \n test",
|
339
|
+
"examples": [
|
340
|
+
{
|
341
|
+
"description": " test \n test",
|
342
|
+
"keyword": "Examples",
|
343
|
+
"line": 21,
|
344
|
+
"name": "name",
|
345
|
+
"rows": [
|
346
|
+
{
|
347
|
+
"cells": [
|
348
|
+
"foo"
|
349
|
+
],
|
350
|
+
"line": 24
|
351
|
+
},
|
352
|
+
{
|
353
|
+
"cells": [
|
354
|
+
"table"
|
355
|
+
],
|
356
|
+
"line": 25
|
357
|
+
}
|
358
|
+
]
|
359
|
+
}
|
360
|
+
],
|
361
|
+
"keyword": "Scenario Outline",
|
362
|
+
"line": 15,
|
363
|
+
"name": "name",
|
364
|
+
"steps": [
|
365
|
+
{
|
366
|
+
"keyword": "Given ",
|
367
|
+
"line": 19,
|
368
|
+
"name": "<foo>"
|
369
|
+
}
|
370
|
+
],
|
371
|
+
"type": "scenario_outline"
|
372
|
+
}
|
373
|
+
]
|
374
|
+
}
|
375
|
+
"""
|
376
|
+
|
377
|
+
|
@@ -6,7 +6,7 @@ World(Gherkin::Formatter::Colors)
|
|
6
6
|
|
7
7
|
Given /^a PrettyFormatter$/ do
|
8
8
|
@io = StringIO.new
|
9
|
-
@formatter = Gherkin::Formatter::PrettyFormatter.new(@io, true)
|
9
|
+
@formatter = Gherkin::Formatter::PrettyFormatter.new(@io, true, false)
|
10
10
|
end
|
11
11
|
|
12
12
|
Given /^a JSON lexer$/ do
|
@@ -10,7 +10,7 @@ module PrettyPlease
|
|
10
10
|
|
11
11
|
def pretty_machinery(gherkin, feature_path)
|
12
12
|
io = StringIO.new
|
13
|
-
formatter = Gherkin::Formatter::PrettyFormatter.new(io, true)
|
13
|
+
formatter = Gherkin::Formatter::PrettyFormatter.new(io, true, false)
|
14
14
|
parser = Gherkin::Parser::Parser.new(formatter, true)
|
15
15
|
parse(parser, gherkin, feature_path)
|
16
16
|
io.string
|
@@ -23,7 +23,7 @@ module PrettyPlease
|
|
23
23
|
parse(gherkin_parser, gherkin, feature_path)
|
24
24
|
|
25
25
|
io = StringIO.new
|
26
|
-
pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(io, true)
|
26
|
+
pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(io, true, false)
|
27
27
|
json_parser = Gherkin::JSONParser.new(pretty_formatter)
|
28
28
|
json_parser.parse(json.string, "#{feature_path}.json", 0)
|
29
29
|
|
@@ -13,8 +13,8 @@ module Gherkin
|
|
13
13
|
# <tt>undefined</tt>:: defaults to <tt>yellow</tt>
|
14
14
|
# <tt>pending</tt>:: defaults to <tt>yellow</tt>
|
15
15
|
# <tt>pending_arg</tt>:: defaults to <tt>yellow,bold</tt>
|
16
|
-
# <tt>executing</tt>:: defaults to <tt>
|
17
|
-
# <tt>executing_arg</tt>:: defaults to <tt>
|
16
|
+
# <tt>executing</tt>:: defaults to <tt>grey</tt>
|
17
|
+
# <tt>executing_arg</tt>:: defaults to <tt>grey,bold</tt>
|
18
18
|
# <tt>failed</tt>:: defaults to <tt>red</tt>
|
19
19
|
# <tt>failed_arg</tt>:: defaults to <tt>red,bold</tt>
|
20
20
|
# <tt>passed</tt>:: defaults to <tt>green</tt>
|
@@ -49,7 +49,7 @@ module Gherkin
|
|
49
49
|
end.merge({
|
50
50
|
'undefined' => 'yellow',
|
51
51
|
'pending' => 'yellow',
|
52
|
-
'executing' => '
|
52
|
+
'executing' => 'grey',
|
53
53
|
'failed' => 'red',
|
54
54
|
'passed' => 'green',
|
55
55
|
'outline' => 'cyan',
|
@@ -14,10 +14,11 @@ module Gherkin
|
|
14
14
|
include Colors
|
15
15
|
include Escaping
|
16
16
|
|
17
|
-
def initialize(io, monochrome
|
17
|
+
def initialize(io, monochrome, executing)
|
18
18
|
@io = io
|
19
19
|
@step_printer = StepPrinter.new
|
20
20
|
@monochrome = monochrome
|
21
|
+
@executing = executing
|
21
22
|
end
|
22
23
|
|
23
24
|
def uri(uri)
|
@@ -55,14 +56,19 @@ module Gherkin
|
|
55
56
|
print_comments(examples.comments, ' ')
|
56
57
|
print_tags(examples.tags, ' ')
|
57
58
|
@io.puts " #{examples.keyword}: #{examples.name}"
|
58
|
-
print_description(examples.description, '
|
59
|
+
print_description(examples.description, ' ')
|
59
60
|
table(examples.rows)
|
60
61
|
end
|
61
62
|
|
62
63
|
def step(step)
|
63
64
|
@step = step
|
64
65
|
@step_index += 1 if @step_index
|
65
|
-
|
66
|
+
# TODO: It feels a little funny to have this logic here in the formatter.
|
67
|
+
# We may have to duplicate it across formatters. So maybe we should move
|
68
|
+
# this out to the callers instead.
|
69
|
+
#
|
70
|
+
# Maybe it's a Filter!! ExecuteFilter and PrettyFilter
|
71
|
+
match(Model::Match.new([], nil)) unless @executing
|
66
72
|
end
|
67
73
|
|
68
74
|
def match(match)
|
@@ -225,4 +231,4 @@ module Gherkin
|
|
225
231
|
end
|
226
232
|
end
|
227
233
|
end
|
228
|
-
end
|
234
|
+
end
|
data/lib/gherkin/i18n.rb
CHANGED
@@ -56,17 +56,13 @@ module Gherkin
|
|
56
56
|
require 'stringio'
|
57
57
|
require 'gherkin/formatter/pretty_formatter'
|
58
58
|
require 'gherkin/formatter/model'
|
59
|
-
io =
|
60
|
-
pf = Gherkin::Formatter::PrettyFormatter.new(io)
|
59
|
+
io = StringIO.new
|
60
|
+
pf = Gherkin::Formatter::PrettyFormatter.new(io, false, false)
|
61
61
|
table = all.map do |i18n|
|
62
62
|
Formatter::Model::Row.new([], [i18n.iso_code, i18n.keywords('name')[0], i18n.keywords('native')[0]], nil)
|
63
63
|
end
|
64
64
|
pf.table(table)
|
65
|
-
|
66
|
-
io.getBuffer.toString
|
67
|
-
else
|
68
|
-
io.string
|
69
|
-
end
|
65
|
+
io.string
|
70
66
|
end
|
71
67
|
|
72
68
|
def unicode_escape(word, prefix="\\u")
|
@@ -149,7 +145,7 @@ module Gherkin
|
|
149
145
|
require 'gherkin/formatter/pretty_formatter'
|
150
146
|
require 'gherkin/formatter/model'
|
151
147
|
io = StringIO.new
|
152
|
-
pf = Gherkin::Formatter::PrettyFormatter.new(io)
|
148
|
+
pf = Gherkin::Formatter::PrettyFormatter.new(io, false, false)
|
153
149
|
|
154
150
|
gherkin_keyword_table = KEYWORD_KEYS.map do |key|
|
155
151
|
Formatter::Model::Row.new([], [key, keywords(key).map{|keyword| %{"#{keyword}"}}.join(', ')], nil)
|
@@ -163,8 +159,7 @@ module Gherkin
|
|
163
159
|
end
|
164
160
|
|
165
161
|
pf.table(gherkin_keyword_table + code_keyword_table)
|
166
|
-
io.
|
167
|
-
io.read
|
162
|
+
io.string
|
168
163
|
end
|
169
164
|
|
170
165
|
private
|
data/lib/gherkin/json_parser.rb
CHANGED
@@ -78,7 +78,7 @@ module Gherkin
|
|
78
78
|
|
79
79
|
def embeddings(o)
|
80
80
|
(o['embeddings'] || []).each do |embedding|
|
81
|
-
@formatter.embedding(embedding['mime_type'], decode64(embedding['data']))
|
81
|
+
@formatter.embedding(embedding['mime_type'], Base64::decode64(embedding['data']))
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
data/ragel/lexer.c.rl.erb
CHANGED
@@ -20,11 +20,6 @@
|
|
20
20
|
rb_str_new(ptr, len)
|
21
21
|
#endif
|
22
22
|
|
23
|
-
#define LF_FLAG 0
|
24
|
-
#define CRLF_FLAG 1
|
25
|
-
#define LF "\n"
|
26
|
-
#define CRLF "\r\n"
|
27
|
-
|
28
23
|
#ifndef RSTRING_PTR
|
29
24
|
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
30
25
|
#endif
|
@@ -44,7 +39,6 @@ typedef struct lexer_state {
|
|
44
39
|
int line_number;
|
45
40
|
int current_line;
|
46
41
|
int start_col;
|
47
|
-
int eol;
|
48
42
|
size_t mark;
|
49
43
|
size_t keyword_start;
|
50
44
|
size_t keyword_end;
|
@@ -70,7 +64,7 @@ static VALUE rb_eGherkinLexingError;
|
|
70
64
|
store_multiline_kw_con(listener, # EVENT, \
|
71
65
|
PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end - 1)), \
|
72
66
|
PTR_TO(content_start), LEN(content_start, PTR_TO(content_end)), \
|
73
|
-
lexer->current_line, lexer->
|
67
|
+
lexer->current_line, lexer->start_col); \
|
74
68
|
if (lexer->content_end != 0) { \
|
75
69
|
p = PTR_TO(content_end - 1); \
|
76
70
|
} \
|
@@ -87,6 +81,7 @@ static VALUE rb_eGherkinLexingError;
|
|
87
81
|
action begin_content {
|
88
82
|
MARK(content_start, p);
|
89
83
|
lexer->current_line = lexer->line_number;
|
84
|
+
lexer->start_col = lexer->content_start - lexer->last_newline - (lexer->keyword_end - lexer->keyword_start) + 2;
|
90
85
|
}
|
91
86
|
|
92
87
|
action begin_pystring_content {
|
@@ -183,8 +178,10 @@ static VALUE rb_eGherkinLexingError;
|
|
183
178
|
VALUE con = ENCODED_STR_NEW(PTR_TO(content_start), LEN(content_start, p));
|
184
179
|
rb_funcall(con, rb_intern("strip!"), 0);
|
185
180
|
VALUE re_pipe = rb_reg_regcomp(rb_str_new2("\\\\\\|"));
|
181
|
+
VALUE re_newline = rb_reg_regcomp(rb_str_new2("\\\\n"));
|
186
182
|
VALUE re_backslash = rb_reg_regcomp(rb_str_new2("\\\\\\\\"));
|
187
183
|
rb_funcall(con, rb_intern("gsub!"), 2, re_pipe, rb_str_new2("|"));
|
184
|
+
rb_funcall(con, rb_intern("gsub!"), 2, re_newline, rb_str_new2("\n"));
|
188
185
|
rb_funcall(con, rb_intern("gsub!"), 2, re_backslash, rb_str_new2("\\"));
|
189
186
|
|
190
187
|
rb_ary_push(current_row, con);
|
@@ -247,23 +244,16 @@ static VALUE rb_eGherkinLexingError;
|
|
247
244
|
%% write data;
|
248
245
|
|
249
246
|
static VALUE
|
250
|
-
|
247
|
+
unindent(VALUE con, int start_col)
|
251
248
|
{
|
252
|
-
|
253
|
-
|
254
|
-
|
249
|
+
// Gherkin will crash gracefully if the string representation of start_col pushes the pattern past 32 characters
|
250
|
+
char pat[32];
|
251
|
+
snprintf(pat, 32, "^[\t ]{0,%d}", start_col);
|
252
|
+
VALUE re = rb_reg_regcomp(rb_str_new2(pat));
|
253
|
+
rb_funcall(con, rb_intern("gsub!"), 2, re, rb_str_new2(""));
|
254
|
+
|
255
255
|
return Qnil;
|
256
|
-
}
|
257
256
|
|
258
|
-
static VALUE
|
259
|
-
multiline_strip(VALUE text)
|
260
|
-
{
|
261
|
-
VALUE map = rb_ary_new();
|
262
|
-
VALUE split = rb_str_split(text, "\n");
|
263
|
-
|
264
|
-
rb_iterate(rb_each, split, strip_i, map);
|
265
|
-
|
266
|
-
return split;
|
267
257
|
}
|
268
258
|
|
269
259
|
static void
|
@@ -283,18 +273,19 @@ static void
|
|
283
273
|
store_multiline_kw_con(VALUE listener, const char * event_name,
|
284
274
|
const char * keyword_at, size_t keyword_length,
|
285
275
|
const char * at, size_t length,
|
286
|
-
int current_line, int
|
276
|
+
int current_line, int start_col)
|
287
277
|
{
|
288
278
|
VALUE con = Qnil, kw = Qnil, name = Qnil, desc = Qnil;
|
289
279
|
|
290
280
|
kw = ENCODED_STR_NEW(keyword_at, keyword_length);
|
291
281
|
con = ENCODED_STR_NEW(at, length);
|
292
282
|
|
293
|
-
|
283
|
+
unindent(con, start_col);
|
294
284
|
|
285
|
+
VALUE split = rb_str_split(con, "\n");
|
286
|
+
|
295
287
|
name = rb_funcall(split, rb_intern("shift"), 0);
|
296
|
-
desc = rb_ary_join(split, rb_str_new2( \
|
297
|
-
eol == CRLF_FLAG ? CRLF : LF ));
|
288
|
+
desc = rb_ary_join(split, rb_str_new2( "\n" ));
|
298
289
|
|
299
290
|
if( name == Qnil )
|
300
291
|
{
|
@@ -305,7 +296,7 @@ store_multiline_kw_con(VALUE listener, const char * event_name,
|
|
305
296
|
desc = rb_str_new2("");
|
306
297
|
}
|
307
298
|
rb_funcall(name, rb_intern("strip!"), 0);
|
308
|
-
rb_funcall(desc, rb_intern("
|
299
|
+
rb_funcall(desc, rb_intern("rstrip!"), 0);
|
309
300
|
rb_funcall(listener, rb_intern(event_name), 4, kw, name, desc, INT2FIX(current_line));
|
310
301
|
}
|
311
302
|
|
@@ -325,13 +316,11 @@ store_pystring_content(VALUE listener,
|
|
325
316
|
int current_line)
|
326
317
|
{
|
327
318
|
VALUE con = ENCODED_STR_NEW(at, length);
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
VALUE re = rb_reg_regcomp(rb_str_new2(pat));
|
319
|
+
|
320
|
+
unindent(con, start_col);
|
321
|
+
|
332
322
|
VALUE re2 = rb_reg_regcomp(rb_str_new2("\r\\Z"));
|
333
323
|
VALUE unescape_escaped_quotes = rb_reg_regcomp(rb_str_new2("\\\\\"\\\\\"\\\\\""));
|
334
|
-
rb_funcall(con, rb_intern("gsub!"), 2, re, rb_str_new2(""));
|
335
324
|
rb_funcall(con, rb_intern("sub!"), 2, re2, rb_str_new2(""));
|
336
325
|
rb_funcall(con, rb_intern("gsub!"), 2, unescape_escaped_quotes, rb_str_new2("\"\"\""));
|
337
326
|
rb_funcall(listener, rb_intern("py_string"), 2, con, INT2FIX(current_line));
|
@@ -343,20 +332,6 @@ raise_lexer_error(const char * at, int line)
|
|
343
332
|
rb_raise(rb_eGherkinLexingError, "Lexing error on line %d: '%s'. See http://wiki.github.com/aslakhellesoy/gherkin/lexingerror for more information.", line, at);
|
344
333
|
}
|
345
334
|
|
346
|
-
static int
|
347
|
-
count_char(char char_to_count, char *str) {
|
348
|
-
|
349
|
-
int count = 0;
|
350
|
-
int i = 0;
|
351
|
-
while(str[i] != '\0') {
|
352
|
-
if(str[i] == char_to_count) {
|
353
|
-
count++;
|
354
|
-
}
|
355
|
-
i++;
|
356
|
-
}
|
357
|
-
return count;
|
358
|
-
}
|
359
|
-
|
360
335
|
static void lexer_init(lexer_state *lexer) {
|
361
336
|
lexer->content_start = 0;
|
362
337
|
lexer->content_end = 0;
|
@@ -369,7 +344,6 @@ static void lexer_init(lexer_state *lexer) {
|
|
369
344
|
lexer->last_newline = 0;
|
370
345
|
lexer->final_newline = 0;
|
371
346
|
lexer->start_col = 0;
|
372
|
-
lexer->eol = LF_FLAG;
|
373
347
|
}
|
374
348
|
|
375
349
|
static VALUE CLexer_alloc(VALUE klass)
|
@@ -407,10 +381,6 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
|
|
407
381
|
char *data = RSTRING_PTR(input_copy);
|
408
382
|
size_t len = RSTRING_LEN(input_copy);
|
409
383
|
|
410
|
-
if (count_char('\r', data) > (count_char('\n', data) / 2)) {
|
411
|
-
lexer->eol = CRLF_FLAG;
|
412
|
-
}
|
413
|
-
|
414
384
|
if (len == 0) {
|
415
385
|
rb_raise(rb_eGherkinLexingError, "No content to lex.");
|
416
386
|
} else {
|
data/ragel/lexer.java.rl.erb
CHANGED
@@ -4,7 +4,6 @@ import java.io.UnsupportedEncodingException;
|
|
4
4
|
import java.util.List;
|
5
5
|
import java.util.ArrayList;
|
6
6
|
import java.util.regex.Pattern;
|
7
|
-
import java.util.regex.Matcher;
|
8
7
|
import gherkin.lexer.Lexer;
|
9
8
|
import gherkin.lexer.Listener;
|
10
9
|
import gherkin.lexer.LexingError;
|
@@ -17,6 +16,9 @@ public class <%= @i18n.underscored_iso_code.upcase %> implements Lexer {
|
|
17
16
|
action begin_content {
|
18
17
|
contentStart = p;
|
19
18
|
currentLine = lineNumber;
|
19
|
+
if(keyword != null) {
|
20
|
+
startCol = p - lastNewline - (keyword.length() + 1);
|
21
|
+
}
|
20
22
|
}
|
21
23
|
|
22
24
|
action start_pystring {
|
@@ -34,35 +36,35 @@ public class <%= @i18n.underscored_iso_code.upcase %> implements Lexer {
|
|
34
36
|
}
|
35
37
|
|
36
38
|
action store_feature_content {
|
37
|
-
String[] nameDescription =
|
39
|
+
String[] nameDescription = nameAndUnindentedDescription(startCol, keywordContent(data, p, eof, nextKeywordStart, contentStart));
|
38
40
|
listener.feature(keyword, nameDescription[0], nameDescription[1], currentLine);
|
39
41
|
if(nextKeywordStart != -1) p = nextKeywordStart - 1;
|
40
42
|
nextKeywordStart = -1;
|
41
43
|
}
|
42
44
|
|
43
45
|
action store_background_content {
|
44
|
-
String[] nameDescription =
|
46
|
+
String[] nameDescription = nameAndUnindentedDescription(startCol, keywordContent(data, p, eof, nextKeywordStart, contentStart));
|
45
47
|
listener.background(keyword, nameDescription[0], nameDescription[1], currentLine);
|
46
48
|
if(nextKeywordStart != -1) p = nextKeywordStart - 1;
|
47
49
|
nextKeywordStart = -1;
|
48
50
|
}
|
49
51
|
|
50
52
|
action store_scenario_content {
|
51
|
-
String[] nameDescription =
|
53
|
+
String[] nameDescription = nameAndUnindentedDescription(startCol, keywordContent(data, p, eof, nextKeywordStart, contentStart));
|
52
54
|
listener.scenario(keyword, nameDescription[0], nameDescription[1], currentLine);
|
53
55
|
if(nextKeywordStart != -1) p = nextKeywordStart - 1;
|
54
56
|
nextKeywordStart = -1;
|
55
57
|
}
|
56
58
|
|
57
59
|
action store_scenario_outline_content {
|
58
|
-
String[] nameDescription =
|
60
|
+
String[] nameDescription = nameAndUnindentedDescription(startCol, keywordContent(data, p, eof, nextKeywordStart, contentStart));
|
59
61
|
listener.scenarioOutline(keyword, nameDescription[0], nameDescription[1], currentLine);
|
60
62
|
if(nextKeywordStart != -1) p = nextKeywordStart - 1;
|
61
63
|
nextKeywordStart = -1;
|
62
64
|
}
|
63
65
|
|
64
66
|
action store_examples_content {
|
65
|
-
String[] nameDescription =
|
67
|
+
String[] nameDescription = nameAndUnindentedDescription(startCol, keywordContent(data, p, eof, nextKeywordStart, contentStart));
|
66
68
|
listener.examples(keyword, nameDescription[0], nameDescription[1], currentLine);
|
67
69
|
if(nextKeywordStart != -1) p = nextKeywordStart - 1;
|
68
70
|
nextKeywordStart = -1;
|
@@ -115,7 +117,11 @@ public class <%= @i18n.underscored_iso_code.upcase %> implements Lexer {
|
|
115
117
|
|
116
118
|
action store_cell_content {
|
117
119
|
String con = substring(data, contentStart, p).trim();
|
118
|
-
currentRow.add(con
|
120
|
+
currentRow.add(con
|
121
|
+
.replaceAll("\\\\\\|", "|")
|
122
|
+
.replaceAll("\\\\n", "\n")
|
123
|
+
.replaceAll("\\\\\\\\", "\\\\")
|
124
|
+
);
|
119
125
|
}
|
120
126
|
|
121
127
|
action store_row {
|
@@ -173,37 +179,15 @@ public class <%= @i18n.underscored_iso_code.upcase %> implements Lexer {
|
|
173
179
|
return substring(data, contentStart, endPoint);
|
174
180
|
}
|
175
181
|
|
176
|
-
private
|
177
|
-
|
178
|
-
|
179
|
-
private static final String LF = "\n";
|
180
|
-
|
181
|
-
private String[] nameAndDescriptionWithPlatformNewlinesIntact(String text) {
|
182
|
-
int crlfCount = matchCount(CRLF_RE.matcher(text));
|
183
|
-
int lfCount = matchCount(LF_RE.matcher(text));
|
184
|
-
String eol = crlfCount > lfCount ? CRLF : LF;
|
185
|
-
|
186
|
-
String name = null;
|
182
|
+
private String[] nameAndUnindentedDescription(int startCol, String text) {
|
183
|
+
String[] lines = text.split("\n");
|
184
|
+
String name = lines.length > 0 ? lines[0].trim() : "";
|
187
185
|
StringBuffer description = new StringBuffer();
|
188
|
-
for(
|
189
|
-
|
190
|
-
|
191
|
-
} else {
|
192
|
-
description.append(s.trim()).append(eol);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
if(name == null) {
|
196
|
-
name = "";
|
197
|
-
}
|
198
|
-
return new String[]{name, description.toString().trim()};
|
199
|
-
}
|
200
|
-
|
201
|
-
private int matchCount(Matcher m) {
|
202
|
-
int count = 0;
|
203
|
-
while(m.find()) {
|
204
|
-
count++;
|
186
|
+
for(int i = 1; i < lines.length; i++) {
|
187
|
+
description.append(lines[i]);
|
188
|
+
description.append("\n");
|
205
189
|
}
|
206
|
-
return
|
190
|
+
return new String[]{name, unindent(startCol+2, description.toString()).replaceAll("\\s+$", "")};
|
207
191
|
}
|
208
192
|
|
209
193
|
private String unindent(int startCol, String text) {
|
data/ragel/lexer.rb.rl.erb
CHANGED
@@ -5,10 +5,11 @@ module Gherkin
|
|
5
5
|
class <%= @i18n.underscored_iso_code.capitalize %> #:nodoc:
|
6
6
|
%%{
|
7
7
|
machine lexer;
|
8
|
-
|
8
|
+
|
9
9
|
action begin_content {
|
10
10
|
@content_start = p
|
11
11
|
@current_line = @line_number
|
12
|
+
@start_col = p - @last_newline - "#{@keyword}:".length
|
12
13
|
}
|
13
14
|
|
14
15
|
action start_pystring {
|
@@ -26,31 +27,32 @@ module Gherkin
|
|
26
27
|
}
|
27
28
|
|
28
29
|
action store_feature_content {
|
29
|
-
|
30
|
+
|
31
|
+
store_keyword_content(:feature, data, p, eof) { |con| unindent(@start_col + 2, con).rstrip }
|
30
32
|
p = @next_keyword_start - 1 if @next_keyword_start
|
31
33
|
@next_keyword_start = nil
|
32
34
|
}
|
33
35
|
|
34
36
|
action store_background_content {
|
35
|
-
store_keyword_content(:background, data, p, eof) { |con|
|
37
|
+
store_keyword_content(:background, data, p, eof) { |con| unindent(@start_col + 2, con).rstrip }
|
36
38
|
p = @next_keyword_start - 1 if @next_keyword_start
|
37
39
|
@next_keyword_start = nil
|
38
40
|
}
|
39
41
|
|
40
42
|
action store_scenario_content {
|
41
|
-
store_keyword_content(:scenario, data, p, eof) { |con|
|
43
|
+
store_keyword_content(:scenario, data, p, eof) { |con| unindent(@start_col + 2, con).rstrip }
|
42
44
|
p = @next_keyword_start - 1 if @next_keyword_start
|
43
45
|
@next_keyword_start = nil
|
44
46
|
}
|
45
47
|
|
46
48
|
action store_scenario_outline_content {
|
47
|
-
store_keyword_content(:scenario_outline, data, p, eof) { |con|
|
49
|
+
store_keyword_content(:scenario_outline, data, p, eof) { |con| unindent(@start_col + 2, con).rstrip }
|
48
50
|
p = @next_keyword_start - 1 if @next_keyword_start
|
49
51
|
@next_keyword_start = nil
|
50
52
|
}
|
51
53
|
|
52
54
|
action store_examples_content {
|
53
|
-
store_keyword_content(:examples, data, p, eof) { |con|
|
55
|
+
store_keyword_content(:examples, data, p, eof) { |con| unindent(@start_col + 2, con).rstrip }
|
54
56
|
p = @next_keyword_start - 1 if @next_keyword_start
|
55
57
|
@next_keyword_start = nil
|
56
58
|
}
|
@@ -105,7 +107,7 @@ module Gherkin
|
|
105
107
|
|
106
108
|
action store_cell_content {
|
107
109
|
con = utf8_pack(data[@content_start...p]).strip
|
108
|
-
current_row << con.gsub(/\\\|/, "|").gsub(/\\\\/, "\\")
|
110
|
+
current_row << con.gsub(/\\\|/, "|").gsub(/\\n/, "\n").gsub(/\\\\/, "\\")
|
109
111
|
}
|
110
112
|
|
111
113
|
action store_row {
|
@@ -145,13 +147,6 @@ module Gherkin
|
|
145
147
|
CRLF = "\r\n"
|
146
148
|
LF = "\n"
|
147
149
|
|
148
|
-
def multiline_strip(text)
|
149
|
-
crlf_count = text.scan(CRLF_RE).size
|
150
|
-
lf_count = text.scan(LF_RE).size
|
151
|
-
eol = crlf_count > lf_count ? CRLF : LF
|
152
|
-
text.split(/\r?\n/).map{|s| s.strip}.join(eol).strip
|
153
|
-
end
|
154
|
-
|
155
150
|
def unindent(startcol, text)
|
156
151
|
text.gsub(/^[\t ]{0,#{startcol}}/, "")
|
157
152
|
end
|
@@ -18,7 +18,7 @@ module Gherkin
|
|
18
18
|
|
19
19
|
def verify_filter(filters, *line_ranges)
|
20
20
|
io = StringIO.new
|
21
|
-
pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(io, true)
|
21
|
+
pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(io, true, false)
|
22
22
|
filter_formatter = Gherkin::Formatter::FilterFormatter.new(pretty_formatter, filters)
|
23
23
|
parser = Gherkin::Parser::Parser.new(filter_formatter)
|
24
24
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
2
3
|
require 'gherkin/formatter/pretty_formatter'
|
3
4
|
require 'gherkin/formatter/argument'
|
4
5
|
require 'gherkin/formatter/model'
|
@@ -20,7 +21,7 @@ module Gherkin
|
|
20
21
|
def assert_pretty(input, expected_output=input)
|
21
22
|
[true, false].each do |force_ruby|
|
22
23
|
io = StringIO.new
|
23
|
-
pf = Gherkin::Formatter::PrettyFormatter.new(io, true)
|
24
|
+
pf = Gherkin::Formatter::PrettyFormatter.new(io, true, false)
|
24
25
|
parser = Gherkin::Parser::Parser.new(pf, true, "root", force_ruby)
|
25
26
|
parser.parse(input, "test.feature", 0)
|
26
27
|
output = io.string
|
@@ -30,7 +31,7 @@ module Gherkin
|
|
30
31
|
|
31
32
|
before do
|
32
33
|
@io = StringIO.new
|
33
|
-
@f = Gherkin::Formatter::PrettyFormatter.new(@io, false)
|
34
|
+
@f = Gherkin::Formatter::PrettyFormatter.new(@io, false, true)
|
34
35
|
end
|
35
36
|
|
36
37
|
it "should print comments when scenario is longer" do
|
@@ -60,9 +61,9 @@ module Gherkin
|
|
60
61
|
World
|
61
62
|
|
62
63
|
Scenario: The scenario #{grey('# features/foo.feature:4')}
|
63
|
-
#{
|
64
|
+
#{grey('Given ')}#{grey('some stuff')} #{grey('# features/step_definitions/bar.rb:56')}
|
64
65
|
\033[1A #{green('Given ')}#{green('some stuff')} #{grey('# features/step_definitions/bar.rb:56')}
|
65
|
-
#{
|
66
|
+
#{grey('When ')}#{grey('foo')} #{grey('# features/step_definitions/bar.rb:96')}
|
66
67
|
\033[1A #{green('When ')}#{green('foo')} #{grey('# features/step_definitions/bar.rb:96')}
|
67
68
|
})
|
68
69
|
end
|
@@ -84,7 +85,7 @@ module Gherkin
|
|
84
85
|
World
|
85
86
|
|
86
87
|
Scenario: The scenario #{grey('# features/foo.feature:4')}
|
87
|
-
#{
|
88
|
+
#{grey('Given ')}#{grey('some stuff that is longer')} #{grey('# features/step_definitions/bar.rb:56')}
|
88
89
|
\033[1A #{green('Given ')}#{green('some stuff that is longer')} #{grey('# features/step_definitions/bar.rb:56')}
|
89
90
|
})
|
90
91
|
end
|
@@ -102,12 +103,12 @@ module Gherkin
|
|
102
103
|
if defined?(JRUBY_VERSION)
|
103
104
|
# Not terribly readable. The result on Java is different because JANSI uses semicolons when there are several codes.
|
104
105
|
assert_io(
|
105
|
-
" \e[
|
106
|
-
"\
|
106
|
+
" \e[90mGiven \e[0m\e[90mI have \e[0m\e[90m\e[1m999\e[0m\e[90m cukes in my belly\e[0m\n" +
|
107
|
+
"\e[1A \e[32mGiven \e[0m\e[32mI have \e[0m\e[32;1m999\e[0m\e[32m cukes in my belly\e[0m\n"
|
107
108
|
)
|
108
109
|
else
|
109
110
|
assert_io(
|
110
|
-
" #{
|
111
|
+
" #{grey('Given ')}#{grey('I have ')}#{grey(bold('999'))}#{grey(' cukes in my belly')}\n" +
|
111
112
|
"\033[1A #{green('Given ')}#{green('I have ')}#{green(bold('999'))}#{green(' cukes in my belly')}\n"
|
112
113
|
)
|
113
114
|
end
|
@@ -167,8 +168,8 @@ Feature: Feature Description
|
|
167
168
|
|
168
169
|
it "should escape backslashes and pipes" do
|
169
170
|
io = StringIO.new
|
170
|
-
|
171
|
-
|
171
|
+
f = Gherkin::Formatter::PrettyFormatter.new(io, true, false)
|
172
|
+
f.__send__(:table, [Gherkin::Formatter::Model::Row.new([], ['|', '\\'], nil)])
|
172
173
|
io.string.should == ' | \\| | \\\\ |' + "\n"
|
173
174
|
end
|
174
175
|
end
|
@@ -96,24 +96,24 @@ module Gherkin
|
|
96
96
|
]
|
97
97
|
end
|
98
98
|
|
99
|
-
it "should allow multiline
|
100
|
-
scan("Background: I have several\n Lines to look at\n
|
99
|
+
it "should allow multiline descriptions ending at eof" do
|
100
|
+
scan("Background: I have several\n Lines to look at\n None starting with Given")
|
101
101
|
@listener.to_sexp.should == [
|
102
|
-
[:background, "Background", "I have several", "Lines to look at\
|
102
|
+
[:background, "Background", "I have several", " Lines to look at\n None starting with Given", 1],
|
103
103
|
[:eof]
|
104
104
|
]
|
105
105
|
end
|
106
106
|
|
107
|
-
it "should allow multiline
|
107
|
+
it "should allow multiline descriptions, including whitespace" do
|
108
108
|
scan(%{Feature: Hi
|
109
109
|
Background: It is my ambition to say
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
in ten sentences
|
111
|
+
what others say
|
112
|
+
in a whole book.
|
113
113
|
Given I am a step})
|
114
114
|
@listener.to_sexp.should == [
|
115
115
|
[:feature, "Feature", "Hi", "", 1],
|
116
|
-
[:background, "Background", "It is my ambition to say", "in ten sentences\
|
116
|
+
[:background, "Background", "It is my ambition to say", "in ten sentences\n what others say \nin a whole book.",2],
|
117
117
|
[:step, "Given ", "I am a step", 6],
|
118
118
|
[:eof]
|
119
119
|
]
|
@@ -141,22 +141,22 @@ Given I am a step})
|
|
141
141
|
]
|
142
142
|
end
|
143
143
|
|
144
|
-
it "should allow multiline
|
144
|
+
it "should allow multiline descriptions, including whitespace" do
|
145
145
|
scan(%{Scenario: It is my ambition to say
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
146
|
+
in ten sentences
|
147
|
+
what others say
|
148
|
+
in a whole book.
|
149
|
+
Given I am a step
|
150
|
+
})
|
151
151
|
@listener.to_sexp.should == [
|
152
|
-
[:scenario, "Scenario", "It is my ambition to say", "in ten sentences\nwhat others say\
|
152
|
+
[:scenario, "Scenario", "It is my ambition to say", "in ten sentences\nwhat others say \n in a whole book.", 1],
|
153
153
|
[:step, "Given ", "I am a step", 5],
|
154
154
|
[:eof]
|
155
155
|
]
|
156
156
|
end
|
157
157
|
|
158
158
|
it "should allow multiline names ending at eof" do
|
159
|
-
scan("Scenario: I have several\
|
159
|
+
scan("Scenario: I have several\nLines to look at\n None starting with Given")
|
160
160
|
@listener.to_sexp.should == [
|
161
161
|
[:scenario, "Scenario", "I have several", "Lines to look at\nNone starting with Given", 1],
|
162
162
|
[:eof]
|
@@ -165,9 +165,9 @@ Given I am a step})
|
|
165
165
|
|
166
166
|
it "should ignore gherkin keywords embedded in other words" do
|
167
167
|
scan(%{Scenario: I have a Button
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
Buttons are great
|
169
|
+
Given I have some
|
170
|
+
But I might not because I am a Charles Dickens character
|
171
171
|
})
|
172
172
|
@listener.to_sexp.should == [
|
173
173
|
[:scenario, "Scenario", "I have a Button", "Buttons are great", 1],
|
@@ -179,7 +179,7 @@ Given I am a step})
|
|
179
179
|
|
180
180
|
it "should allow step keywords in Scenario names" do
|
181
181
|
scan(%{Scenario: When I have when in scenario
|
182
|
-
|
182
|
+
I should be fine
|
183
183
|
Given I am a step
|
184
184
|
})
|
185
185
|
@listener.to_sexp.should == [
|
@@ -192,14 +192,15 @@ Given I am a step
|
|
192
192
|
|
193
193
|
describe "Scenario Outlines" do
|
194
194
|
it "should be parsed" do
|
195
|
-
scan(
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
195
|
+
scan(<<-HERE)
|
196
|
+
Scenario Outline: Hello
|
197
|
+
With a description
|
198
|
+
Given a <what> cucumber
|
199
|
+
Examples: With a name
|
200
|
+
and a description
|
201
|
+
|what|
|
202
|
+
|green|
|
203
|
+
HERE
|
203
204
|
@listener.to_sexp.should == [
|
204
205
|
[:scenario_outline, "Scenario Outline", "Hello", "With a description", 1],
|
205
206
|
[:step, "Given ", "a <what> cucumber", 3],
|
@@ -224,15 +225,15 @@ Given I am a step
|
|
224
225
|
end
|
225
226
|
|
226
227
|
it "should allow multiline description" do
|
227
|
-
scan(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
@listener.to_sexp.should == [
|
235
|
-
[:scenario_outline, "Scenario Outline", "It is my ambition to say", "in ten sentences\
|
228
|
+
scan(<<-HERE)
|
229
|
+
Scenario Outline: It is my ambition to say
|
230
|
+
in ten sentences
|
231
|
+
what others say
|
232
|
+
in a whole book.
|
233
|
+
Given I am a step
|
234
|
+
HERE
|
235
|
+
@listener.to_sexp.should == [
|
236
|
+
[:scenario_outline, "Scenario Outline", "It is my ambition to say", "in ten sentences\n what others say \nin a whole book.", 1],
|
236
237
|
[:step, "Given ", "I am a step", 5],
|
237
238
|
[:eof]
|
238
239
|
]
|
@@ -255,11 +256,11 @@ Given I am a step
|
|
255
256
|
|
256
257
|
it "should parse multiline example names" do
|
257
258
|
scan(%{Examples: I'm a multiline name
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
259
|
+
and I'm ok
|
260
|
+
f'real
|
261
|
+
|x|
|
262
|
+
|5|
|
263
|
+
})
|
263
264
|
@listener.to_sexp.should == [
|
264
265
|
[:examples, "Examples", "I'm a multiline name", "and I'm ok\nf'real", 1],
|
265
266
|
[:row, ["x"], 4],
|
@@ -34,6 +34,11 @@ module Gherkin
|
|
34
34
|
@listener.should_receive(:row).with(r(['|', 'the', '\a', '\\', '|\\|']), 1)
|
35
35
|
scan('| \| | the | \a | \\ | \|\\\| |' + "\n")
|
36
36
|
end
|
37
|
+
|
38
|
+
it "should parse cells with newlines" do
|
39
|
+
@listener.should_receive(:row).with(r(["\n"]), 1)
|
40
|
+
scan("|\\n|" + "\n")
|
41
|
+
end
|
37
42
|
|
38
43
|
it "should parse cells with spaces within the content" do
|
39
44
|
@listener.should_receive(:row).with(r(["Dill pickle", "Valencia orange"]), 1)
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 2
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 2.3.
|
8
|
+
- 2
|
9
|
+
version: 2.3.2
|
10
10
|
platform: universal-dotnet
|
11
11
|
authors:
|
12
12
|
- Mike Sassak
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-12-05 00:00:00 +00:00
|
20
20
|
default_executable: gherkin
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -282,7 +282,7 @@ rubyforge_project:
|
|
282
282
|
rubygems_version: 1.3.7
|
283
283
|
signing_key:
|
284
284
|
specification_version: 3
|
285
|
-
summary: gherkin-2.3.
|
285
|
+
summary: gherkin-2.3.2
|
286
286
|
test_files:
|
287
287
|
- features/escaped_pipes.feature
|
288
288
|
- features/feature_parser.feature
|