cukecooker 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.
Files changed (3) hide show
  1. data/README.rdoc +24 -0
  2. data/bin/cukecooker +581 -0
  3. metadata +67 -0
data/README.rdoc ADDED
@@ -0,0 +1,24 @@
1
+ == Cukecooker
2
+
3
+ Cukecooker aids you in writing Cucumber scenarios.
4
+
5
+ It generates an HTML with your project's step definitions and lets you
6
+ use them to write scenarios in a comfortable way, with autocomplete
7
+ and text inputs for group matches.
8
+
9
+ === Usage
10
+
11
+ In your project root, which should contain a features folder, run this
12
+ in a terminal:
13
+
14
+ cukecooker
15
+
16
+ Or:
17
+
18
+ cukecooker path/to/your/project
19
+
20
+ If everything went well you should see
21
+
22
+ Done! Now open cukecooker-project.html in a browser.
23
+
24
+ Go ahead, try it!
data/bin/cukecooker ADDED
@@ -0,0 +1,581 @@
1
+ StepRegex = %r(\s*(?:When|Given|Then).*/\^(.+)\$\/\s*do(\s*\|.*\|)?)
2
+
3
+ steps = ""
4
+
5
+ # Checks that dir exists and is a directory
6
+ def check_dir(dir)
7
+ unless File.exists?(dir)
8
+ puts "Error: #{dir} does not exist"
9
+ exit
10
+ end
11
+
12
+ unless File.directory?(dir)
13
+ puts "Error: #{dir} is not a directory"
14
+ exit
15
+ end
16
+ end
17
+
18
+ # Get command line directory or default to current
19
+ dir = ARGV[0] || "."
20
+ dir = File.expand_path(dir)
21
+ check_dir dir
22
+
23
+ # Asume project name is the name of the given directory.
24
+ # If it's src, go one level up
25
+ name = File.basename dir
26
+ if name == 'src'
27
+ name = File.basename File.dirname(dir)
28
+ end
29
+
30
+ dir = "#{dir}/features/step_definitions"
31
+ check_dir dir
32
+
33
+ step_files = Dir["#{dir}/**/*.rb"]
34
+ step_files.each do |step_file|
35
+ lines = File.readlines step_file
36
+ lines.each do |line|
37
+ next unless match = StepRegex.match(line)
38
+
39
+ regexp = match[1]
40
+ next if regexp.empty?
41
+
42
+ steps << "," unless steps.empty?
43
+ if match[2]
44
+ parameters = match[2].strip[1 ... -1].split(',').map!{|x| "'#{x.strip.gsub('_', ' ')}'"}.join(',') if match[2]
45
+ steps << "[/^#{regexp}$/, [#{parameters}]]"
46
+ else
47
+ steps << "[/^#{regexp}$/]"
48
+ end
49
+ end
50
+ end
51
+
52
+ if steps.empty?
53
+ puts "Error: no step definitions found in #{dir}/features/step_definitions"
54
+ puts " Be sure to run cukecooker in a directory which contains a features/step_definitions directory"
55
+ puts " or give cukecooker the path to that directory."
56
+ exit
57
+ end
58
+
59
+ OrRegexp = '/\(\?\:.*?\|.*?\)/'
60
+ OptionalRegexp = '/.\?/'
61
+ OptionalWithParenRegexp = '/\(\?\:.*?\)\?/'
62
+ CaptureRegepx = '/\(.+?\)/'
63
+ CaptureWithQuotesRegepx = '/"\(.+?\)"/'
64
+ RegexpRegexp = '/\\\\\//'
65
+
66
+ File.open("cukecooker-#{name}.html", "w") do |file|
67
+ file.write <<EOF
68
+ <html>
69
+ <head>
70
+ <title>cukecooker - #{name}</title>
71
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
72
+ <script type="text/javascript">
73
+ // The original step definitions, extracted from the step definition files.
74
+ // Each element in the array is an array whose first element is the regular expression
75
+ // and the next element is an (optional) array containing the parameters given in the do block.
76
+ originalSteps = [#{steps}];
77
+
78
+ // The expanded steps (splitted by ors or optional regexps).
79
+ // Each element in the array is a hash with the following properties:
80
+ // regexp: the regular expression to deal with
81
+ // params: an array of parameters that were given in the do block
82
+ // regexpReplaced: the regular expression with groups replaced with params
83
+ steps = [];
84
+
85
+ // The index of the selected step.
86
+ selectedStepIndex = 0;
87
+
88
+ // The indices of the li's that are shown.
89
+ liIndices = [];
90
+
91
+ // The selected step once the user pressed enter.
92
+ buildingStep = null;
93
+
94
+ // The current action entered by the user
95
+ currentAction = "Given ";
96
+
97
+ // The last action entered by the user
98
+ lastAction = '';
99
+
100
+ // Is the current action different than the last action?
101
+ lastActionChanged = false;
102
+
103
+ // The text that was in #stepMatch before a keyup event was fired
104
+ lastStepMatchText = '';
105
+
106
+ // Is the user creating the first step in the scenario?
107
+ firstStepInScenario = true;
108
+
109
+ allLiIndices = [];
110
+
111
+ // HACK: keyup event if fired twice... why? :'-(
112
+ justBuiltAStep = false;
113
+
114
+ $(function() {
115
+ $steps = $("#steps");
116
+ $stepBuilder = $("#stepBuilder");
117
+ $scenario = $("#scenario");
118
+ $stepMatch = $("#stepMatch");
119
+ $stepMatch_static = $("#stepMatch_static");
120
+ $explanationStep = $("#explanationStep");
121
+ $explanationStepBuilder = $("#explanationStepBuilder");
122
+
123
+ // Push all elements into array
124
+ function pushAll(array, elements) {
125
+ for(var i = 0; i < elements.length; i++)
126
+ array.push(elements[i]);
127
+ }
128
+
129
+ // Split the regexp into their expansions.
130
+ // For example:
131
+ // "(?:a|b) c" --> ["a c", "b c"]
132
+ // "(?:a )?b" --> ["a b", "b"]
133
+ // "(?:a )?(?:b|c) d" --> ["a b d", "a c d", "b d", "c d"]
134
+ function splitRegexp(regexp) {
135
+ var results = [];
136
+
137
+ // Find an OR expression
138
+ var match = regexp.match(#{OrRegexp});
139
+ if (match) {
140
+ match = match[0];
141
+ var idx = regexp.indexOf(match);
142
+ // Remove (?: ... )
143
+ match = match.substring(3, match.length - 1);
144
+ var pieces = match.split("\|");
145
+ for(var i = 0; i < pieces.length; i++) {
146
+ var before = regexp.substring(0, idx);
147
+ var after = regexp.substring(idx + match.length + 4);
148
+ var newRegexp = before + pieces[i] + after;
149
+ pushAll(results, splitRegexp(newRegexp));
150
+ }
151
+ return results;
152
+ }
153
+
154
+ // Find optional expression with parenthesis
155
+ match = regexp.match(#{OptionalWithParenRegexp});
156
+ if (match) {
157
+ match = match[0];
158
+ var idx = regexp.indexOf(match);
159
+ var before = regexp.substring(0, idx);
160
+ var after = regexp.substring(idx + match.length);
161
+ // This is without the surrounding (?: ... )?
162
+ var middle = match.substring(3, match.length - 2);
163
+ var regexpWithout = before + after;
164
+ var regexpWith = before + middle + after;
165
+ pushAll(results, splitRegexp(regexpWithout));
166
+ pushAll(results, splitRegexp(regexpWith));
167
+ return results;
168
+ }
169
+
170
+ // Find optional expression without parenthesis
171
+ match = regexp.match(#{OptionalRegexp});
172
+ if (match) {
173
+ match = match[0];
174
+ var idx = regexp.indexOf(match);
175
+ var before = regexp.substring(0, idx);
176
+ var after = regexp.substring(idx + match.length);
177
+ // This is without the ?
178
+ var middle = match.substring(0, match.length - 1);
179
+ var regexpWithout = before + after;
180
+ var regexpWith = before + middle + after;
181
+ pushAll(results, splitRegexp(regexpWithout));
182
+ pushAll(results, splitRegexp(regexpWith));
183
+ return results;
184
+ }
185
+
186
+ // Replace \/ with /
187
+ while(regexp.indexOf("\\\\/") >= 0) {
188
+ regexp = regexp.replace(#{RegexpRegexp}, "/");
189
+ }
190
+
191
+ return [regexp];
192
+ }
193
+
194
+ // Processes each group in the step regexp with the given callback. The callback
195
+ // receives the index of the matched group and must return a replacement
196
+ // for it.
197
+ function processGroups(step, callback) {
198
+ paramIdx = 0;
199
+ regexp = step.regexp;
200
+ while(true) {
201
+ m = regexp.match(#{CaptureRegepx});
202
+ if (!m) break;
203
+
204
+ m = m[0];
205
+ idx = regexp.indexOf(m);
206
+ regexp = regexp.substring(0, idx) + callback(paramIdx) + regexp.substring(idx + m.length);
207
+ paramIdx++;
208
+ }
209
+ return regexp;
210
+ }
211
+
212
+ // Transforms 'foo "(...)" bar' into 'foo (...) bar'
213
+ function removeQuotedGroups(step) {
214
+ regexp = step.regexp;
215
+ while(true) {
216
+ m = regexp.match(#{CaptureWithQuotesRegepx});
217
+ if (!m) break;
218
+
219
+ m = m[0];
220
+ idx = regexp.indexOf(m);
221
+ regexp = regexp.substring(0, idx) + m.substring(1, m.length -1) + regexp.substring(idx + m.length);
222
+ }
223
+ return regexp;
224
+ }
225
+
226
+ // Appends the step just built to the scenario div.
227
+ function appendToScenario() {
228
+ replacement = processGroups(buildingStep, function(idx) {
229
+ return '<span class="param">' + $("#p" + idx).val() + '</span>';
230
+ });
231
+ replacement = "<strong>" + currentAction + "</strong>" + replacement;
232
+ if (currentAction == "And ") {
233
+ replacement = "&nbsp;&nbsp;" + replacement;
234
+ } else if (lastActionChanged && !firstStepInScenario) {
235
+ replacement = "<br/>" + replacement;
236
+ }
237
+ regexp = buildingStep.regexp;
238
+ if (regexp[regexp.length - 1] == ':') {
239
+ if (currentAction == "And ") {
240
+ replacement += '<br/>&nbsp;&nbsp;"""<br/>&nbsp;&nbsp;TODO: text or table goes here&nbsp;&nbsp;<br/>&nbsp;&nbsp;"""';
241
+ } else {
242
+ replacement += '<br/>"""<br/>TODO: text or table goes here<br/>"""';
243
+ }
244
+ }
245
+ firstStepInScenario = false;
246
+ justBuiltAStep = true;
247
+ $scenario.append(replacement + "<br/>");
248
+ }
249
+
250
+ // Prepares the page for building steps (input texts for parameters)
251
+ function prepareBuildStep() {
252
+ buildingStep = steps[currentLiIndex()];
253
+
254
+ // Set the current action and see if it changed from the last one
255
+ if (lastAction == currentAction && currentAction != "And ") {
256
+ currentAction = "And ";
257
+ lastActionChanged = false;
258
+ } else {
259
+ lastActionChanged = true;
260
+ }
261
+ lastAction = currentAction;
262
+
263
+ groupsCount = 0;
264
+ processGroups(buildingStep, function(idx) {
265
+ groupsCount++;
266
+ return '';
267
+ });
268
+
269
+ if (groupsCount == 0) {
270
+ appendToScenario();
271
+ searchAgain();
272
+ } else {
273
+ $explanationStep.hide();
274
+ $explanationStepBuilder.show();
275
+ $stepMatch.attr('readonly', 'readonly');
276
+ $stepMatch.val(currentAction + buildingStep.regexpReplaced);
277
+ $steps.hide();
278
+ $stepBuilder.show();
279
+
280
+ regexpWithoutQuotes = removeQuotedGroups(buildingStep);
281
+ originalRegexp = buildingStep.regexp;
282
+ buildingStep.regexp = regexpWithoutQuotes;
283
+
284
+ html = '<table><tr><td><nobr><strong>' + currentAction + "</strong>";
285
+ html += processGroups(buildingStep, function(idx) {
286
+ return '</nobr></td><td width="100"><input type="text" id="p' + idx + '" class="complete"></td><td><nobr>';
287
+ });
288
+ html += '</td></tr><tr align="center"><td>';
289
+ for(var i = 0; i < groupsCount; i++) {
290
+ html += '</td><td class="param">' + buildingStep.params[i] + "</td><td>";
291
+ }
292
+ html += "</td></tr></table>";
293
+ $stepBuilder.html(html);
294
+
295
+ $("#p0").focus();
296
+
297
+ buildingStep.regexp = originalRegexp;
298
+ }
299
+ }
300
+
301
+ // Resets everything except the scenario div to search a new step.
302
+ function searchAgain() {
303
+ selectedStepIndex = 0;
304
+ liIndices = allLiIndices;
305
+ $explanationStep.show();
306
+ $explanationStepBuilder.hide();
307
+ $stepMatch.attr('readonly', '');
308
+ $steps.show();
309
+ $stepBuilder.hide();
310
+ $stepsLi.show();
311
+ $stepsLi.removeClass("selected");
312
+ stepLi(0).addClass("selected");
313
+ $stepMatch.val("");
314
+ $stepMatch.focus();
315
+ $steps.scrollTop(0);
316
+ }
317
+
318
+ // Returns the index of the currently selected <li>
319
+ function currentLiIndex() {
320
+ return liIndices[selectedStepIndex];
321
+ }
322
+
323
+ // Returns the selected <li> as a jQuery object
324
+ function currentLi() {
325
+ return $($stepsLi[currentLiIndex()]);
326
+ }
327
+
328
+ // Returns a step <li> as a jQuery object
329
+ function stepLi(idx) {
330
+ return $($stepsLi[idx]);
331
+ }
332
+
333
+ // Split original steps and create the steps array
334
+ for(var i = 0; i < originalSteps.length; i++) {
335
+ originalStep = originalSteps[i];
336
+ step = originalStep[0].toString();
337
+ // Remove /^ and $/
338
+ s = step.substring(2, step.length - 2);
339
+ splits = splitRegexp(s);
340
+ for(var j = 0; j < splits.length; j++) {
341
+ split = splits[j];
342
+
343
+ newStep = {}
344
+ newStep.regexp = split
345
+ newStep.params = originalStep.length == 1 ? [] : originalStep[1]
346
+ newStep.regexpReplaced = processGroups(newStep, function(idx) {
347
+ return newStep.params[idx];
348
+ });
349
+
350
+ steps.push(newStep);
351
+
352
+ allLiIndices.push(allLiIndices.length);
353
+ }
354
+ }
355
+
356
+ liIndices = allLiIndices;
357
+
358
+ // Sort steps according to regexps, alphabetically
359
+ steps.sort(function(a, b) {
360
+ x = a.regexp.toLowerCase();
361
+ y = b.regexp.toLowerCase();
362
+ return x < y ? -1 : (x > y ? 1 : 0);
363
+ });
364
+
365
+ // Write the steps in the <li>s
366
+ for(var i = 0; i < steps.length; i++) {
367
+ step = steps[i];
368
+ replacement = processGroups(step, function(idx) {
369
+ return '<span class="param">' + step.params[idx] + '</span>';
370
+ });
371
+ $steps.append("<li>" + replacement + "</li>");
372
+ }
373
+
374
+ $stepsLi = $steps.find("li");
375
+
376
+ // Highlight the first selected step
377
+ stepLi(0).addClass("selected");
378
+
379
+ // Focus the text input to write the match
380
+ $stepMatch.focus();
381
+
382
+ // When pressing a key in the step match input, filter the steps
383
+ $stepMatch.keyup(function(ev) {
384
+ if (justBuiltAStep) {
385
+ justBuiltAStep = false;
386
+ return;
387
+ }
388
+
389
+ text = $stepMatch.val();
390
+
391
+ // If the text didn't change and it's not enter, do nothing
392
+ if (text == lastStepMatchText && ev.keyCode != 13)
393
+ return true;
394
+
395
+ // These are the arrow keys, home, end, etc. We can ignore them.
396
+ if (ev.keyCode >= 33 && ev.keyCode <= 40) {
397
+ return true;
398
+ }
399
+
400
+ lastStepMatchText = text;
401
+
402
+ // If the user pressed enter, selected the step
403
+ if (ev.keyCode == 13) {
404
+ prepareBuildStep();
405
+ return false;
406
+ }
407
+
408
+ // Unselect the current step
409
+ currentLi().removeClass("selected");
410
+
411
+ // See if we the text starts with an action and remove it
412
+ foundMatch = false;
413
+ if (text.match(/^when /i)) {
414
+ text = text.substring(5);
415
+ currentAction = "When ";
416
+ }
417
+ if (text.match(/^then /i)) {
418
+ text = text.substring(5);
419
+ currentAction = "Then ";
420
+ }
421
+ if (text.match(/^given /i)) {
422
+ text = text.substring(6);
423
+ currentAction = "Given ";
424
+ }
425
+ if (text.match(/^and /i)) {
426
+ text = text.substring(4);
427
+ currentAction = "And ";
428
+ }
429
+
430
+ // Do the filtering
431
+ text = eval("/^" + text + "/i");
432
+ liIndices = [];
433
+
434
+ selectedStepIndex = -1;
435
+ for(var i = 0; i < steps.length; i++) {
436
+ step = steps[i];
437
+ $stepLi = stepLi(i);
438
+ if (step.regexpReplaced.match(text)) {
439
+ $stepLi.show();
440
+ if (selectedStepIndex == -1) {
441
+ $stepLi.addClass("selected");
442
+ selectedStepIndex = 0;
443
+ }
444
+ liIndices.push(i);
445
+ } else {
446
+ $stepLi.hide();
447
+ }
448
+ }
449
+ });
450
+
451
+ // When pressing up or down on the stepMatch input,
452
+ // change the selected step
453
+ $stepMatch.keydown(function(ev) {
454
+ if (selectedStepIndex == -1) return;
455
+
456
+ // Up, Down, PageUp or PageDown
457
+ if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) {
458
+ currentLi().removeClass("selected");
459
+
460
+ var increment = 0;
461
+ switch(ev.keyCode) {
462
+ case 33: increment = -10; break;
463
+ case 34: increment = 10; break;
464
+ case 38: increment = -1; break;
465
+ case 40: increment = 1; break;
466
+ }
467
+
468
+ selectedStepIndex += increment;
469
+
470
+ if (selectedStepIndex < 0) selectedStepIndex = 0;
471
+ if (selectedStepIndex > liIndices.length - 1) selectedStepIndex = liIndices.length - 1;
472
+
473
+ current = currentLi();
474
+ current.addClass("selected");
475
+ $steps.scrollTop(0);
476
+ $steps.scrollTop(current.position().top - 90);
477
+ return false;
478
+ }
479
+
480
+ return true;
481
+ });
482
+
483
+ // When hovering an <li>, highglight it
484
+ $stepsLi.mouseenter(function(ev) {
485
+ currentLi().removeClass("selected");
486
+ selectedStepIndex = $stepsLi.index(this);
487
+ currentLi().addClass("selected");
488
+ });
489
+
490
+ // When clicking an <li>, build it
491
+ $stepsLi.click(function(ev) {
492
+ selectedStepIndex = $stepsLi.index(this);
493
+ prepareBuildStep();
494
+ });
495
+
496
+ // When pressing enter or tab in the step building inputs, go
497
+ // to the next one or write to scenario if it's the last one
498
+ $(".complete").live("keyup", function(ev) {
499
+ // When pressing ESC, go back to search
500
+ if (ev.keyCode == 27) {
501
+ searchAgain();
502
+ return false;
503
+ }
504
+
505
+ // Enter
506
+ if (ev.keyCode == 13) {
507
+ $this = $(this);
508
+ value = $this.val();
509
+ if (value == '')
510
+ return false;
511
+
512
+ id = $this.attr("id").substring(1);
513
+ id = parseInt(id) + 1;
514
+ $next = $("#p" + id);
515
+ if ($next.length > 0) {
516
+ $next.focus();
517
+ } else {
518
+ appendToScenario();
519
+ searchAgain();
520
+ }
521
+ return false;
522
+ }
523
+ return true;
524
+ });
525
+
526
+ // Clear the scenario
527
+ $("#clear").click(function() {
528
+ $scenario.html("");
529
+ searchAgain();
530
+ firstStepInScenario = true;
531
+ currentAction = "Given ";
532
+ });
533
+ });
534
+ </script>
535
+ <style>
536
+ body { font-family: "Helvetica Neue",Arial,Helvetica,sans-serif; font-size:85%; height:80% }
537
+ table { font-size:100%; }
538
+ ul { list-style-type:none; margin:0px; padding:0px;}
539
+ li { padding: 4px; cursor: pointer;}
540
+ h3 { padding: 0px; margin:0px; }
541
+ #steps, #stepBuilder { height: 40%; position: relative; overflow: auto; border: 1px solid black; background-color: #DFDFEF; margin-top: 20px;}
542
+ #scenarioContainer { margin-top:20px; border: 1px solid black; padding:10px; background-color: #EFEF99; }
543
+ #scenario { font-size: 105%; padding-left: 20px; margin-top: 10px;}
544
+ #container { min-height: 100%; position: relative; }
545
+ #separator { height: 5%; }
546
+ #logo { position: absolute; bottom: 0px; right: 4px; height: 20px; width: 100%; text-align: right; font-weight:bold;}
547
+ .param { font-weight: bold; color: blue;}
548
+ .selected { background-color: #BBE; padding:4px;}
549
+ .complete { width:100%; }
550
+ </style>
551
+ </head>
552
+ <body>
553
+ <div id="container">
554
+ <h3 id="explanationStep">
555
+ Write a step as you would write it in cucumber (including given, when, then, and) and press enter when you have selected one.
556
+ </h3>
557
+ <h3 id="explanationStepBuilder" style="display:none">
558
+ Now fill in the fields for the step (press tab or enter to change between fields, or escape to search another step).
559
+ </h3>
560
+ <input type="text" id="stepMatch" size="100" />
561
+ <ul id="steps">
562
+ </ul>
563
+ <div id="stepBuilder" style="display:none">
564
+ </div>
565
+ <div id="scenarioContainer">
566
+ <h3>Scenario <a href="javascript:void(0)" id="clear" style="margin-left:10px">clear</a></h3>
567
+ <div id="scenario">
568
+ </div>
569
+ </div>
570
+ </div>
571
+ <div id="separator">
572
+ </div>
573
+ <div id="logo">
574
+ #{name} - cukecooker :)
575
+ </div>
576
+ </body>
577
+ </html>
578
+ EOF
579
+ end
580
+
581
+ puts "Done! Now open cukecooker-#{name}.html in a browser."
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cukecooker
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Ary Borenszweig
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-08 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: aborenszweig@manas.com.ar
23
+ executables:
24
+ - cukecooker
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - bin/cukecooker
31
+ - README.rdoc
32
+ has_rdoc: true
33
+ homepage: https://github.com/asterite/cukecooker
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - .
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.7
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Write cucumber scenarios with aid
66
+ test_files: []
67
+