specdown 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +1 -0
- data/SPEC.markdown +122 -0
- data/bin/specdown +8 -0
- data/features/command.feature +93 -0
- data/features/fixtures/parser_example.markdown +21 -0
- data/features/parser.feature +108 -0
- data/features/report.feature +58 -0
- data/features/runner.feature +95 -0
- data/features/specdown_examples/no_ruby/specdown/parser_example.markdown +21 -0
- data/features/specdown_examples/with_ruby/specdown/env.rb +2 -0
- data/features/specdown_examples/with_ruby/specdown/parser_example.markdown +21 -0
- data/features/step_definitions/command.rb +21 -0
- data/features/step_definitions/parser.rb +15 -0
- data/features/step_definitions/report.rb +14 -0
- data/features/step_definitions/runner.rb +15 -0
- data/features/support/env.rb +4 -0
- data/lib/specdown/command.rb +28 -0
- data/lib/specdown/node.rb +15 -0
- data/lib/specdown/parser.rb +59 -0
- data/lib/specdown/runner/report.rb +40 -0
- data/lib/specdown/runner/stats.rb +18 -0
- data/lib/specdown/runner.rb +44 -0
- data/lib/specdown/sandbox.rb +4 -0
- data/lib/specdown/tree.rb +8 -0
- data/lib/specdown.rb +9 -0
- metadata +94 -14
data/README.markdown
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This library has not yet been implemented. See SPEC.markdown for info about what it might become.
|
data/SPEC.markdown
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# specdown
|
2
|
+
|
3
|
+
Write your README in markdown, and execute it with specdown. Documentation == Specification == WINNING
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
I love README DRIVEN DEVELOPMENT. I love the free-form nature of writing a README. I dislike the process of copying and transforming my README into cucumber. Gherkin works well for many types of requirements, but I dislike turning readable, natural prose into "Given/When/Then" Gherkin. I dislike turning my readable prose into RSpec even more.
|
8
|
+
|
9
|
+
## How?
|
10
|
+
|
11
|
+
Writing your tests with specdown is as simple as writing a README. I'll show you.
|
12
|
+
|
13
|
+
Let's imagine we're writing a README for a silly little ruby library called "Todo":
|
14
|
+
|
15
|
+
# Todo
|
16
|
+
|
17
|
+
A simple library for managing your todo list.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Go into `irb`, then `require 'todo'`. Type `Todo.List` to see your current to do list:
|
22
|
+
|
23
|
+
Todo.List #==> []
|
24
|
+
|
25
|
+
If at any point, you want to completely reset your Todo.List, simply call `Todo!`:
|
26
|
+
|
27
|
+
Todo!
|
28
|
+
Todo.List.should be_empty
|
29
|
+
|
30
|
+
### Adding/Removing
|
31
|
+
|
32
|
+
Now, add an item to your to-do list by calling it as if it were a method on the `Todo` object:
|
33
|
+
|
34
|
+
Todo.shop_for_groceries
|
35
|
+
|
36
|
+
This added the item to your `Todo.List`:
|
37
|
+
|
38
|
+
Todo.List.should == [:shop_for_groceries]
|
39
|
+
|
40
|
+
You can remove an item from your todo list by adding an exclamation point onto the end of it:
|
41
|
+
Todo.shop_for_groceries!
|
42
|
+
Todo.List.should be_empty
|
43
|
+
|
44
|
+
|
45
|
+
### Bulk operations
|
46
|
+
|
47
|
+
You can also perform bulk operations on your `Todo.List`:
|
48
|
+
|
49
|
+
Todo.edit! do
|
50
|
+
shop
|
51
|
+
dry_cleaning!
|
52
|
+
shave!
|
53
|
+
walk_dogs
|
54
|
+
end
|
55
|
+
|
56
|
+
The items "dry_cleaning" and "shave" were removed from your `Todo.List`, and the items "shop" and "walk_dogs" were added to your Todo.List.
|
57
|
+
|
58
|
+
Todo.List.should == [
|
59
|
+
:shop,
|
60
|
+
:walk_dogs
|
61
|
+
]
|
62
|
+
|
63
|
+
Let's call this file "README.markdown", and place it inside a "specdown/" directory:
|
64
|
+
|
65
|
+
$ ls -r
|
66
|
+
specdown/
|
67
|
+
README.markdown
|
68
|
+
|
69
|
+
We can execute this markdown with specdown by simply the `specdown` command. If you'd like to have some code run before the markdown is executed, put it in a ruby file inside a "specdown" directory:
|
70
|
+
|
71
|
+
$ cat > specdown/support.rb
|
72
|
+
|
73
|
+
$LOAD_PATH.unshift './lib'
|
74
|
+
require 'rspec/expectations' # for using "should" matchers in the README
|
75
|
+
require 'todo'
|
76
|
+
|
77
|
+
## Trees and Leafs
|
78
|
+
|
79
|
+
In the README that we wrote for `Todo`, we actually wrote two tests, or scenarios.
|
80
|
+
|
81
|
+
`specdown` creates a tree out of our markdown. In our case, the tree for our `Todo` README looks like this:
|
82
|
+
|
83
|
+
|
84
|
+
#Todo
|
85
|
+
|
|
86
|
+
##Usage
|
87
|
+
/ \
|
88
|
+
/ \
|
89
|
+
/ \
|
90
|
+
/ \
|
91
|
+
/ \
|
92
|
+
/ \
|
93
|
+
/ \
|
94
|
+
###Adding/Removing ###Bulk Operations
|
95
|
+
|
96
|
+
`specdown` will then walk the path from the root to every leaf node in the tree, executing any code it finds along the way.
|
97
|
+
|
98
|
+
Thus, the first "test" will look like this:
|
99
|
+
|
100
|
+
Todo.List #==> []
|
101
|
+
Todo!
|
102
|
+
Todo.List.should be_empty
|
103
|
+
Todo.shop_for_groceries
|
104
|
+
Todo.List.should == [:shop_for_groceries]
|
105
|
+
Todo.shop_for_groceries!
|
106
|
+
Todo.List.should be_empty
|
107
|
+
|
108
|
+
The second "test" looks like this:
|
109
|
+
|
110
|
+
Todo.List
|
111
|
+
Todo!
|
112
|
+
Todo.List.should be_empty
|
113
|
+
Todo.edit! do
|
114
|
+
shop
|
115
|
+
dry_cleaning!
|
116
|
+
shave!
|
117
|
+
walk_dogs
|
118
|
+
end
|
119
|
+
Todo.List.should == [
|
120
|
+
:shop,
|
121
|
+
:walk_dogs
|
122
|
+
]
|
data/bin/specdown
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
Feature: `specdown` command
|
2
|
+
|
3
|
+
Specdown comes with a `specdown` command that allows you to run your tests from the command line.
|
4
|
+
|
5
|
+
By default, running `specdown` at a command prompt will cause specdown to look in the current working directory for a "specdown" directory. If it finds a directory called `specdown`, it will eval all code files it finds, then load all "markdown" files and run them, providing you with a report at the end.
|
6
|
+
|
7
|
+
Scenario: `specdown` with no arguments, and no specdown directory
|
8
|
+
|
9
|
+
When I run `specdown` from the command line in a directory that contains no 'specdown' directory
|
10
|
+
Then I should see the following output:
|
11
|
+
"""
|
12
|
+
0 markdowns
|
13
|
+
0 tests
|
14
|
+
0 failures
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: `specdown` with no arguments, and a specdown directory with a single test
|
18
|
+
|
19
|
+
Given I have a specdown directory containing a single markdown file:
|
20
|
+
"""
|
21
|
+
# Specdown Example
|
22
|
+
|
23
|
+
This is an example specdown file.
|
24
|
+
|
25
|
+
## Child Node
|
26
|
+
|
27
|
+
This section is a child node. It contains some ruby code:
|
28
|
+
|
29
|
+
"simple code".should_not == nil
|
30
|
+
|
31
|
+
### First Leaf
|
32
|
+
|
33
|
+
This section has a failure simulation:
|
34
|
+
|
35
|
+
raise "specdown error simulation!"
|
36
|
+
|
37
|
+
## Last Leaf
|
38
|
+
|
39
|
+
This section is a leaf node. It contains some ruby code:
|
40
|
+
|
41
|
+
1.should == 1
|
42
|
+
"""
|
43
|
+
|
44
|
+
When I run `specdown` with no arguments
|
45
|
+
Then I should see the following output:
|
46
|
+
"""
|
47
|
+
1 markdown
|
48
|
+
2 tests
|
49
|
+
2 failures
|
50
|
+
|
51
|
+
undefined method `should_not' for "simple code":String
|
52
|
+
undefined method `should' for 1:Fixnum
|
53
|
+
"""
|
54
|
+
|
55
|
+
Scenario: `specdown` with no arguments, and a specdown directory containing a single ruby file and a single markdown file
|
56
|
+
Given I have a specdown directory containing a markdown file:
|
57
|
+
"""
|
58
|
+
# Specdown Example
|
59
|
+
|
60
|
+
This is an example specdown file.
|
61
|
+
|
62
|
+
## Child Node
|
63
|
+
|
64
|
+
This section is a child node. It contains some ruby code:
|
65
|
+
|
66
|
+
"simple code".should_not == nil
|
67
|
+
|
68
|
+
### First Leaf
|
69
|
+
|
70
|
+
This section has a failure simulation:
|
71
|
+
|
72
|
+
raise "specdown error simulation!"
|
73
|
+
|
74
|
+
## Last Leaf
|
75
|
+
|
76
|
+
This section is a leaf node. It contains some ruby code:
|
77
|
+
|
78
|
+
1.should == 1
|
79
|
+
"""
|
80
|
+
And a single ruby file:
|
81
|
+
"""
|
82
|
+
require 'rubygems'
|
83
|
+
require 'rspec/expectations'
|
84
|
+
"""
|
85
|
+
When I run `specdown` with no arguments
|
86
|
+
Then I should see the following output:
|
87
|
+
"""
|
88
|
+
1 markdown
|
89
|
+
2 tests
|
90
|
+
1 failure
|
91
|
+
|
92
|
+
specdown error simulation!
|
93
|
+
"""
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Specdown Example
|
2
|
+
|
3
|
+
This is an example specdown file.
|
4
|
+
|
5
|
+
## Child Node
|
6
|
+
|
7
|
+
This section is a child node. It contains some ruby code:
|
8
|
+
|
9
|
+
"simple code".should_not == nil
|
10
|
+
|
11
|
+
### First Leaf
|
12
|
+
|
13
|
+
This section has a failure simulation:
|
14
|
+
|
15
|
+
raise "specdown error simulation!"
|
16
|
+
|
17
|
+
## Last Leaf
|
18
|
+
|
19
|
+
This section is a leaf node. It contains some ruby code:
|
20
|
+
|
21
|
+
1.should == 1
|
@@ -0,0 +1,108 @@
|
|
1
|
+
Feature: Specdown Parser
|
2
|
+
|
3
|
+
The default specdown parser will convert a markdown file into a tree, with the headings inside the markdown forming the nodes of the tree.
|
4
|
+
|
5
|
+
For example, imagine we are given following simple markdown file:
|
6
|
+
|
7
|
+
readme = <<-README
|
8
|
+
|
9
|
+
\# Specdown Example
|
10
|
+
|
11
|
+
This is an example specdown file.
|
12
|
+
|
13
|
+
\## Child Node
|
14
|
+
|
15
|
+
This section is a child node. It contains some ruby code:
|
16
|
+
|
17
|
+
"simple code".should_not == nil
|
18
|
+
|
19
|
+
\### First Leaf
|
20
|
+
|
21
|
+
This section has a failure simulation:
|
22
|
+
|
23
|
+
raise "specdown error simulation!"
|
24
|
+
|
25
|
+
\## Last Leaf
|
26
|
+
|
27
|
+
This section is a leaf node. It contains some ruby code:
|
28
|
+
|
29
|
+
1.should == 1
|
30
|
+
README
|
31
|
+
|
32
|
+
As you can see, this forms a tree, with "# Specdown Example" at the root of the tree, and "## Leaf 1" and "## Leaf 2" as the children / leafs.
|
33
|
+
|
34
|
+
then the Specdown::Parser would turn into the a tree data structure:
|
35
|
+
|
36
|
+
tree = Specdown::Parser.parse readme
|
37
|
+
|
38
|
+
You can ask for the root of the tree object via the `root` method:
|
39
|
+
|
40
|
+
tree.root
|
41
|
+
|
42
|
+
The root returned is a `Specdown::Parser::Node` object. A `Node` responds to four methods: `name`, `code`, `contents`, and `children`.
|
43
|
+
|
44
|
+
tree.root.name.should == "Specdown Example"
|
45
|
+
tree.root.code.should be(nil)
|
46
|
+
tree.root.contents.should == "# Specdown Example\n\nThis is an example specdown file."
|
47
|
+
|
48
|
+
The `children` method is simply an array of `Specdown::Parser::Node` objects, each corresponding to the children (if any) of the current node.
|
49
|
+
|
50
|
+
tree.root.children.first.name.should == "Leaf 1"
|
51
|
+
tree.root.children.first.code.should == "1.should == 1"
|
52
|
+
|
53
|
+
|
54
|
+
Scenario: Parsing a specdown file
|
55
|
+
|
56
|
+
Given the following specdown example file:
|
57
|
+
"""
|
58
|
+
# Specdown Example
|
59
|
+
|
60
|
+
This is an example specdown file.
|
61
|
+
|
62
|
+
## Child Node
|
63
|
+
|
64
|
+
This section is a child node. It contains some ruby code:
|
65
|
+
|
66
|
+
"simple code".should_not == nil
|
67
|
+
|
68
|
+
### First Leaf
|
69
|
+
|
70
|
+
This section has a failure simulation:
|
71
|
+
|
72
|
+
raise "specdown error simulation!"
|
73
|
+
|
74
|
+
## Last Leaf
|
75
|
+
|
76
|
+
This section is a leaf node. It contains some ruby code:
|
77
|
+
|
78
|
+
1.should == 1
|
79
|
+
"""
|
80
|
+
|
81
|
+
When I parse it into a tree:
|
82
|
+
"""
|
83
|
+
@tree = Specdown::Parser.parse @readme
|
84
|
+
"""
|
85
|
+
|
86
|
+
Then the root should be the "h1" section:
|
87
|
+
"""
|
88
|
+
@tree.root.name.should == "Specdown Example"
|
89
|
+
@tree.root.code.should be_empty
|
90
|
+
"""
|
91
|
+
|
92
|
+
And the root should have two children:
|
93
|
+
"""
|
94
|
+
@tree.root.children.length.should == 2
|
95
|
+
|
96
|
+
child_node = @tree.root.children.first
|
97
|
+
first_leaf = child_node.children.first
|
98
|
+
last_leaf = @tree.root.children.last
|
99
|
+
|
100
|
+
child_node.name.should == "Child Node"
|
101
|
+
child_node.code.should == '"simple code".should_not == nil'
|
102
|
+
|
103
|
+
first_leaf.name.should == "First Leaf"
|
104
|
+
first_leaf.code.should == 'raise "specdown error simulation!"'
|
105
|
+
|
106
|
+
last_leaf.name.should == "Last Leaf"
|
107
|
+
last_leaf.code.should == "1.should == 1"
|
108
|
+
"""
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: Report
|
2
|
+
|
3
|
+
Specdown comes with a generic reporting class. If you provide it with a runner's Specdown::Stats object (or an array of Specdown::Stats objects), then it will generate a report for you.
|
4
|
+
|
5
|
+
For example, suppose we have the following Specdown::Stats instance:
|
6
|
+
|
7
|
+
stats = Specdown::Stats.new
|
8
|
+
stats.tests = 3
|
9
|
+
stats.exceptions = [StandardError.new("error simulation")]
|
10
|
+
|
11
|
+
We could then pass it off to our reporter class and receive the following report:
|
12
|
+
|
13
|
+
Specdown::Report.new(stats).generate.should == %{
|
14
|
+
1 markdown
|
15
|
+
3 tests
|
16
|
+
2 successes
|
17
|
+
1 failure
|
18
|
+
|
19
|
+
StandardError: error simulation
|
20
|
+
%}
|
21
|
+
|
22
|
+
|
23
|
+
Scenario: A Specdown::Report instantiated with a single stats object
|
24
|
+
|
25
|
+
Given the following Specdown::Stats instance:
|
26
|
+
"""
|
27
|
+
@stats = Specdown::Stats.new
|
28
|
+
@stats.tests = 3
|
29
|
+
@stats.exceptions << StandardError.new("error simulation")
|
30
|
+
"""
|
31
|
+
|
32
|
+
Then `Specdown::Report.new(@stats).generate` should include the following output:
|
33
|
+
"""
|
34
|
+
1 markdown
|
35
|
+
3 tests
|
36
|
+
1 failure
|
37
|
+
|
38
|
+
error simulation
|
39
|
+
"""
|
40
|
+
|
41
|
+
Scenario: A Specdown::Report instantiated with an array of stats
|
42
|
+
|
43
|
+
Given the following of Specdown::Stats instances:
|
44
|
+
"""
|
45
|
+
@results = [
|
46
|
+
Specdown::Stats.new.tap {|s| s.tests = 3 },
|
47
|
+
Specdown::Stats.new.tap {|s| s.tests = 1; s.exceptions << StandardError.new("error simulation") }
|
48
|
+
]
|
49
|
+
"""
|
50
|
+
|
51
|
+
Then `Specdown::Report.new(@results).generate` should include the following output:
|
52
|
+
"""
|
53
|
+
2 markdowns
|
54
|
+
4 tests
|
55
|
+
1 failure
|
56
|
+
|
57
|
+
error simulation
|
58
|
+
"""
|
@@ -0,0 +1,95 @@
|
|
1
|
+
Feature: Runner
|
2
|
+
|
3
|
+
The `Specdown::Runner` class accepts a markdown parse tree, and runs the tests found within.
|
4
|
+
|
5
|
+
Imagine we start with this markdown file:
|
6
|
+
|
7
|
+
\# Specdown Example
|
8
|
+
|
9
|
+
This is an example specdown file.
|
10
|
+
|
11
|
+
\## Child Node
|
12
|
+
|
13
|
+
This section is a child node. It contains some ruby code:
|
14
|
+
|
15
|
+
"simple code".should_not == nil
|
16
|
+
|
17
|
+
\### First Leaf
|
18
|
+
|
19
|
+
This section has a failure simulation:
|
20
|
+
|
21
|
+
raise "specdown error simulation!"
|
22
|
+
|
23
|
+
\## Last Leaf
|
24
|
+
|
25
|
+
This section is a leaf node. It contains some ruby code:
|
26
|
+
|
27
|
+
1.should == 1
|
28
|
+
|
29
|
+
We then parse it into a Specdown::Tree:
|
30
|
+
|
31
|
+
parse_tree = Specdown::Parser.parse File.read("/path/to/markdown")
|
32
|
+
|
33
|
+
We can now generate a new `Specdown::Runner` instance and run the tests:
|
34
|
+
|
35
|
+
runner = Specdown::Runner.new(parse_tree)
|
36
|
+
runner.run
|
37
|
+
|
38
|
+
While running, it will print results to STDOUT like "F."
|
39
|
+
|
40
|
+
We can access statistics about the run programatically:
|
41
|
+
|
42
|
+
runner.stats.tests #==> 2
|
43
|
+
runner.stats.failures #==> 1
|
44
|
+
runner.stats.successes #==> 1
|
45
|
+
runner.stats.exceptions.map(&:to_s) #==> ['StandardError: "specdown error simulation"']
|
46
|
+
|
47
|
+
Scenario: Running tests
|
48
|
+
|
49
|
+
Given the following specdown example file:
|
50
|
+
"""
|
51
|
+
# Specdown Example
|
52
|
+
|
53
|
+
This is an example specdown file.
|
54
|
+
|
55
|
+
## Child Node
|
56
|
+
|
57
|
+
This section is a child node. It contains some ruby code:
|
58
|
+
|
59
|
+
"simple code".should_not == nil
|
60
|
+
|
61
|
+
### First Leaf
|
62
|
+
|
63
|
+
This section has a failure simulation:
|
64
|
+
|
65
|
+
raise "specdown error simulation!"
|
66
|
+
|
67
|
+
## Last Leaf
|
68
|
+
|
69
|
+
This section is a leaf node. It contains some ruby code:
|
70
|
+
|
71
|
+
1.should == 1
|
72
|
+
"""
|
73
|
+
|
74
|
+
When I parse it into a tree:
|
75
|
+
"""
|
76
|
+
@tree = Specdown::Parser.parse @readme
|
77
|
+
"""
|
78
|
+
|
79
|
+
And I generate a `Specdown::Runner` instance from it:
|
80
|
+
"""
|
81
|
+
@runner = Specdown::Runner.new @tree
|
82
|
+
"""
|
83
|
+
|
84
|
+
Then I should be able to run the tests:
|
85
|
+
"""
|
86
|
+
@runner.run
|
87
|
+
"""
|
88
|
+
|
89
|
+
And I should be able to access the report data programatically:
|
90
|
+
"""
|
91
|
+
@runner.stats.tests #==> 2
|
92
|
+
@runner.stats.failures #==> 1
|
93
|
+
@runner.stats.successes #==> 1
|
94
|
+
@runner.stats.exceptions.map(&:to_s) #==> ['(eval):3:in `execute_test': specdown error simulation!']
|
95
|
+
"""
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Specdown Example
|
2
|
+
|
3
|
+
This is an example specdown file.
|
4
|
+
|
5
|
+
## Child Node
|
6
|
+
|
7
|
+
This section is a child node. It contains some ruby code:
|
8
|
+
|
9
|
+
"simple code".should_not == nil
|
10
|
+
|
11
|
+
### First Leaf
|
12
|
+
|
13
|
+
This section has a failure simulation:
|
14
|
+
|
15
|
+
raise "specdown error simulation!"
|
16
|
+
|
17
|
+
## Last Leaf
|
18
|
+
|
19
|
+
This section is a leaf node. It contains some ruby code:
|
20
|
+
|
21
|
+
1.should == 1
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Specdown Example
|
2
|
+
|
3
|
+
This is an example specdown file.
|
4
|
+
|
5
|
+
## Child Node
|
6
|
+
|
7
|
+
This section is a child node. It contains some ruby code:
|
8
|
+
|
9
|
+
"simple code".should_not == nil
|
10
|
+
|
11
|
+
### First Leaf
|
12
|
+
|
13
|
+
This section has a failure simulation:
|
14
|
+
|
15
|
+
raise "specdown error simulation!"
|
16
|
+
|
17
|
+
## Last Leaf
|
18
|
+
|
19
|
+
This section is a leaf node. It contains some ruby code:
|
20
|
+
|
21
|
+
1.should == 1
|
@@ -0,0 +1,21 @@
|
|
1
|
+
When /^I run `specdown` from the command line in a directory that contains no 'specdown' directory$/ do
|
2
|
+
@output = `bundle exec ruby -I ./lib ./bin/specdown`
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^I should see the following output:$/ do |string|
|
6
|
+
string.split("\n").each do |line|
|
7
|
+
@output.include?(line.strip).should be(true)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^I have a specdown directory containing a (?:single )?markdown file:$/ do |string|
|
12
|
+
@directory = "features/specdown_examples/no_ruby/"
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I run `specdown` with no arguments$/ do
|
16
|
+
@output = `cd #{@directory} && bundle exec ruby -I ../../../lib ../../../bin/specdown`
|
17
|
+
end
|
18
|
+
|
19
|
+
Given /^a single ruby file:$/ do |string|
|
20
|
+
@directory = "features/specdown_examples/with_ruby/"
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Given /^the following specdown example file:$/ do |string|
|
2
|
+
@readme = File.read "features/fixtures/parser_example.markdown"
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I parse it into a tree:$/ do |string|
|
6
|
+
eval string
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the root should be the ".*" section:$/ do |string|
|
10
|
+
eval string
|
11
|
+
end
|
12
|
+
|
13
|
+
Then /^the root should have two children:$/ do |string|
|
14
|
+
eval string
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Given /^the following Specdown::Stats instance:$/ do |string|
|
2
|
+
eval string
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^`(.*)` should include the following output:$/ do |code, string|
|
6
|
+
output = eval code
|
7
|
+
string.split("\n").each do |line|
|
8
|
+
output.should include(line.strip)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Given /^the following of Specdown::Stats instances:$/ do |string|
|
13
|
+
eval string
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
When /^I generate a `Specdown::Runner` instance from it:$/ do |string|
|
2
|
+
eval string
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^I should be able to run the tests:$/ do |string|
|
6
|
+
eval string
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^I should be able to print a report:$/ do |string|
|
10
|
+
eval string
|
11
|
+
end
|
12
|
+
|
13
|
+
Then /^I should be able to access the report data programatically:$/ do |string|
|
14
|
+
eval string
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Specdown
|
2
|
+
class Command
|
3
|
+
def initialize
|
4
|
+
@markdowns = Dir["specdown/**/*.markdown"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
run
|
9
|
+
report
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def run
|
14
|
+
@results =
|
15
|
+
@markdowns.map {|markdown|
|
16
|
+
Parser.parse(File.read(markdown))
|
17
|
+
}.map {|tree|
|
18
|
+
Runner.new(tree)
|
19
|
+
}.map {|runner|
|
20
|
+
runner.run
|
21
|
+
}.collect &:stats
|
22
|
+
end
|
23
|
+
|
24
|
+
def report
|
25
|
+
puts Specdown::Report.new(@results).generate
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Specdown
|
2
|
+
module Parser
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def parse(readme)
|
6
|
+
kramdown = Kramdown::Document.new readme, :input => :markdown
|
7
|
+
build_tree kramdown.root.children
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def build_tree(parsed_elements)
|
12
|
+
tree = Tree.new
|
13
|
+
scan_for_root_node parsed_elements
|
14
|
+
tree.root = consume_section parsed_elements unless parsed_elements.empty?
|
15
|
+
consume_children parsed_elements, tree.root unless parsed_elements.empty?
|
16
|
+
tree
|
17
|
+
end
|
18
|
+
|
19
|
+
def consume_children(parsed_elements, current_parent)
|
20
|
+
current_level = parsed_elements.first.options[:level]
|
21
|
+
raise "Specdown Parse Error: Detected multiple h1 headers in document." if current_level == 1
|
22
|
+
|
23
|
+
unless parsed_elements.empty?
|
24
|
+
current_parent.children << consume_section(parsed_elements, current_parent)
|
25
|
+
end
|
26
|
+
|
27
|
+
unless parsed_elements.empty?
|
28
|
+
next_section_level = parsed_elements.first.options[:level]
|
29
|
+
if next_section_level < current_level
|
30
|
+
consume_children parsed_elements, current_parent.parent
|
31
|
+
elsif next_section_level == current_level
|
32
|
+
consume_children parsed_elements, current_parent
|
33
|
+
else
|
34
|
+
consume_children parsed_elements, current_parent.children.last
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def consume_section(parsed_elements, parent=nil)
|
40
|
+
node = Specdown::Node.new
|
41
|
+
node.name = parsed_elements.shift.options[:raw_text]
|
42
|
+
node.parent = parent
|
43
|
+
|
44
|
+
while !parsed_elements.empty? && parsed_elements.first.type != :header
|
45
|
+
element = parsed_elements.shift
|
46
|
+
node.code += element.value if element.type == :codeblock
|
47
|
+
node.contents += element.value.to_s + element.children.map(&:value).join
|
48
|
+
end
|
49
|
+
node
|
50
|
+
end
|
51
|
+
|
52
|
+
def scan_for_root_node(parsed_elements)
|
53
|
+
until parsed_elements.empty? || (
|
54
|
+
parsed_elements.first.type == :header && parsed_elements.first.options[:level] == 1
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Specdown
|
2
|
+
class Report
|
3
|
+
def initialize(stats)
|
4
|
+
if stats.kind_of? Array
|
5
|
+
@stats = stats
|
6
|
+
else
|
7
|
+
@stats = [stats]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate
|
12
|
+
[
|
13
|
+
format_stat("markdown", markdowns),
|
14
|
+
format_stat("test", tests),
|
15
|
+
format_stat("failure", failures)
|
16
|
+
].join("\n") + "\n\n" + exceptions.join("\n\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def format_stat(word, number)
|
21
|
+
"#{number} #{number == 1 ? word : word + "s"}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def tests
|
25
|
+
@tests ||= @stats.inject(0) {|sum, stat| sum += stat.tests}
|
26
|
+
end
|
27
|
+
|
28
|
+
def failures
|
29
|
+
@failures ||= @stats.inject(0) {|sum, stat| sum += stat.failures}
|
30
|
+
end
|
31
|
+
|
32
|
+
def markdowns
|
33
|
+
@stats.count
|
34
|
+
end
|
35
|
+
|
36
|
+
def exceptions
|
37
|
+
@stats.collect(&:exceptions).flatten.map {|e| [e.to_s, e.backtrace].join "\n"}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Specdown
|
2
|
+
class Runner
|
3
|
+
attr_reader :stats
|
4
|
+
|
5
|
+
def initialize(tree)
|
6
|
+
@tree = tree
|
7
|
+
@stats = Stats.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
depth_first_search @tree.root
|
12
|
+
puts "\n\n"
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def depth_first_search(node, code=[])
|
18
|
+
if node.children.empty?
|
19
|
+
execute_test(code + [node.code])
|
20
|
+
else
|
21
|
+
node.children.each do |child|
|
22
|
+
depth_first_search(child, (code + [node.code]))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute_test(code)
|
28
|
+
@stats.tests += 1
|
29
|
+
|
30
|
+
begin
|
31
|
+
Sandbox.new.instance_eval do
|
32
|
+
eval code.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
print '.'
|
36
|
+
|
37
|
+
rescue Exception => e
|
38
|
+
@stats.exceptions << e
|
39
|
+
|
40
|
+
print 'F'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/specdown.rb
ADDED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: specdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 29
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Parker
|
@@ -15,19 +15,87 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-12-
|
19
|
-
dependencies:
|
20
|
-
|
18
|
+
date: 2011-12-30 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: kramdown
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 35
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 13
|
32
|
+
- 4
|
33
|
+
version: 0.13.4
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: cucumber
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
21
64
|
description:
|
22
65
|
email: moonmaster9000@gmail.com
|
23
|
-
executables:
|
24
|
-
|
66
|
+
executables:
|
67
|
+
- specdown
|
25
68
|
extensions: []
|
26
69
|
|
27
|
-
extra_rdoc_files:
|
28
|
-
|
29
|
-
|
30
|
-
|
70
|
+
extra_rdoc_files:
|
71
|
+
- README.markdown
|
72
|
+
- SPEC.markdown
|
73
|
+
files:
|
74
|
+
- lib/specdown/command.rb
|
75
|
+
- lib/specdown/node.rb
|
76
|
+
- lib/specdown/parser.rb
|
77
|
+
- lib/specdown/runner/report.rb
|
78
|
+
- lib/specdown/runner/stats.rb
|
79
|
+
- lib/specdown/runner.rb
|
80
|
+
- lib/specdown/sandbox.rb
|
81
|
+
- lib/specdown/tree.rb
|
82
|
+
- lib/specdown.rb
|
83
|
+
- bin/specdown
|
84
|
+
- README.markdown
|
85
|
+
- SPEC.markdown
|
86
|
+
- features/command.feature
|
87
|
+
- features/fixtures/parser_example.markdown
|
88
|
+
- features/parser.feature
|
89
|
+
- features/report.feature
|
90
|
+
- features/runner.feature
|
91
|
+
- features/specdown_examples/no_ruby/specdown/parser_example.markdown
|
92
|
+
- features/specdown_examples/with_ruby/specdown/env.rb
|
93
|
+
- features/specdown_examples/with_ruby/specdown/parser_example.markdown
|
94
|
+
- features/step_definitions/command.rb
|
95
|
+
- features/step_definitions/parser.rb
|
96
|
+
- features/step_definitions/report.rb
|
97
|
+
- features/step_definitions/runner.rb
|
98
|
+
- features/support/env.rb
|
31
99
|
homepage: http://github.com/moonmaster9000/specdown
|
32
100
|
licenses: []
|
33
101
|
|
@@ -61,5 +129,17 @@ rubygems_version: 1.8.10
|
|
61
129
|
signing_key:
|
62
130
|
specification_version: 3
|
63
131
|
summary: Write your specs as if they were a README, then EXECUTE them.
|
64
|
-
test_files:
|
65
|
-
|
132
|
+
test_files:
|
133
|
+
- features/command.feature
|
134
|
+
- features/fixtures/parser_example.markdown
|
135
|
+
- features/parser.feature
|
136
|
+
- features/report.feature
|
137
|
+
- features/runner.feature
|
138
|
+
- features/specdown_examples/no_ruby/specdown/parser_example.markdown
|
139
|
+
- features/specdown_examples/with_ruby/specdown/env.rb
|
140
|
+
- features/specdown_examples/with_ruby/specdown/parser_example.markdown
|
141
|
+
- features/step_definitions/command.rb
|
142
|
+
- features/step_definitions/parser.rb
|
143
|
+
- features/step_definitions/report.rb
|
144
|
+
- features/step_definitions/runner.rb
|
145
|
+
- features/support/env.rb
|