gherkin 2.3.1 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
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 RL_LANG environment variable is set, only the parsers for the languages specified there will be built.
43
- E.g. in Bash, export RL_LANG="en,fr,no". This can be quite helpful when modifying the Ragel grammar.
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
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>magenta</tt>
17
- # <tt>executing_arg</tt>:: defaults to <tt>magenta,bold</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' => 'magenta',
52
+ 'executing' => 'grey',
53
53
  'failed' => 'red',
54
54
  'passed' => 'green',
55
55
  'outline' => 'cyan',
@@ -8,7 +8,7 @@ module Gherkin
8
8
  #
9
9
  # This is used in the pretty formatter.
10
10
  def escape_cell(s)
11
- s.gsub(/\|/, "\\|").gsub(/\\(?!\|)/, "\\\\\\\\")
11
+ s.gsub(/\\(?!\|)/, "\\\\\\\\").gsub(/\n/, "\\n").gsub(/\|/, "\\|")
12
12
  end
13
13
  end
14
14
  end
@@ -94,7 +94,7 @@ module Gherkin
94
94
 
95
95
  def encode64s(data)
96
96
  # Strip newlines
97
- encode64(data).gsub(/\n/, '')
97
+ Base64.encode64(data).gsub(/\n/, '')
98
98
  end
99
99
  end
100
100
  end
@@ -14,10 +14,11 @@ module Gherkin
14
14
  include Colors
15
15
  include Escaping
16
16
 
17
- def initialize(io, monochrome=false)
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
- match(Model::Match.new([], nil)) if @monochrome
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 = defined?(JRUBY_VERSION) ? Java.java.io.StringWriter.new : StringIO.new
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
- if defined?(JRUBY_VERSION)
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.rewind
167
- io.read
162
+ io.string
168
163
  end
169
164
 
170
165
  private
@@ -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->eol); \
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
- strip_i(VALUE str, VALUE ary)
247
+ unindent(VALUE con, int start_col)
251
248
  {
252
- rb_funcall(str, rb_intern("strip!"), 0);
253
- rb_ary_push(ary, str);
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 eol)
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
- VALUE split = multiline_strip(con);
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("strip!"), 0);
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
- // Gherkin will crash gracefully if the string representation of start_col pushes the pattern past 32 characters
329
- char pat[32];
330
- snprintf(pat, 32, "^[\t ]{0,%d}", start_col);
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 {
@@ -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 = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart));
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 = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart));
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 = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart));
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 = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart));
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 = nameAndDescriptionWithPlatformNewlinesIntact(keywordContent(data, p, eof, nextKeywordStart, contentStart));
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.replaceAll("\\\\\\|", "|").replaceAll("\\\\\\\\", "\\\\"));
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 static final Pattern CRLF_RE = Pattern.compile("\r\n");
177
- private static final Pattern LF_RE = Pattern.compile("[^\r]\n");
178
- private static final String CRLF = "\r\n";
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(String s : text.split("\r?\n")) {
189
- if(name == null) {
190
- name = s.trim();
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 count;
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) {
@@ -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
- store_keyword_content(:feature, data, p, eof) { |con| multiline_strip(con) }
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| multiline_strip(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| multiline_strip(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| multiline_strip(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| multiline_strip(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
- #{magenta('Given ')}#{magenta('some stuff')} #{grey('# features/step_definitions/bar.rb:56')}
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
- #{magenta('When ')}#{magenta('foo')} #{grey('# features/step_definitions/bar.rb:96')}
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
- #{magenta('Given ')}#{magenta('some stuff that is longer')} #{grey('# features/step_definitions/bar.rb:56')}
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[35mGiven \e[0m\e[35mI have \e[0m\e[35;1m999\e[0m\e[35m cukes in my belly\e[0m\n" +
106
- "\033[1A \e[32mGiven \e[0m\e[32mI have \e[0m\e[32;1m999\e[0m\e[32m cukes in my belly\e[0m\n"
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
- " #{magenta('Given ')}#{magenta('I have ')}#{magenta(bold('999'))}#{magenta(' cukes in my belly')}\n" +
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
- l = Gherkin::Formatter::PrettyFormatter.new(io, true)
171
- l.__send__(:table, [Gherkin::Formatter::Model::Row.new([], ['|', '\\'], nil)])
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 names ending at eof" do
100
- scan("Background: I have several\n Lines to look at\n None starting with Given")
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\nNone starting with Given", 1],
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 names" do
107
+ it "should allow multiline descriptions, including whitespace" do
108
108
  scan(%{Feature: Hi
109
109
  Background: It is my ambition to say
110
- in ten sentences
111
- what others say
112
- in a whole book.
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\nwhat others say\nin a whole book.",2],
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 names" do
144
+ it "should allow multiline descriptions, including whitespace" do
145
145
  scan(%{Scenario: It is my ambition to say
146
- in ten sentences
147
- what others say
148
- in a whole book.
149
- Given I am a step
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\nin a whole book.", 1],
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\n Lines to look at\n None starting with Given")
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
- Buttons are great
169
- Given I have some
170
- But I might not because I am a Charles Dickens character
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
- I should be fine
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(%{Scenario Outline: Hello
196
- With a description
197
- Given a <what> cucumber
198
- Examples: With a name
199
- and a description
200
- |what|
201
- |green|
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(%{Scenario Outline: It is my ambition to say
228
- in ten sentences
229
- what others say
230
- in a whole book.
231
- Given I am a step
232
-
233
- })
234
- @listener.to_sexp.should == [
235
- [:scenario_outline, "Scenario Outline", "It is my ambition to say", "in ten sentences\nwhat others say\nin a whole book.", 1],
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
- and I'm ok
259
- f'real
260
- |x|
261
- |5|
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
- - 1
9
- version: 2.3.1
8
+ - 2
9
+ version: 2.3.2
10
10
  platform: ruby
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-11-13 00:00:00 +00:00
19
+ date: 2010-12-05 00:00:00 +00:00
20
20
  default_executable: gherkin
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -471,7 +471,7 @@ rubyforge_project:
471
471
  rubygems_version: 1.3.7
472
472
  signing_key:
473
473
  specification_version: 3
474
- summary: gherkin-2.3.1
474
+ summary: gherkin-2.3.2
475
475
  test_files:
476
476
  - features/escaped_pipes.feature
477
477
  - features/feature_parser.feature