interpol 0.0.2 → 0.0.3

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/Gemfile CHANGED
@@ -8,3 +8,4 @@ group :extras do
8
8
  end
9
9
 
10
10
  gem 'json-jruby', platform: 'jruby'
11
+ gem 'compass_twitter_bootstrap', git: 'git://github.com/vwall/compass-twitter-bootstrap.git'
data/README.md CHANGED
@@ -19,8 +19,8 @@ definitions:
19
19
  validates your API responses against the JSON schema in your endpoint
20
20
  definition files. This is useful in test/development environments to
21
21
  ensure that your real API returns valid responses.
22
- * A documentation browser for your API is planned as well (but not yet
23
- implemented).
22
+ * `Interpol::DocumentationApp` builds a sinatra app that renders
23
+ documentation for your API based on the endpoint definitions.
24
24
 
25
25
  You can use any of these tools individually or some combination of all
26
26
  of them.
@@ -81,7 +81,8 @@ definitions:
81
81
  Let's look at this YAML file, point-by-point:
82
82
 
83
83
  * `name` can be anything you want. Each endpoint should have a different name. Interpol uses
84
- it in schema validation error messages.
84
+ it in schema validation error messages. It is also used by the
85
+ documentation app.
85
86
  * `route` defines the sinatra route for this endpoint. Note that while
86
87
  Interpol::StubApp supports any sinatra route, Interpol::ResponseSchemaValidator
87
88
  (which has to find a matching endpoint definition from the request path), only
@@ -159,6 +160,12 @@ Interpol.default_configuration do |config|
159
160
  #
160
161
  # Used by Interpol::ResponseSchemaValidator.
161
162
  config.validation_mode = :error # or :warn
163
+
164
+ # Determines the title shown on the rendered documentation
165
+ # pages.
166
+ #
167
+ # Used by Interpol::DocumentationApp.
168
+ config.documentation_title = "Acme Widget API Documentaton"
162
169
  end
163
170
 
164
171
  ```
@@ -254,6 +261,29 @@ get '/users/:user_id/projects' do
254
261
  end
255
262
  ```
256
263
 
264
+ ### Interpol::DocumentationApp
265
+
266
+ This will build a little sinatra app that renders documentation
267
+ about your API based on your endpoint definitions.
268
+
269
+ ``` ruby
270
+ # config.ru
271
+
272
+ require 'interpol/documentation_app'
273
+
274
+ # the block is only necessary if you want to override the default
275
+ # config or if you have not set a default config.
276
+ doc_app = Interpol::DocumentationApp.build do |app|
277
+ app.endpoint_definition_files = Dir["config/endpoints_definitions/*.yml"]
278
+ app.documentation_title = "My API Documentation"
279
+ end
280
+
281
+ run doc_app
282
+ ```
283
+
284
+ Note: the documentation app is definitely a work-in-progress and I'm not
285
+ a front-end/UI developer. I'd happily accept a pull request improving it!
286
+
257
287
  ## Contributing
258
288
 
259
289
  1. Fork it
data/Rakefile CHANGED
@@ -21,3 +21,25 @@ else
21
21
  end
22
22
 
23
23
  task default: [:spec, :quality]
24
+
25
+ desc "Watch Documentation App Compass Files"
26
+ task :compass_watch do
27
+ Dir.chdir("lib/interpol/documentation_app") do
28
+ sh "bundle exec compass watch"
29
+ end
30
+ end
31
+
32
+ desc "Import the twitter bootstrap assets from the compass_twitter_bootstrap gem"
33
+ task :import_bootstrap_assets do
34
+ require 'bundler'
35
+ Bundler.setup
36
+
37
+ # when the gem installed as a :git gem, it has "-" as a separator;
38
+ # when it's installed as a released rubygem, it has "_" as a separator.
39
+ gem_lib_path = $LOAD_PATH.grep(/compass[-_]twitter[-_]bootstrap/).first
40
+ assets = Dir[File.join(gem_lib_path, '..', 'vendor', 'assets', '**')]
41
+
42
+ destination_path = File.expand_path("../lib/interpol/documentation_app/public", __FILE__)
43
+ FileUtils.cp_r(assets, destination_path)
44
+ end
45
+
@@ -26,7 +26,7 @@ module Interpol
26
26
  # Public: Defines interpol configuration.
