rspec-apigen 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.rdoc ADDED
@@ -0,0 +1,186 @@
1
+ == What is this ?
2
+
3
+ A tool that generates an API documentation using RSpec and some DSL magic.
4
+ If successful I might use it for my own project (neo4j.rb)
5
+ <b>This is an early experiment !</b>
6
+
7
+ === How ?
8
+
9
+ Instead of writing specs like this:
10
+
11
+ describe Account do
12
+ context "transfering money" do
13
+ it "deposits transfer amount to the other account" do
14
+ source = Account.new(50, :USD)
15
+ target = mock('target account')
16
+ target.should_receive(:deposit).with(Money.new(5, :USD))
17
+ source.transfer(5, :USD).to(target)
18
+ end
19
+
20
+ it "reduces its balance by the transfer amount" do
21
+ source = Account.new(50, :USD)
22
+ target = stub('target account')
23
+ source.transfer(5, :USD).to(target)
24
+ source.balance.should == Money.new(45, :USD)
25
+ end
26
+ end
27
+ end
28
+
29
+ Which generates the following output
30
+
31
+ $ spec ./spec/account_spec.rb --format nested
32
+ Account
33
+ transfering money
34
+ deposits transfer amount to the other account
35
+ reduces its balance by the transfer amount
36
+
37
+ 2 examples, 0 failures
38
+
39
+ I (also) want to generate a detailed API documentation something like this from a RSpec Macro DSL:
40
+ Account
41
+ Public Static Methods
42
+ #new ()
43
+ Given
44
+ no arguments
45
+ Then
46
+ Return Account with 0 USD
47
+ has #balance == 0
48
+ has #currency == 'USD
49
+ #new (amount,currency)
50
+ Scenario account and currency has valid values
51
+ Given
52
+ arguments 50, USD
53
+ Then
54
+ Return an Account with given amount and currency
55
+ should == 50
56
+ should == "USD"
57
+ Public Instance Methods
58
+ #transfer (amount,currency)
59
+ Scenario transfer amount and currency have valid values
60
+ Given
61
+ arguments 5, USD
62
+ Then
63
+ should not change subject
64
+ Return A transfer of 5 USD from Account with 50 USD
65
+ should be a kind of TransferDSL
66
+ Finished in 0.00412 seconds
67
+ 9 examples, 0 failures
68
+
69
+ TransferDSL
70
+ Public Static Methods
71
+ #new (source_account,amount,currency)
72
+ Given
73
+ arguments Account balance: 50 USD, 5, USD
74
+ Then
75
+ Return An TransferDSL instance with initialized source account, amount and currency
76
+ should == 50
77
+ Public Instance Methods
78
+ #to (target_account)
79
+ Given
80
+ arguments Account balance: 10 USD
81
+ Then it should add money on the target account
82
+ should add money to the target account
83
+
84
+ Finished in 0.00225 seconds
85
+ 4 examples, 0 failures
86
+
87
+ The above is generated from the following RSpec file:
88
+
89
+ describe Account do
90
+
91
+ static_methods do
92
+ new do
93
+ Return("Account with 0 USD") do
94
+ # it_behaves_like "Account with 0 USD" can also be used
95
+ it("has #balance == 0") { subject.balance.should == 0 }
96
+ it("has #currency == 'USD") { subject.currency.should == 'USD' }
97
+ end
98
+ end
99
+
100
+ new(arg(:amount), arg(:currency)) do
101
+ Scenario 'account and currency has valid values' do
102
+ Given do
103
+ arg.amount = 50
104
+ arg.currency = 'USD'
105
+ end
106
+
107
+ Return "an Account with given amount and currency" do
108
+ it { subject.balance.should == 50 } #given.amount }
109
+ it { subject.currency.should == 'USD' } # given.currency }
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ instance_methods do
116
+ transfer(arg(:amount), arg(:currency)) do
117
+ # Description 'bla bla bla'
118
+ Scenario 'transfer amount and currency have valid values' do
119
+ subject { Account.new(50, 'USD') }
120
+ Given do
121
+ arg.amount = 5
122
+ arg.currency = 'USD'
123
+ end
124
+ Return "A transfer of 5 USD from Account with 50 USD" do
125
+ it { should be_kind_of(TransferDSL) }
126
+ end
127
+ Then do
128
+ it "should not change subject" do
129
+ given.subject.balance.should == subject.balance
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+
138
+ describe TransferDSL do
139
+ static_methods do
140
+ new(arg(:source_account) do
141
+ Given do
142
+ arg.source_account = Account.new(50, 'USD')
143
+ end
144
+ Returns("A Transfer DSL with the given source account") do
145
+ it {subject.source_account.should == given.source_account}
146
+ end
147
+
148
+ end
149
+
150
+ instance_methods do
151
+ to(arg(:target_account, arg(:amount), arg(:currency)) do
152
+ Scenario 'source account has enough money' do
153
+ Given do
154
+ arg.target_account = Account.new(50, 'USD')
155
+ arg.amount = 5
156
+ arg.currency = 'USD'
157
+ subject{TransferDSL.new(Account.new(50, 'USD')}
158
+ end
159
+
160
+ # the following line describes the subject's source_account method
161
+ Then "it added money to the target account" do
162
+ it { subject.target_account.amount.should == given.arg.source_account.amount + given.arg.amount }
163
+ end
164
+ end
165
+
166
+ Scenario 'source account does NOT have enough money' do
167
+ Given do
168
+ arg.target_account = Account.new(50, 'USD')
169
+ arg.amount = 100
170
+ arg.currency = 'USD'
171
+ subject{TransferDSL.new(Account.new(10, 'USD')}
172
+ end
173
+ Throws(Error)
174
+ end
175
+ end
176
+
177
+ end
178
+
179
+
180
+ TODO: I will implement a new RSpec HTML formatter which will generate something similar to RDoc. I want
181
+ to write specification in my rspec code instead of using RDoc.
182
+
183
+
184
+ === Example
185
+ gem install rspec --prerelease (2.0.0.beta.19)
186
+ rspec -f d -c spec
@@ -0,0 +1,114 @@
1
+ module RSpec::ApiGen
2
+
3
+ def run_scenario(method, args, block)
4
+ # have we defined any scenarios ?
5
+ MetaHelper.create_singleton_method(self, :Scenario) do |*scenario_desc, &scenario_block|
6
+ context "Scenario #{scenario_desc[0]}" do
7
+ run_scenario(method, args, scenario_block)
8
+ end
9
+ end
10
+
11
+ # create method to set the describe_return variable
12
+ describe_return = nil
13
+ MetaHelper.create_singleton_method(self, :Return) do |*example_desc, &example|
14
+ describe_return = {:example => example, :example_desc => example_desc}
15
+ end
16
+
17
+ # create method to set the given_block variable
18
+ given_block = nil
19
+ given_caller = nil # so that we can print correct line number if there is an exception
20
+ MetaHelper.create_singleton_method(self, :Given) { |*a, &b| given_block = b; given_caller = b.send(:caller)}
21
+
22
+ MetaHelper.create_singleton_method(self, :Description) { |desc| context(desc){} }
23
+
24
+
25
+ # create method to set the given_block variable
26
+ then_block = nil
27
+ then_desc = nil
28
+ MetaHelper.create_singleton_method(self, :Then) { |*desc, &b| then_block = b; then_desc = desc[0] if desc.size > 0}
29
+
30
+ # eval and set the given_block and describe_return variables
31
+ self.instance_eval(&block)
32
+
33
+ # if there are no then_block or describe_return then there is nothing to do
34
+ return if describe_return.nil? && then_block.nil? && then_desc.nil?
35
+
36
+ given = nil
37
+ context "Given" do
38
+ given = Given.new(self, method, args, given_caller, &given_block)
39
+ end
40
+
41
+ # todo should be possible to have several Then
42
+ context "Then #{then_desc}" do
43
+ self.send(:define_method, :given) { given }
44
+ self.send(:define_method, :arg) { given.arg }
45
+ self.send(:define_method, :fixtures) { given.fixtures }
46
+
47
+
48
+ # use the same subject as we used when calling the method on it in the given block
49
+ # self.send(:define_method, :subject) { given.subject }
50
+
51
+ context "Return #{describe_return[:example_desc][0]}" do
52
+ subject { given.return }
53
+
54
+ self.instance_eval(&describe_return[:example])
55
+ end if describe_return
56
+
57
+ self.instance_eval(&then_block) if then_block
58
+ end
59
+ end
60
+
61
+ def create_scenarios_for(method, param, &block)
62
+ args = param[:args]
63
+ context "##{method}", Argument.describe(args) do
64
+ run_scenario(method, args, block)
65
+ end
66
+ end
67
+
68
+
69
+ def static_methods(&block)
70
+ clazz = describes
71
+ describe "Public Static Methods" do
72
+ static_context = Method.new
73
+
74
+ # TODO - how do I find which methods was defined on the clazz and not inherited ?
75
+ def_methods = clazz.public_methods - Object.methods + %w[new]
76
+ current_context = self
77
+
78
+ # add methods on the context - one for each public static method
79
+ def_methods.each do |meth_name|
80
+ MetaHelper.create_singleton_method(static_context, meth_name) do |*args, &example_group|
81
+ current_context.subject { clazz }
82
+ current_context.create_scenarios_for(meth_name, :args => args, &example_group)
83
+ end
84
+ end
85
+ static_context.instance_eval(&block)
86
+ end
87
+ end
88
+
89
+ def instance_methods(&block)
90
+ clazz = describes
91
+ describe "Public Instance Methods" do
92
+ # Check if we are testing a module - in that case construct a new class that includes that Module
93
+ # so that we can test this Module
94
+ subject do
95
+ x = Class.new
96
+ x.send(:include, clazz)
97
+ x.new
98
+ end if clazz.class == Module
99
+
100
+ meth_ctx = Method.new
101
+
102
+ # TODO - how do I find which methods was defined on the clazz and not inherited ?
103
+ def_methods = clazz.public_instance_methods - Object.public_instance_methods
104
+ current_context = self
105
+ def_methods.each do |meth_name|
106
+ MetaHelper.create_singleton_method(meth_ctx, meth_name) do |*args, &example_group|
107
+ current_context.create_scenarios_for(meth_name, :args => args, &example_group)
108
+ end
109
+ end
110
+ meth_ctx.instance_eval(&block)
111
+ end
112
+ end
113
+
114
+ end
@@ -0,0 +1,38 @@
1
+ module RSpec::ApiGen
2
+ class Argument
3
+ attr_reader :name, :description
4
+
5
+ def initialize(name, description, accept_block = false)
6
+ @name = name
7
+ @description = description
8
+ @accept_block = accept_block
9
+ end
10
+
11
+ def accept_block?
12
+ @accept_block
13
+ end
14
+
15
+ def to_s
16
+ "Arg #{name}"
17
+ end
18
+
19
+ def self.inspect_args(args)
20
+ args.collect do |x|
21
+ case x
22
+ when Argument
23
+ x.name
24
+ when RSpec::Mocks::Mock
25
+ x.instance_variable_get('@name')
26
+ else
27
+ "#{x}:#{x.class}"
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.describe(args)
33
+ "(#{inspect_args(args).join(', ')})"
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,5 @@
1
+ # Make the RSpec::ApiGen module available in all 'spec/api' test folders automatically'
2
+
3
+ RSpec.configure do |c|
4
+ c.extend RSpec::ApiGen, :example_group => { :file_path => %r{\b/spec/api} }
5
+ end
@@ -0,0 +1,25 @@
1
+ module RSpec::ApiGen
2
+ class Fixture
3
+ attr_reader :description
4
+ attr_reader :create_proc
5
+ attr_reader :destroy_proc
6
+
7
+ #def initialize(description, create_proc, destroy_proc = nil)
8
+ def initialize(description, &block)
9
+ @description = description && ""
10
+
11
+ # initialize this instance
12
+ self.instance_eval &block
13
+ end
14
+
15
+ def create(&block)
16
+ block.nil? ? @create_proc.call : @create_proc = block
17
+ end
18
+
19
+ def destroy(&block)
20
+ block.nil? ? @destroy_proc.call : @destroy_proc = block
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,56 @@
1
+ module RSpec::ApiGen
2
+ class Formatter
3
+ def method_missing(m, *a, &b)
4
+ puts "CALLED #{m}"
5
+ end
6
+
7
+ def initialize(output )
8
+ end
9
+
10
+ def start(example_count)
11
+ puts "start #{example_count}"
12
+ @current_method = nil
13
+ end
14
+
15
+ def example_group_started(example_group)
16
+ # is it a new class ?
17
+ if @current_class != example_group.describes
18
+ @current_class = example_group.describes
19
+ @current_method = nil
20
+ puts "#{@current_class.class} #{example_group.describes}"
21
+ elsif example_group.description == "Public Static Methods"
22
+ puts " Static Static Methods"
23
+ elsif example_group.description == "Public Instance Methods"
24
+ puts " Static Instance Methods"
25
+ elsif @current_method
26
+ puts " #{example_group.description}"
27
+ else
28
+ puts " Method #{example_group.description}"
29
+ @current_method = example_group.description
30
+ end
31
+ # puts "example_groupstarted #{example_group.describes.class} #{example_group.describes.object_id} #{example_group.description}"
32
+ end
33
+
34
+ def start_dump
35
+ puts "start dump"
36
+ end
37
+
38
+ def example_started(example)
39
+ end
40
+
41
+ def example_passed(example)
42
+ puts " #{example.metadata[:description]}"
43
+ #puts " #{example.pretty_inspect}"
44
+ end
45
+
46
+ def example_failed(example)
47
+ puts "failed"
48
+ end
49
+
50
+ def example_pending(example)
51
+ puts "pending"
52
+ end
53
+
54
+
55
+ end
56
+ end
@@ -0,0 +1,91 @@
1
+ module RSpec::ApiGen
2
+ class Given
3
+ attr_reader :args # contains name of argument and its value
4
+ attr_reader :arg # the DSL object used to set the args
5
+ attr_reader :fixtures
6
+ attr_accessor :subject
7
+ attr_accessor :return
8
+
9
+
10
+ # list_of_args - the list of arguments current method accept
11
+ # When the given block is evaluated in this method
12
+ # the Given#args will return a hash of name or argument and its value.
13
+ def initialize(context, method, list_of_args, given_caller, &block)
14
+ this = self # so we can access it as closure
15
+ @arg = Object.new
16
+ @args = {}
17
+ @fixtures = {}
18
+ block_arg = nil
19
+
20
+ list_of_args.find_all { |a| a.kind_of?(Argument) }.each do |a|
21
+ MetaHelper.create_singleton_method(@arg, "#{a.name}=") do |val|
22
+ this.args[a.name] = val
23
+ end
24
+ if (a.accept_block?)
25
+ MetaHelper.create_singleton_method(@arg, "#{a.name}") do | &bl |
26
+ block_arg = bl
27
+ end
28
+ else
29
+ MetaHelper.create_singleton_method(@arg, "#{a.name}") do
30
+ this.args[a.name]
31
+ end
32
+ end
33
+ end
34
+
35
+ context.it "no arguments" do
36
+ # create the arguments
37
+ MetaHelper.create_singleton_method(self, :arg) { this.arg }
38
+
39
+ # accessor for setting fixture
40
+ MetaHelper.create_singleton_method(self, :fixtures) { this.fixtures }
41
+
42
+ begin
43
+ self.instance_eval &block
44
+ rescue Exception => e
45
+ this.set_backtrace(example, e, given_caller)
46
+ end if block
47
+
48
+ list_of_args.delete_if { |a| a.kind_of?(Argument) && a.accept_block? }
49
+
50
+ # now, the args hash should have been populated
51
+ # for each param we replace the args with the real value
52
+ list_of_args.collect! { |a| a.kind_of?(Argument) ? this.args[a.name] : a }
53
+
54
+ example.metadata[:description] = "arguments #{Argument.describe(list_of_args)}" unless list_of_args.empty?
55
+
56
+ # get the subject which we will use to test the method on, and store it so we can check it
57
+ this.subject = subject
58
+
59
+ begin
60
+ example.execution_result[:exception_encountered] = given_caller
61
+
62
+ if (block_arg)
63
+ # call the method on this instance which will will test
64
+ this.return = this.subject.send(method, *list_of_args, &block_arg)
65
+ else
66
+ this.return = this.subject.send(method, *list_of_args)
67
+ end
68
+ rescue Exception => e
69
+ this.set_backtrace(example, e, given_caller)
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ def set_backtrace(example, e, given_caller)
76
+ trace = e.backtrace
77
+
78
+ # TODO very ugly - don't know how to filter rspec own backtrace
79
+ # remove all lines containing ../lib/rspec
80
+ trace.delete_if {|line| line =~ /\/lib\/rspec\//}
81
+ trace.delete_if {|line| line =~ /\/rubygems\/custom_require/}
82
+ trace.delete_if {|line| line =~ /\/bin\/rspec/}
83
+
84
+ bt = trace + given_caller
85
+ e.set_backtrace(bt)
86
+ example.set_exception(e)
87
+ example.execution_result[:exception_encountered] = bt
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,14 @@
1
+ module RSpec::ApiGen
2
+ module MetaHelper
3
+ def self.singleton_of(obj)
4
+ class << obj;
5
+ self;
6
+ end
7
+ end
8
+
9
+ def self.create_singleton_method(obj, name, &block)
10
+ singleton_of(obj).send(:define_method, name, &block)
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module RSpec::ApiGen
2
+
3
+ class Method
4
+ def arg(name, description='')
5
+ Argument.new(name, description)
6
+ end
7
+
8
+ def arg_block(name, description='')
9
+ Argument.new(name, description, true)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,6 @@
1
+ puts "IN VERSION"
2
+ module RSpec
3
+ module ApiGen
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module RSpec::ApiGen
2
+ VERSION = 0.0.1
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'rspec-apigen/meta_helper'
2
+ require 'rspec-apigen/apigen'
3
+ require 'rspec-apigen/method'
4
+ require 'rspec-apigen/argument'
5
+ require 'rspec-apigen/fixture'
6
+ require 'rspec-apigen/given'
7
+ require 'rspec-apigen/config.rb'
8
+ require 'rspec-apigen/formatter'
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-apigen
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Andreas Ronge
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-03 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 0
31
+ - beta
32
+ - 20
33
+ version: 2.0.0.beta.20
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: Write your API documentation using a custom RSpec DSL instead of using RDoc
37
+ email:
38
+ - andreas.ronge@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - lib/rspec-apigen.rb
47
+ - lib/rspec-apigen/given.rb
48
+ - lib/rspec-apigen/config.rb
49
+ - lib/rspec-apigen/argument.rb
50
+ - lib/rspec-apigen/version.rb~
51
+ - lib/rspec-apigen/apigen.rb
52
+ - lib/rspec-apigen/meta_helper.rb
53
+ - lib/rspec-apigen/method.rb
54
+ - lib/rspec-apigen/formatter.rb
55
+ - lib/rspec-apigen/fixture.rb
56
+ - lib/rspec-apigen/version.rb
57
+ - README.rdoc
58
+ has_rdoc: true
59
+ homepage: http://github.com/andreasronge/rspec-apigen
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 1
80
+ - 3
81
+ - 6
82
+ version: 1.3.6
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.6
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: A plugin for RSpec for generating an API documentation
90
+ test_files: []
91
+