goodall 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/.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