27
27
  class Configuration
28
28
  attr_reader :endpoint_definition_files, :endpoints
29
- attr_accessor :validation_mode
29
+ attr_accessor :validation_mode, :documentation_title
30
30
 
31
31
  def initialize
32
32
  api_version do
@@ -46,6 +46,7 @@ module Interpol
46
46
  end
47
47
 
48
48
  self.endpoint_definition_files = []
49
+ self.documentation_title = "API Documentation Provided by Interpol"
49
50
  yield self if block_given?
50
51
  end
51
52
 
@@ -0,0 +1,72 @@
1
+ require 'nokogiri'
2
+
3
+ module Interpol
4
+ module Documentation
5
+ extend self
6
+
7
+ def html_for_schema(schema)
8
+ ForSchemaDefinition.new(schema).to_html
9
+ end
10
+
11
+ # Renders the documentation for a schema definition.
12
+ class ForSchemaDefinition
13
+ def initialize(schema)
14
+ @schema = schema
15
+ end
16
+
17
+ def to_html
18
+ build do |doc|
19
+ schema_definition(doc, @schema)
20
+ end.to_html
21
+ end
22
+
23
+ private
24
+
25
+ def build
26
+ Nokogiri::HTML::DocumentFragment.parse("").tap do |doc|
27
+ Nokogiri::HTML::Builder.with(doc) do |doc|
28
+ yield doc
29
+ end
30
+ end
31
+ end
32
+
33
+ def schema_description(doc, schema)
34
+ return unless schema.has_key?('description')
35
+ doc.h3(class: "description") { doc.text(schema['description']) }
36
+ end
37
+
38
+ def schema_definition(doc, schema)
39
+ doc.div(class: "schema-definition") do
40
+ schema_description(doc, schema)
41
+ render_properties(doc, Array(schema['properties']))
42
+ end
43
+ end
44
+
45
+ def render_properties(doc, properties)
46
+ return if properties.none?
47
+
48
+ doc.dl(class: "properties") do
49
+ properties.each do |name, property|
50
+ property_definition(doc, name, property)
51
+ end
52
+ end
53
+ end
54
+
55
+ def property_definition(doc, name, property)
56
+ doc.dt(class: "name") { doc.text(property_title name, property) }
57
+
58
+ if property.has_key?('description')
59
+ doc.dd { doc.text(property['description']) }
60
+ end
61
+
62
+ render_properties(doc, Array(property['properties']))
63
+ end
64
+
65
+ def property_title(name, property)
66
+ return name unless property['type']
67
+ "#{name} (#{property['type']})"
68
+ end
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,103 @@
1
+ require 'interpol'
2
+ require 'interpol/documentation'
3
+ require 'sinatra/base'
4
+ require 'nokogiri'
5
+
6
+ module Interpol
7
+ module DocumentationApp
8
+ extend self
9
+
10
+ def build(&block)
11
+ config = Configuration.default.customized_duplicate(&block)
12
+ Builder.new(config).app
13
+ end
14
+
15
+ def render_static_page(&block)
16
+ require 'rack/mock'
17
+ app = build(&block)
18
+ status, headers, body = app.call(Rack::MockRequest.env_for "/", method: "GET")
19
+ AssetInliner.new(body.join, app.public_folder).standalone_page
20
+ end
21
+
22
+ # Inlines the assets so the page can be viewed as a standalone web page.
23
+ class AssetInliner
24
+ def initialize(page, asset_root)
25
+ @page, @asset_root = page, asset_root
26
+ @doc = Nokogiri::HTML(page)
27
+ end
28
+
29
+ def standalone_page
30
+ inline_stylesheets
31
+ inline_javascript
32
+ @doc.to_s
33
+ end
34
+
35
+ private
36
+
37
+ def inline_stylesheets
38
+ @doc.css("link[rel=stylesheet]").map do |link|
39
+ inline_asset link, "style", link['href'], type: "text/css"
40
+ end
41
+ end
42
+
43
+ def inline_javascript
44
+ @doc.css("script[src]").each do |script|
45
+ inline_asset script, "script", script['src'], type: "text/javascript"
46
+ end
47
+ end
48
+
49
+ def contents_for(asset)
50
+ File.read(File.join(@asset_root, asset))
51
+ end
52
+
53
+ def inline_asset(tag, tag_type, filename, attributes = {})
54
+ inline_tag = Nokogiri::XML::Node.new(tag_type, @doc)
55
+ attributes.each { |k, v| inline_tag[k] = v }
56
+ inline_tag.content = contents_for(filename)
57
+ tag.add_next_sibling(inline_tag)
58
+ tag.remove
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ module Helpers
65
+ def interpol_config
66
+ self.class.interpol_config
67
+ end
68
+
69
+ def endpoints
70
+ interpol_config.endpoints
71
+ end
72
+
73
+ def current_endpoint
74
+ endpoints.first
75
+ end
76
+
77
+ def title
78
+ interpol_config.documentation_title
79
+ end
80
+ end
81
+
82
+ # Private: Builds a stub sinatra app for the given interpol
83
+ # configuration.
84
+ class Builder
85
+ attr_reader :app
86
+
87
+ def initialize(config)
88
+ @app = Sinatra.new do
89
+ dir = File.dirname(File.expand_path(__FILE__))
90
+ set :views, "#{dir}/documentation_app/views"
91
+ set :public_folder, "#{dir}/documentation_app/public"
92
+ set :interpol_config, config
93
+ helpers Helpers
94
+
95
+ get('/') do
96
+ erb :layout, locals: { endpoints: endpoints, current_endpoint: current_endpoint }
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,24 @@
1
+ # Require any additional compass plugins here.
2
+ require 'compass_twitter_bootstrap'
3
+
4
+ # Set this to the root of your project when deployed:
5
+ http_path = "/"
6
+ css_dir = "public/stylesheets"
7
+ sass_dir = "sass"
8
+ images_dir = "public/images"
9
+ javascripts_dir = "public/javascripts"
10
+
11
+ # You can select your preferred output style here (can be overridden via the command line):
12
+ # output_style = :expanded or :nested or :compact or :compressed
13
+
14
+ # To enable relative paths to assets via compass helper functions. Uncomment:
15
+ relative_assets = true
16
+
17
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
18
+ line_comments = false
19
+
20
+ # If you prefer the indented syntax, you might want to regenerate this
21
+ # project again passing --syntax sass, or you can uncomment this:
22
+ # preferred_syntax = :sass
23
+ # and then run:
24
+ # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
@@ -24,6 +24,7 @@ module Interpol
24
24
  @route = fetch_from(endpoint_hash, 'route')
