interpol 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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