goodall 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.rb
19
+ api_docs.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm --create use 1.9.3@goodall
2
+ #rvm --create use ree@goodall
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - rbx-18mode
6
+ - rbx-19mode
7
+ - 1.8.7
8
+ - ree
9
+ - jruby-19mode
10
+ - jruby-18mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in goodall.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matthew Nielsen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,311 @@
1
+ [![Build Status](https://travis-ci.org/xunker/Goodall.png?branch=master)](https://travis-ci.org/xunker/Goodall)
2
+ # Goodall
3
+
4
+ Goodall provides an easy interface for documenting your API while you
5
+ write your integration tests.
6
+
7
+ It is compatible with Rspec, Cucumber and test-unit, as well as others. It
8
+ currently supports JSON and XML APIs, but modules can be easily written to
9
+ handler other types.
10
+
11
+ Goodall is named after researcher Jane Goodall, who has spent her life observing and
12
+ documenting the behviour of chimpanzees.
13
+
14
+ ## Purpose
15
+
16
+ Goodall is a gemified version of the ideas in outlined a few years ago in
17
+ this blog post: http://n4k3d.com/blog/2011/08/19/generate-easy-up-to-date-rails-api-docs-with-cucumber/
18
+
19
+ The basic idea is that you are writing integration tests anyway, you are 90%
20
+ there. All that was left was to record the requests and responses to a text
21
+ file. For example, the following cucumber test:
22
+
23
+ ```cucumber
24
+ Feature: /widgets
25
+ Scenario: Get a list of all widgets
26
+ Given the following widget exists:
27
+ | name | job |
28
+ | Billy the Widget | Be a widget |
29
+ | Sally the Widget | Be a widget |
30
+ When I GET "/widgets"
31
+ Then I should get a successful response
32
+ And the response should include 2 widgets
33
+ And the response should include the Widget data for "Billy the Widget"
34
+ And the response should include the Widget data for "Sally the Widget"
35
+
36
+ Scenario: Create a new widget
37
+ Given I have a JSON request for "widget"
38
+ | name | Bob the Widget |
39
+ | job | Be a widget |
40
+ And I POST that JSON to "/widgets.json"
41
+ Then I should get a successful response
42
+ And the response should include the Widget data for "Bob the Widget"
43
+ ```
44
+
45
+ .. would then generate the following documentation:
46
+
47
+ ```
48
+ ——————————————————————————–
49
+ Feature: /widgets
50
+
51
+ Scenario: Get a list of all widgets
52
+ GET /widgets.json
53
+ RESPONSE:
54
+ {
55
+ "widgets" : [
56
+ {
57
+ "name" : "Billy the Widget",
58
+ "job" : "Be a widget"
59
+ },
60
+ {
61
+ "name" : "Sally the Widget",
62
+ "job" : "Be a widget"
63
+ }
64
+ ]
65
+ }
66
+
67
+ Scenario: Create a new widget
68
+ POST /widgets.json:
69
+ {
70
+ "widget" : {
71
+ "name" : "Bob the Widget",
72
+ "job" : "Be a widget"
73
+ }
74
+ }
75
+ RESPONSE:
76
+ {
77
+ "widget" : {
78
+ "name" : "Billy the Widget",
79
+ "job" : "Be a widget"
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Installation
85
+
86
+ Add this line to your application's Gemfile:
87
+
88
+ gem 'goodall'
89
+
90
+ And then execute:
91
+
92
+ $ bundle
93
+
94
+ Or install it yourself as:
95
+
96
+ $ gem install goodall
97
+
98
+ ## Usage
99
+
100
+ ### Cucumber
101
+
102
+ In your *env.rv* file, require 'goodall/cucumber' and either of the JSON
103
+ or XML handlers. Or both, if you need them.
104
+
105
+ ```ruby
106
+ # env.rb
107
+ require 'goodall/cucumber'
108
+ require 'goodall/handler/json' # or/and 'goodall/handler/xml'
109
+ ```
110
+
111
+ In your features, where you want to log a request or response, call either
112
+ *Goodall.document_request* or *Goodall.document_response*.
113
+
114
+ For cucumber, the best way is to have often-reused methods in each scenario, and then put the Goodall methods call in those. Take this example feature:
115
+
116
+ ```Cucumber
117
+ # something.feature
118
+ Given I get "/some/api.json"
119
+ Then the response should be valid json
120
+ And the response should should be successful
121
+
122
+ # something_steps.rb
123
+
124
+ Given /^I get \"(.+)\"$/ do |path|
125
+ get(path)
126
+ Goodall.document_request(:get, path)
127
+ end
128
+
129
+ Given /^the response should be valid json$/ do
130
+ Goodall.document_response(last_response.body)
131
+ @json_response = MultiJson.load(last_response.body)
132
+ end
133
+
134
+ Given /^the response should be successul$/ do
135
+ @last_response['success'].should be_true
136
+ end
137
+ ```
138
+
139
+ The steps "I get (.+)" and "the response should be valid json" are steps I
140
+ will use in almost every scenario, so it is the perfect place for the Goodall
141
+ calls. Using Goodall in these frequently used steps is key for ease-of-use.
142
+
143
+ _IMPORTANT NOTE_: Using the cucumber helpers, Goodall will NOT log unless
144
+ executed via the rake task "rake cucumber:document". To force Goodall to
145
+ log, please set the environment variable 'ENABLE_GOODALL' to a non-nil
146
+ value. For example:
147
+
148
+ ```
149
+ ENABLE_GOODALL=true cucumber features/
150
+ ```
151
+
152
+ ### Rspec
153
+
154
+ In your *spec_helper.rb* file, require 'goodall/rspec' and either of the
155
+ JSON or XML handlers. Or both, if you need them.
156
+
157
+ ```ruby
158
+ # spec_helper.rb
159
+ require 'goodall/rspec'
160
+ require 'goodall/handler/json' # or/and 'goodall/handler/xml'
161
+ ```
162
+
163
+ For controller specs, re-use can be acheived by making a delegation method
164
+ for the get/post/put/delete/patch verbs:
165
+
166
+ ```ruby
167
+ # spec_helper.rb
168
+
169
+ def documented_get(*args)
170
+ Goodall.document_request(:get, args[0])
171
+ get_response = get(args)
172
+ Goodall.document_response(response.body)
173
+ get_response
174
+ end
175
+
176
+ # some_controller_spec.rb
177
+ describe SomeController do
178
+ describe "GET foo" do
179
+ it "should return a body containing 'blah'" do
180
+
181
+ documented_get(:foo) # replaces get(:foo)
182
+ expect(response.body).to include?('blah')
183
+
184
+ end
185
+ end
186
+ end
187
+ ```
188
+
189
+ _IMPORTANT NOTE_: Using the rspec helpers, Goodall will NOT log unless
190
+ executed via the rake task "rake rspec:document". To force Goodall to
191
+ log, please set the environment variable 'ENABLE_GOODALL' to a non-nil
192
+ value. For example:
193
+
194
+ ```shell
195
+ ENABLE_GOODALL=true rspec spec/
196
+ ```
197
+
198
+ ### Test Unit and derivatives, and others
199
+
200
+ If you are not using any of the convenience wrappers for rspec or cucumber,
201
+ there is more work to be done when using Goodall.
202
+
203
+ First, like others, require the files:
204
+
205
+ ```ruby
206
+ require 'goodall'
207
+ require 'goodall/handler/json' # or/and 'goodall/handler/xml'
208
+ ```
209
+
210
+ You will also need to set the path of the file for output. If you choose
211
+ not the set this, the default will be used which is "./api_docs.txt".
212
+ You can override this by:
213
+
214
+ ```ruby
215
+ Goodall.output_path = "./some/path/file.txt"
216
+ ```
217
+
218
+ Enabing the logging process involves setting the *enabled* property. It is
219
+ disabled by default, which means that Goodall will not document unless
220
+ explicity told to do so. You can enable logging with
221
+
222
+ ```ruby
223
+ Goodall.enabled = true
224
+ ```
225
+
226
+ ### Rake task
227
+
228
+ To automate the creation of documentation, a rake task can be added that
229
+ automatically set the output file and triggers the tests.
230
+
231
+ ```ruby
232
+ # Rakefile
233
+ require 'goodall/rake_task'
234
+ ```
235
+
236
+ This unlocks the following rake commands:
237
+
238
+ ```shell
239
+ rake cucumber:document # Run cucumber and write Goodall documentation
240
+ rake spec:document # Run rspec tests and write Goodall documentation
241
+ rake goodall:output_path # Show current Goodall documentation output path
242
+ ```
243
+
244
+ By default, the output path will be 'doc/api_docs.txt' when run with this
245
+ rake task.
246
+
247
+ ## Handlers
248
+
249
+ By default, Goodall includes handlers for XML and JSON. This means that the
250
+ logged output can be parsed from these formats and pretty-printed correctly
251
+ to the documention file.
252
+
253
+ If only one handler is required in your config, Goodall assumes you want to
254
+ use that one all the time. For example:
255
+
256
+ ```ruby
257
+ require 'goodall'
258
+ require 'goodall/handler/json'
259
+
260
+ Goodall.document_reponse(response.body)
261
+ ```
262
+
263
+ ..will assume *response.body* is JSON and will parse it as such. If you were
264
+ to swap "goodall/handler/json" with "goodall/handler/xml" then the response
265
+ would be assumed to be XML.
266
+
267
+ If you need to use both formats at the same time, you can include both
268
+ handlers and set the actuve handler by name.
269
+
270
+ ```ruby
271
+ require 'goodall'
272
+ require 'goodall/handler/json'
273
+ require 'goodall/handler/xml'
274
+
275
+ Goodall.set_handler(:json)
276
+ Goodall.document_response(response.body) # json response
277
+
278
+ Goodall.set_handler(:xml)
279
+ Goodall.document_response(response.body) # xml response
280
+ ```
281
+
282
+ In the above case, the default handler would be XML since it was required
283
+ last.
284
+
285
+ ### Writing new handlers
286
+
287
+ Please see *lib/goodall/handler/json.rb* for a good example. A handler will
288
+ need to do two things:
289
+
290
+ **Implement #parse_payload**: accepts a data structure and is expected to
291
+ return a pretty-printed string representing that data.
292
+
293
+ **Register itself as a handler**: This is done by calling:
294
+
295
+ ```ruby
296
+ Goodall.register_handler(:handler_name, self)
297
+ ```
298
+ ..where *:handler_name* is a **symbol** for what type of data is being
299
+ handled, and self is the **class** of the handler.
300
+
301
+ ## Methods
302
+
303
+ Documented with rdoc.
304
+
305
+ ## Contributing
306
+
307
+ 1. Fork it
308
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
309
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
310
+ 4. Push to the branch (`git push origin my-new-feature`)
311
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ require "rspec/core/version"
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run all examples"
9
+ RSpec::Core::RakeTask.new(:spec, :default) do |t|
10
+ t.ruby_opts = %w[-w]
11
+ end
data/goodall.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'goodall/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "goodall"
8
+ spec.version = Goodall::VERSION
9
+ spec.authors = ["Matthew Nielsen"]
10
+ spec.email = ["xunker@pyxidis.org"]
11
+ spec.description = %q{An easy interface for documenting your API while you
12
+ write your tests.}
13
+ spec.summary = %q{Goodall provides an easy interface for documenting your API while you write your tests. It is compatible with Rspec, Cucumber and test-unit, as well as others. Goodall is named after Jane Goodall who has spent her life observing and documenting the behviour of chimpanzees.}
14
+ spec.homepage = "http://github.com/xunker/goodall"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "multi_json", ">= 1.6"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", ">= 2.10"
27
+ end
data/lib/goodall.rb ADDED
@@ -0,0 +1,142 @@
1
+ require 'singleton'
2
+ require "goodall/version"
3
+ require "goodall/errors"
4
+ require "goodall/writer"
5
+
6
+ require 'goodall/rake_task' if defined?(Rails)
7
+
8
+ class Goodall
9
+ include Singleton
10
+
11
+ @@enabled = false #:nodoc:
12
+ @@output_path = './api_docs.txt' #:nodoc:
13
+ @@registered_handlers = {} #:nodoc:
14
+
15
+ # Get the current documentation output path
16
+ def self.output_path
17
+ @@output_path
18
+ end
19
+
20
+ # Set the current documentation output path
21
+ def self.output_path=(val)
22
+ @@output_path = val
23
+ end
24
+
25
+ # Is Goodall logging enabled?
26
+ def self.enabled
27
+ @@enabled
28
+ end
29
+
30
+ # alias unreliable on class methods, use this instead.
31
+ def self.enabled?
32
+ enabled
33
+ end
34
+
35
+ # Explicity set the enabled state, true or false
36
+ def self.enabled=(val)
37
+ @@enabled=!!val
38
+ end
39
+
40
+ # Enable Goodall, which is disabled by default.
41
+ def self.enable
42
+ self.enabled=true
43
+ end
44
+
45
+ # Enable Goodall, which is the default by default.
46
+ def self.disable
47
+ self.enabled = false
48
+ end
49
+
50
+ # write to the currently open output file
51
+ def self.write(str)
52
+ writer.write(str) if enabled?
53
+ end
54
+
55
+ # Document a request.
56
+ #
57
+ # * +:method+ - a symbol of the verb: :get, :post, :put, :delete, :patch
58
+ # * +:path+ - a string of the path (URL/URI) of the request
59
+ # * +:payload+ - the parameters sent, e.g. post body. Usually a hash.
60
+ def self.document_request(method, path, payload=nil)
61
+ return unless enabled?
62
+
63
+ str = "#{method.to_s.upcase}: #{path}"
64
+
65
+ if payload && payload.to_s.size > 0
66
+ str << "\n" + current_handler.parse_payload(payload)
67
+ end
68
+
69
+ str << "\n"
70
+
71
+ writer.write(str)
72
+ end
73
+
74
+ # Document a response.
75
+ #
76
+ # * +:payload - the data returned from the request, e.g. response.body. `payload` will be run through the current handler and be pretty-printed to the output file.
77
+ def self.document_response(payload)
78
+ return unless enabled?
79
+
80
+ if payload
81
+ payload = current_handler.parse_payload(payload)
82
+ end
83
+
84
+ str = "RESPONSE:\n#{payload}\n"
85
+
86
+ writer.write(str)
87
+ end
88
+
89
+ at_exit do
90
+ if enabled? && writer
91
+ writer.close
92
+ end
93
+ end
94
+
95
+ # When writing a custom hander, it must register itself with Goodall using
96
+ # this method.
97
+ #
98
+ # * +:payload_type+ - The name of the kind of content that this handler will be processing, e.g. JSON, XML, HTML etc.
99
+ # * +:handler_class+ - The class of the handler itself (not the class name).
100
+ def self.register_handler(payload_type, handler_class)
101
+ @@registered_handlers[payload_type.to_sym] = handler_class
102
+ end
103
+
104
+ # Set the currently active handler. By default, if only one handler is
105
+ # registered then it will be made active by default. If you hanve multiple
106
+ # handlers registered and wish to switch between them, use this.
107
+ #
108
+ # * +:handler_name+ - Handler name as a symbol, e.g. :json, :xml.
109
+ def self.set_handler(handler_name)
110
+ handler_name = handler_name.to_sym
111
+ if handler_class = @@registered_handlers[handler_name]
112
+ @current_handler = handler_class.new
113
+ else
114
+ raise HandlerNotRegisteredError, "No handler registered for for #{handler_name}"
115
+ end
116
+ end
117
+
118
+ # returns an array of arrays of the currently registered handlers.
119
+ #
120
+ # [ [ :identifier, class ], [ :identifier, class ] ]
121
+ def self.registered_handlers
122
+ @@registered_handlers.map{|k,v| [k,v]}
123
+ end
124
+
125
+ private
126
+
127
+ def self.current_handler
128
+ @current_handler ||= if default_handler = @@registered_handlers.first
129
+ default_handler[1].new
130
+ else
131
+ raise(
132
+ Goodall::NoHandlersRegisteredError,
133
+ "There are no handlers registered, please require at least one."
134
+ )
135
+ end
136
+ end
137
+
138
+ def self.writer
139
+ @writer ||= Goodall::Writer.new(@@output_path)
140
+ end
141
+
142
+ end
@@ -0,0 +1,8 @@
1
+ if (ENV["ENABLE_GOODALL"].to_s.size > 0) && (ENV["ENABLE_GOODALL"].to_s.upcase != 'FALSE')
2
+ Goodall.enable
3
+ end
4
+
5
+ if (ENV["GOODALL_OUTPUT_PATH"].to_s.size > 0)
6
+ Goodall.output_path = ENV["GOODALL_OUTPUT_PATH"]
7
+ end
8
+
@@ -0,0 +1,13 @@
1
+ require 'goodall'
2
+ require 'goodall/command_line_enable'
3
+
4
+ Before do |scenario|
5
+ if scenario.feature != $current_feature
6
+ $current_feature = scenario.feature
7
+ Goodall.write("\n")
8
+ Goodall.write("#{'-'*80}\nFeature: #{$current_feature.name}")
9
+ end
10
+ Goodall.write("\n")
11
+ Goodall.write("Scenario: #{scenario.name}")
12
+ Goodall.write("\n")
13
+ end
@@ -0,0 +1,4 @@
1
+ class Goodall
2
+ class NoHandlersRegisteredError < StandardError; end
3
+ class HandlerNotRegisteredError < StandardError; end
4
+ end
@@ -0,0 +1,9 @@
1
+ class Goodall
2
+ module Handler
3
+ class Base
4
+ def parse_payload(payload_string)
5
+ raise NotImplementedError
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ require 'multi_json'
2
+ require "goodall/handler/base"
3
+
4
+ class Goodall
5
+ module Handler
6
+ class Json < Base
7
+ Goodall.register_handler :json, self
8
+
9
+ def parse_payload(payload)
10
+ payload = if payload.class == String
11
+ # assue it's a string of json
12
+ begin
13
+ MultiJson.load(payload)
14
+ rescue MultiJson::LoadError
15
+ # probably not JSON, return as-is
16
+ return payload+"\n"
17
+ end
18
+ else
19
+ payload
20
+ end
21
+
22
+ # detect "pretty" json by seeing if there are CRs in here
23
+ if (json = MultiJson.dump(payload)) =~ /\n/
24
+ json
25
+ else
26
+ pretty_print(json)
27
+ # json
28
+ end
29
+ end
30
+
31
+
32
+ private
33
+
34
+ # We're doing this outselves because it's too unreliable detecting which parsers support pretty-print and whoch one don't. If this method is broken, at least it will be *consitently* broken.
35
+ def pretty_print(json)
36
+ return json if json.to_s.size < 1
37
+
38
+ str = json.to_s.gsub("},", "},\n").gsub("],", "],\n").gsub("{[", "{\n[").gsub("}]", "}\n]").gsub("[{", "[\n{").gsub("]}", "]\n}").gsub("{\"", "{\n\"").gsub("\"}", "\"\n}").gsub("\",\"", "\",\n\"")
39
+
40
+ if str.match(/[^\n]\}$/)
41
+ str.gsub!(/\}$/, "\n}")
42
+ end
43
+
44
+ output = []
45
+
46
+ indent_level = 0
47
+ str.split("\n").each do |s|
48
+ indent_level -= 1 if ["]", "}"].include?(s.split('').first) && indent_level > 0
49
+ output << (" "*indent_level) + s
50
+ if ["{", "["].include?(s.split('').last)
51
+ indent_level += 1
52
+ next
53
+ end
54
+
55
+ if ["{", "["].include?(s.split('').first)
56
+ indent_level += 1
57
+ next
58
+ end
59
+ end
60
+ output.join("\n")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ require "goodall/handler/base"
2
+
3
+ # This sucks.
4
+ # It needs love from someone who knows xml. That's not me.
5
+
6
+ class Goodall
7
+ module Handler
8
+ class Xml < Base
9
+ Goodall.register_handler :xml, self
10
+
11
+ def parse_payload(payload)
12
+ if payload.class == String
13
+ # assue it's a string of xml
14
+ return payload
15
+ else
16
+ begin
17
+ return payload.to_xml
18
+ rescue Exception => e
19
+ puts "!!! Just tried to call to_xml on your response, but an error was returned. Your object may not support this."
20
+ raise e
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ require 'goodall'
2
+ require 'rails'
3
+ class Goodall
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :goodall
6
+
7
+ rake_tasks do
8
+ load "tasks/goodall.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require 'goodall'
2
+ require 'goodall/command_line_enable'
@@ -0,0 +1,3 @@
1
+ class Goodall
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,15 @@
1
+ class Goodall
2
+ class Writer
3
+ def initialize(file_path)
4
+ @documentation_file = File.new(file_path, "w")
5
+ end
6
+
7
+ def close
8
+ @documentation_file.close
9
+ end
10
+
11
+ def write(str)
12
+ @documentation_file.puts str
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,60 @@
1
+ def goodall_installed?
2
+ begin
3
+ gem 'goodall'
4
+ rescue Gem::LoadError
5
+ false
6
+ end
7
+ end
8
+
9
+ def running_under_rails?
10
+ defined?(Rails)
11
+ end
12
+
13
+ def goodall_not_installed
14
+ raise "the Goodall gem is not installed or not enabled. `bundle exec` may fix this."
15
+ end
16
+
17
+ def goodall_output_path
18
+ if running_under_rails?
19
+ "#{Rails.root}/doc/api_docs.txt"
20
+ else
21
+ Goodall.output_path
22
+ end
23
+ end
24
+
25
+ def set_goodall_putput_path
26
+ ENV['GOODALL_OUTPUT_PATH'] = goodall_output_path
27
+ end
28
+
29
+ def enable_goodall
30
+ ENV['ENABLE_GOODALL'] = 'true'
31
+ end
32
+
33
+ namespace :cucumber do
34
+ desc "Run cucumber and write Goodall documentation"
35
+ goodall_not_installed unless goodall_installed?
36
+ task :document => :environment do
37
+ set_goodall_putput_path
38
+ enable_goodall
39
+ Rake::Task["cucumber"].invoke
40
+ end
41
+ end
42
+
43
+ namespace :spec do
44
+ desc "Run rspec tests and write Goodall documentation"
45
+ goodall_not_installed unless goodall_installed?
46
+ task :document => :environment do
47
+ set_goodall_putput_path
48
+ enable_goodall
49
+ Rake::Task["spec"].invoke
50
+ end
51
+ end
52
+
53
+ namespace :goodall do
54
+ desc "Show current Goodall documentation output path"
55
+ goodall_not_installed unless goodall_installed?
56
+ task :output_path => :environment do
57
+ puts "Goodall output path: #{goodall_output_path}"
58
+ end
59
+ end
60
+
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'goodall'
3
+ require 'goodall/handler/json'
4
+
5
+ describe Goodall::Handler::Json do
6
+ describe '#parse_payload' do
7
+ context 'payload is a string' do
8
+ context 'string is valid json' do
9
+
10
+ let(:valid_json_string) { '{"foo":"bar"}' }
11
+
12
+ it 'should return it as pretty-printed json' do
13
+ expect(
14
+ subject.parse_payload(valid_json_string)
15
+ ).to eq("{\n \"foo\":\"bar\"\n}")
16
+ end
17
+ end
18
+ context 'the string not valid json' do
19
+
20
+ let(:invalid_json_string) { 'BLAHBLAH' }
21
+
22
+ it 'should return the string with CR added' do
23
+ expect(
24
+ subject.parse_payload(invalid_json_string)
25
+ ).to eq("#{invalid_json_string}\n")
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'payload is not a string' do
31
+
32
+ let(:payload) { { :foo => :bar } }
33
+
34
+ it 'should return it as pretty-printed json' do
35
+ expect(
36
+ subject.parse_payload(payload)
37
+ ).to eq("{\n \"foo\":\"bar\"\n}")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,209 @@
1
+ require 'spec_helper'
2
+ require 'goodall'
3
+
4
+ describe Goodall do
5
+ let(:klass) { Goodall }
6
+
7
+ let(:mock_writer) { double(:writer, :close => nil) }
8
+
9
+ let(:mock_handler) { double(:mock_handler) }
10
+
11
+ let(:mock_new_handler) { double(:mock_new_handler) }
12
+
13
+ before(:each) do
14
+ Goodall.stub(:writer).and_return(mock_writer)
15
+ end
16
+
17
+ after(:each) do
18
+ Goodall.disable
19
+ end
20
+
21
+ describe ".output_path" do
22
+ it "must return the current output file path" do
23
+ # default
24
+ expect(klass.output_path).to eq('./api_docs.txt')
25
+
26
+ # setting
27
+ klass.output_path = 'foo/bar.txt'
28
+ expect(klass.output_path).to eq('foo/bar.txt')
29
+ end
30
+ end
31
+
32
+ describe ".enabled" do
33
+ it "must be true if goodall is enabled" do
34
+ klass.enable
35
+
36
+ expect(klass.enabled).to be_true
37
+ end
38
+ it "must be false if goodall is not enabled" do
39
+ klass.disable
40
+
41
+ expect(klass.enabled).to be_false
42
+ end
43
+ end
44
+
45
+ describe ".write" do
46
+ context "Goodall is enabled" do
47
+ before(:each) { klass.enable; }
48
+ it "must deleage to the writer" do
49
+ expect(mock_writer).to receive(:write).with('string')
50
+
51
+ Goodall.write('string')
52
+ end
53
+ end
54
+
55
+ context "Goodall is not enabled" do
56
+ before(:each) { klass.disable }
57
+ it "must silently return and not touch the writer" do
58
+ expect(mock_writer).not_to receive(:write)
59
+
60
+ Goodall.write('string')
61
+ end
62
+ end
63
+ end
64
+
65
+ describe ".document_request" do
66
+ context "Goodall is enabled" do
67
+
68
+ context "post with payload" do
69
+
70
+ let(:mock_payload) { '{ "foo" : "bar" }' }
71
+
72
+ let(:formatted_payload) do
73
+ "{\n \"foo\" : \"bar\"\n}\n"
74
+ end
75
+
76
+ before(:each) do
77
+ klass.enable
78
+
79
+ mock_handler.stub(
80
+ :parse_payload
81
+ ).with(
82
+ mock_payload
83
+ ).and_return(
84
+ formatted_payload
85
+ )
86
+
87
+ klass.stub(:current_handler).and_return(mock_handler)
88
+ klass.stub(:writer).and_return(mock_writer)
89
+ end
90
+
91
+ let(:expected_write) do
92
+ "POST: /foo/bar\n#{formatted_payload}\n"
93
+ end
94
+
95
+ it "must send a formatted response to the writer" do
96
+ expect(mock_writer).to receive(:write).with(expected_write)
97
+
98
+ klass.document_request(:post, '/foo/bar', mock_payload)
99
+ end
100
+ end
101
+
102
+ context "get without payload" do
103
+ before(:each) do
104
+ klass.enable
105
+ end
106
+
107
+ let(:expected_write) do
108
+ "GET: /foo/bar\n"
109
+ end
110
+
111
+ it "must send a formatted request to the writer" do
112
+ expect(mock_writer).to receive(:write).with(expected_write)
113
+
114
+ klass.document_request(:get, '/foo/bar')
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ context "Goodall is not enabled" do
121
+ before(:each) { klass.disable }
122
+ it "must silently return without writing" do
123
+ mock_writer.should_not_receive(:write)
124
+
125
+ klass.document_request(:foo, 'bar', { :baz => :baz })
126
+ end
127
+ end
128
+ end
129
+
130
+ describe ".document_response" do
131
+ context "Goodall is enabled" do
132
+
133
+ let(:mock_payload) { '{ "foo" : "bar" }' }
134
+
135
+ let(:formatted_payload) do
136
+ "{\n \"foo\" : \"bar\"\n}\n"
137
+ end
138
+
139
+ before(:each) do
140
+ klass.enable
141
+
142
+ mock_handler.stub(
143
+ :parse_payload
144
+ ).with(
145
+ mock_payload
146
+ ).and_return(
147
+ formatted_payload
148
+ )
149
+
150
+ klass.stub(:current_handler).and_return(mock_handler)
151
+ klass.stub(:writer).and_return(mock_writer)
152
+ end
153
+
154
+ let(:expected_write) do
155
+ "RESPONSE:\n#{formatted_payload}\n"
156
+ end
157
+
158
+ it "must send a formatted response to the writer" do
159
+ expect(mock_writer).to receive(:write).with(expected_write)
160
+
161
+ klass.document_response(mock_payload)
162
+ end
163
+ end
164
+
165
+ context "Goodall is not enabled" do
166
+ before(:each) { klass.disable }
167
+ it "must silently return without writing" do
168
+ mock_writer.should_not_receive(:write)
169
+
170
+ klass.document_response({ :baz => :baz })
171
+ end
172
+ end
173
+ end
174
+
175
+ describe ".register_handler" do
176
+ it "should add a handler class to the list of registered handlers" do
177
+ klass.register_handler(:foo_register_test, mock_new_handler)
178
+
179
+ expect(klass.registered_handlers).to include([:foo_register_test, mock_new_handler])
180
+ end
181
+ end
182
+
183
+ describe ".set_handler" do
184
+ context "handler is registered" do
185
+ it "should set that handler as the current active handler" do
186
+ foo_handler_instance = double(:foo_handler_instance)
187
+ foo_handler_class = double(:foo_handler_class, :new => foo_handler_instance)
188
+ bar_handler_instance = double(:bar_handler_instance)
189
+ bar_handler_class = double(:bar_handler_class, :new => bar_handler_instance)
190
+
191
+ klass.register_handler(:foo, foo_handler_class)
192
+ klass.register_handler(:bar, bar_handler_class)
193
+
194
+ klass.set_handler(:foo)
195
+ expect(klass.send(:current_handler)).to eq(foo_handler_instance)
196
+
197
+ klass.set_handler(:bar)
198
+ expect(klass.send(:current_handler)).to eq(bar_handler_instance)
199
+ end
200
+ end
201
+ context "handler is not registered" do
202
+ it "should raise HandlerNotRegisteredError" do
203
+ expect{
204
+ klass.set_handler(:invalid)
205
+ }.to raise_error(Goodall::HandlerNotRegisteredError)
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,5 @@
1
+ RSpec.configure do |config|
2
+ config.treat_symbols_as_metadata_keys_with_true_values = true
3
+ config.run_all_when_everything_filtered = true
4
+ config.order = 'random'
5
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: goodall
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Nielsen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-08-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: multi_json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 1
31
+ - 6
32
+ version: "1.6"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bundler
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 9
44
+ segments:
45
+ - 1
46
+ - 3
47
+ version: "1.3"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: rake
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
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 23
73
+ segments:
74
+ - 2
75
+ - 10
76
+ version: "2.10"
77
+ type: :development
78
+ version_requirements: *id004
79
+ description: |-
80
+ An easy interface for documenting your API while you
81
+ write your tests.
82
+ email:
83
+ - xunker@pyxidis.org
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - .rvmrc
94
+ - .travis.yml
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - goodall.gemspec
100
+ - lib/goodall.rb
101
+ - lib/goodall/command_line_enable.rb
102
+ - lib/goodall/cucumber.rb
103
+ - lib/goodall/errors.rb
104
+ - lib/goodall/handler/base.rb
105
+ - lib/goodall/handler/json.rb
106
+ - lib/goodall/handler/xml.rb
107
+ - lib/goodall/rake_task.rb
108
+ - lib/goodall/rspec.rb
109
+ - lib/goodall/version.rb
110
+ - lib/goodall/writer.rb
111
+ - lib/tasks/goodall.rake
112
+ - spec/lib/goodall/handler/json_spec.rb
113
+ - spec/lib/goodall_spec.rb
114
+ - spec/spec_helper.rb
115
+ homepage: http://github.com/xunker/goodall
116
+ licenses:
117
+ - MIT
118
+ post_install_message:
119
+ rdoc_options: []
120
+
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ requirements: []
142
+
143
+ rubyforge_project:
144
+ rubygems_version: 1.8.25
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: Goodall provides an easy interface for documenting your API while you write your tests. It is compatible with Rspec, Cucumber and test-unit, as well as others. Goodall is named after Jane Goodall who has spent her life observing and documenting the behviour of chimpanzees.
148
+ test_files:
149
+ - spec/lib/goodall/handler/json_spec.rb
150
+ - spec/lib/goodall_spec.rb
151
+ - spec/spec_helper.rb