25
25
  @method = fetch_from(endpoint_hash, 'method').downcase.to_sym
26
26
  @definitions = extract_definitions_from(endpoint_hash)
27
+ validate_name!
27
28
  end
28
29
 
29
30
  def find_definition!(version)
@@ -76,6 +77,13 @@ module Interpol
76
77
 
77
78
  definitions
78
79
  end
80
+
81
+ def validate_name!
82
+ unless name =~ /\A[\w\-]+\z/
83
+ raise ArgumentError, "Invalid endpoint name (#{name.inspect}). "+
84
+ "Only letters, numbers, underscores and dashes are allowed."
85
+ end
86
+ end
79
87
  end
80
88
 
81
89
  # Wraps a single versioned definition for an endpoint.
@@ -1,3 +1,3 @@
1
1
  module Interpol
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interpol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-05 00:00:00.000000000 Z
12
+ date: 2012-04-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
16
- requirement: &2155991900 !ruby/object:Gem::Requirement
16
+ requirement: &2152438820 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -24,10 +24,10 @@ dependencies:
24
24
  version: 2.0.0
25
25
  type: :runtime
26
26
  prerelease: false
27
- version_requirements: *2155991900
27
+ version_requirements: *2152438820
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: json-schema
30
- requirement: &2155989620 !ruby/object:Gem::Requirement
30
+ requirement: &2152437840 !ruby/object:Gem::Requirement
31
31
  none: false
32
32
  requirements:
33
33
  - - ~>
@@ -35,10 +35,21 @@ dependencies:
35
35
  version: 1.0.5
