specdown 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|