36
36
  type: :runtime
37
37
  prerelease: false
38
- version_requirements: *2155989620
38
+ version_requirements: *2152437840
39
+ - !ruby/object:Gem::Dependency
40
+ name: nokogiri
41
+ requirement: &2152437180 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '1.5'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: *2152437180
39
50
  - !ruby/object:Gem::Dependency
40
51
  name: rspec
41
- requirement: &2155987560 !ruby/object:Gem::Requirement
52
+ requirement: &2152436180 !ruby/object:Gem::Requirement
42
53
  none: false
43
54
  requirements:
44
55
  - - ~>
@@ -46,10 +57,10 @@ dependencies:
46
57
  version: '2.9'
47
58
  type: :development
48
59
  prerelease: false
49
- version_requirements: *2155987560
60
+ version_requirements: *2152436180
50
61
  - !ruby/object:Gem::Dependency
51
62
  name: rspec-fire
52
- requirement: &2155985640 !ruby/object:Gem::Requirement
63
+ requirement: &2152435480 !ruby/object:Gem::Requirement
53
64
  none: false
54
65
  requirements:
55
66
  - - ~>
@@ -57,10 +68,10 @@ dependencies:
57
68
  version: '0.4'
58
69
  type: :development
59
70
  prerelease: false
60
- version_requirements: *2155985640
71
+ version_requirements: *2152435480
61
72
  - !ruby/object:Gem::Dependency
62
73
  name: simplecov
63
- requirement: &2155984820 !ruby/object:Gem::Requirement
74
+ requirement: &2152434900 !ruby/object:Gem::Requirement
64
75
  none: false
65
76
  requirements:
66
77
  - - ~>
@@ -68,10 +79,10 @@ dependencies:
68
79
  version: '0.6'
69
80
  type: :development
70
81
  prerelease: false
71
- version_requirements: *2155984820
82
+ version_requirements: *2152434900
72
83
  - !ruby/object:Gem::Dependency
73
84
  name: cane
74
- requirement: &2155983800 !ruby/object:Gem::Requirement
85
+ requirement: &2152433580 !ruby/object:Gem::Requirement
75
86
  none: false
76
87
  requirements:
77
88
  - - ~>
@@ -79,10 +90,10 @@ dependencies:
79
90
  version: '1.2'
80
91
  type: :development
81
92
  prerelease: false
82
- version_requirements: *2155983800
93
+ version_requirements: *2152433580
83
94
  - !ruby/object:Gem::Dependency
84
95
  name: rake
85
- requirement: &2151940960 !ruby/object:Gem::Requirement
96
+ requirement: &2152433060 !ruby/object:Gem::Requirement
86
97
  none: false
87
98
  requirements:
88
99
  - - ~>
@@ -90,10 +101,10 @@ dependencies:
90
101
  version: 0.9.2.2
91
102
  type: :development
92
103
  prerelease: false
93
- version_requirements: *2151940960
104
+ version_requirements: *2152433060
94
105
  - !ruby/object:Gem::Dependency
95
106
  name: rack-test
96
- requirement: &2151939280 !ruby/object:Gem::Requirement
107
+ requirement: &2152432580 !ruby/object:Gem::Requirement
97
108
  none: false
98
109
  requirements:
99
110
  - - =
@@ -101,7 +112,7 @@ dependencies:
101
112
  version: 0.6.1
102
113
  type: :development
103
114
  prerelease: false
104
- version_requirements: *2151939280
115
+ version_requirements: *2152432580
105
116
  description: Interpol is a toolkit for working with API endpoint definition files,
106
117
  giving you a stub app, a schema validation middleware, and browsable documentation.
107
118
  email:
@@ -115,6 +126,9 @@ files:
115
126
  - Gemfile
116
127
  - Rakefile
117
128
  - lib/interpol/configuration.rb
129
+ - lib/interpol/documentation.rb
130
+ - lib/interpol/documentation_app/config.rb
131
+ - lib/interpol/documentation_app.rb
118
132
  - lib/interpol/endpoint.rb
119
133
  - lib/interpol/errors.rb
120
134
  - lib/interpol/response_schema_validator.rb