useless-doc 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,7 +20,7 @@ module Useless
20
20
  #
21
21
  def initialize(attrs)
22
22
  @content_type = attrs[:content_type]
23
- @attributes = attrs[:attributes]
23
+ @attributes = attrs[:attributes] || []
24
24
  end
25
25
 
26
26
  # Documentation for an attribute on an HTTP body.
@@ -13,12 +13,15 @@ module Useless
13
13
  # @!attribute [r] description
14
14
  # @return [String] a description of the domain.
15
15
  #
16
+ # @!attribute [r] timestamp
17
+ # @return [Time] the time that this domain doc was last updated.
18
+ #
16
19
  # @!attribute [r] apis
17
20
  # @return [Array<API>] the APIs included in this domain.
18
21
  #
19
22
  class Domain
20
23
 
21
- attr_accessor :name, :url, :description, :apis
24
+ attr_accessor :name, :url, :description, :timestamp, :apis
22
25
 
23
26
  # @param [Hash] attrs corresponds to the class's instance attributes.
24
27
  #
@@ -26,7 +29,8 @@ module Useless
26
29
  @name = attrs[:name]
27
30
  @url = attrs[:url]
28
31
  @description = attrs[:description]
29
- @apis = attrs[:apis]
32
+ @timestamp = attrs[:timestamp]
33
+ @apis = attrs[:apis] || []
30
34
  end
31
35
  end
32
36
  end
@@ -54,10 +54,10 @@ module Useless
54
54
  @method = attrs[:method]
55
55
  @description = attrs[:description]
56
56
  @authentication_required = attrs[:authentication_required]
57
- @parameters = attrs[:parameters]
58
- @headers = attrs[:headers]
57
+ @parameters = attrs[:parameters] || []
58
+ @headers = attrs[:headers] || []
59
59
  @body = attrs[:body]
60
- @responses = attrs[:responses]
60
+ @responses = attrs[:responses] || []
61
61
  end
62
62
 
63
63
  # Documentation for a request parameter for an API action.
@@ -24,7 +24,7 @@ module Useless
24
24
  def initialize(attrs = {})
25
25
  @path = attrs[:path]
26
26
  @description = attrs[:description]
27
- @requests = attrs[:requests]
27
+ @requests = attrs[:requests] || []
28
28
  end
29
29
  end
30
30
  end
@@ -26,7 +26,7 @@ module Useless
26
26
  def initialize(attrs = {})
27
27
  @code = attrs[:code]
28
28
  @description = attrs[:description]
29
- @headers = attrs[:headers]
29
+ @headers = attrs[:headers] || []
30
30
  @body = attrs[:body]
31
31
  end
32
32
  end
@@ -84,6 +84,14 @@ module Useless
84
84
  @attributes[:description] = description
85
85
  end
86
86
 
87
+ def timestamp(timestamp)
88
+ if timestamp.is_a?(String)
89
+ timestamp = Time.parse(timestamp)
90
+ end
91
+
92
+ @attributes[:timestamp] = timestamp
93
+ end
94
+
87
95
  def api(name, &block)
88
96
  api = API.build name: name, &block
89
97
  @attributes[:apis] << api
@@ -11,6 +11,16 @@ module Useless
11
11
  @default ||= Doc::Router::Default.new
12
12
  end
13
13
 
14
+ def self.uri_for(raw_url)
15
+ url = raw_url.dup
16
+
17
+ unless url =~ /^https?:\/\//
18
+ url = 'http://' + url
19
+ end
20
+
21
+ URI(url)
22
+ end
23
+
14
24
  def doc_for_api(url)
15
25
  end
16
26
 
@@ -26,7 +36,7 @@ module Useless
26
36
 
27
37
  def doc_for_api(url)
28
38
  return nil unless supported_url?(url)
29
- uri = URI(url)
39
+ uri = Router.uri_for(url)
30
40
  host = uri.host
31
41
  new_host = host.
32
42
  split('.').
@@ -36,7 +46,7 @@ module Useless
36
46
  end
37
47
 
38
48
  def api_for_doc(url)
39
- uri = URI(url)
49
+ uri = Router.uri_for(url)
40
50
  host = uri.host
41
51
  parts = host.split('.')
42
52
  parts.slice!(-3) if parts[-3] == 'doc'
@@ -30,6 +30,7 @@ module Useless
30
30
  'name' => domain.name,
31
31
  'url' => domain.url,
32
32
  'description' => domain.description,
33
+ 'timestamp' => domain.timestamp ? domain.timestamp.iso8601 : nil,
33
34
  'apis' => domain.apis.map { |api| api(api) }
34
35
  end
35
36
  end
@@ -46,6 +47,7 @@ module Useless
46
47
  'name' => api.name,
47
48
  'url' => api.url,
48
49
  'description' => api.description,
50
+ 'timestamp' => api.timestamp ? api.timestamp.iso8601 : nil,
49
51
  'resources' => api.resources.map { |resource| resource(resource) }
50
52
  end
51
53
  end
@@ -51,9 +51,16 @@ module Useless
51
51
  api json
52
52
  end
53
53
 
54
+ timestamp = begin
55
+ Time.parse(hash['timestamp'])
56
+ rescue TypeError, ArgumentError
57
+ nil
58
+ end
59
+
54
60
  Useless::Doc::Core::Domain.new \
55
61
  name: hash['name'],
56
62
  url: hash['url'],
63
+ timestamp: timestamp,
57
64
  description: hash['description'],
58
65
  apis: apis
59
66
  end
@@ -73,9 +80,16 @@ module Useless
73
80
  resource json
74
81
  end
75
82
 
83
+ timestamp = begin
84
+ Time.parse(hash['timestamp'])
85
+ rescue TypeError, ArgumentError
86
+ nil
87
+ end
88
+
76
89
  Useless::Doc::Core::API.new \
77
90
  name: hash['name'],
78
91
  url: hash['url'],
92
+ timestamp: timestamp,
79
93
  description: hash['description'],
80
94
  resources: resources
81
95
  end
@@ -30,8 +30,8 @@
30
30
  </header>
31
31
 
32
32
  <section class="main description">
33
- <article>
34
- <p class="description api">{{description}}</p>
33
+ <article class="description api">
34
+ {{{description}}}
35
35
  </article>
36
36
  </section>
37
37
 
@@ -13,14 +13,14 @@
13
13
  <section class="api header-section">
14
14
  <a class="name header-section-title" href="{{doc_url}}">{{name}}</a>
15
15
 
16
- <p class="description">{{description}}</p>
16
+ {{{description}}}
17
17
  </section>
18
18
  {{/apis}}
19
19
  </header>
20
20
 
21
21
  <section class="main description">
22
- <article>
23
- <p class="description domain">{{description}}</p>
22
+ <article class="description domain">
23
+ {{{description}}}
24
24
  </article>
25
25
  </section>
26
26
  </body>
@@ -10,6 +10,7 @@ h1, h2, h3, h4 {
10
10
  font-family: 'Open Sans Condensed', sans-serif;
11
11
  margin-top: 0;
12
12
  margin-bottom: 0.25em;
13
+ color: #333;
13
14
  }
14
15
 
15
16
  h2 {
@@ -35,11 +36,19 @@ h4 span.content-type {
35
36
  font-size: 0.75em;
36
37
  }
37
38
 
38
- p.description {
39
- color: #888;
39
+ article.description {
40
+ color: #555;
40
41
  margin-top: 0;
41
42
  }
42
43
 
44
+ article.description code {
45
+ font-family: 'Courier New', 'Courier', monospace;
46
+ }
47
+
48
+ article.description h2 {
49
+ margin-bottom: 0.25em;
50
+ }
51
+
43
52
  p.description.request {
44
53
  margin-bottom: 0.5em;
45
54
  }
@@ -71,7 +80,7 @@ header section.header-section.api {
71
80
  }
72
81
 
73
82
  header section.header-section p {
74
- margin-bottom: 0.75em;
83
+ margin: 0.25em 0 0.75em;
75
84
  }
76
85
 
77
86
  header section.header-section a.header-section-title {
@@ -85,7 +94,7 @@ header section.header-section td.description {
85
94
 
86
95
  section.main {
87
96
  position: relative;
88
- margin-left: 313px;
97
+ margin-left: 303px;
89
98
  min-width: 520px;
90
99
  }
91
100
 
@@ -1,6 +1,7 @@
1
1
  require 'mustache'
2
2
  require 'forwardable'
3
3
  require 'rack/utils'
4
+ require 'redcarpet'
4
5
 
5
6
  require 'useless/doc/core/domain'
6
7
  require 'useless/doc/core/api'
@@ -16,6 +17,14 @@ module Useless
16
17
  File.dirname(__FILE__) + '/godel'
17
18
  end
18
19
 
20
+ def self.markdown
21
+ @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML,
22
+ :no_intra_emphasis => true, :tables => true,
23
+ :fenced_code_blocks => true,:autolink => true,
24
+ :space_after_headers => true, :lax_spacing => true,
25
+ :superscript => true)
26
+ end
27
+
19
28
  def initialize(router)
20
29
  @router = router
21
30
  end
@@ -39,7 +48,7 @@ module Useless
39
48
  class Domain < Mustache
40
49
  extend Forwardable
41
50
 
42
- def_delegators :@domain, :name, :description
51
+ def_delegators :@domain, :name
43
52
 
44
53
  self.template_file = Godel.asset_path + '/domain.mustache'
45
54
 
@@ -48,6 +57,10 @@ module Useless
48
57
  @router = router
49
58
  end
50
59
 
60
+ def description
61
+ Godel.markdown.render(@domain.description)
62
+ end
63
+
51
64
  def apis
52
65
  @domain.apis.map{ |api| Godel::API.new(api, @router) }
53
66
  end
@@ -56,7 +69,7 @@ module Useless
56
69
  class API < Mustache
57
70
  extend Forwardable
58
71
 
59
- def_delegators :@api, :name, :url, :description
72
+ def_delegators :@api, :name, :url
60
73
 
61
74
  self.template_file = Godel.asset_path + '/api.mustache'
62
75
 
@@ -69,6 +82,10 @@ module Useless
69
82
  @router.doc_for_api(@api.url)
70
83
  end
71
84
 
85
+ def description
86
+ Godel.markdown.render(@api.description)
87
+ end
88
+
72
89
  def resources
73
90
  @api.resources.map{ |resource| Godel::Resource.new(resource, @router) }
74
91
  end
@@ -1,5 +1,5 @@
1
1
  module Useless
2
2
  module Doc
3
- VERSION = '0.3.1'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "Twonk API",
3
3
  "url": "twonk.useless.io",
4
+ "timestamp": "2013-03-06T23:13:00-05:00",
4
5
  "description": "Twonk information. Duh.",
5
6
  "resources": [
6
7
  {
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "Useless",
3
3
  "url": "http://useless.io",
4
+ "timestamp": "2013-03-06T23:13:00-05:00",
4
5
  "description": "A collection of useless APIs.",
5
6
  "apis": [
6
7
  {
@@ -11,6 +11,7 @@ describe Useless::Doc::DSL::Domain do
11
11
  domain = Useless::Doc.domain 'useless.io' do
12
12
  url 'http://useless.io'
13
13
  description 'A collection of useless APIs.'
14
+ timestamp '2013-03-06 11:13 PM'
14
15
 
15
16
  api 'Twonk' do
16
17
  url 'twonk.useless.io'
@@ -21,6 +22,7 @@ describe Useless::Doc::DSL::Domain do
21
22
  domain.name.should == 'useless.io'
22
23
  domain.url.should == 'http://useless.io'
23
24
  domain.description.should == 'A collection of useless APIs.'
25
+ domain.timestamp.should == Time.parse('2013-03-06 11:13 PM')
24
26
 
25
27
  domain.apis.first.name.should == 'Twonk'
26
28
  domain.apis.first.url.should == 'twonk.useless.io'
@@ -18,6 +18,11 @@ describe Useless::Doc::Router::Default do
18
18
  it 'should return nil if the specified URL is not part of the domain' do
19
19
  @router.doc_for_api('http://nonexistant.domain.com/some/path').should be_nil
20
20
  end
21
+
22
+ it 'should handle URLs that are missing a scheme' do
23
+ @router.doc_for_api('api.domain.com/some/path').
24
+ should == 'http://api.doc.domain.com/some/path'
25
+ end
21
26
  end
22
27
 
23
28
  describe '.api_for_doc' do
@@ -34,5 +39,10 @@ describe Useless::Doc::Router::Default do
34
39
  it 'should return nil if the resulting URL is not part of the domain' do
35
40
  @router.api_for_doc('http://nonexistant.doc.domain.com/some/path').should be_nil
36
41
  end
42
+
43
+ it 'should handle URLs that are missing a scheme' do
44
+ @router.api_for_doc('api.doc.domain.com/some/path').
45
+ should == 'http://api.domain.com/some/path'
46
+ end
37
47
  end
38
48
  end
@@ -33,6 +33,7 @@ describe Useless::Doc::Serialization::Dump do
33
33
  domain = Useless::Doc::Core::Domain.new \
34
34
  name: 'Useless',
35
35
  url: 'http://useless.io',
36
+ timestamp: Time.parse('2013-03-06 11:13 PM'),
36
37
  description: 'A collection of useless APIs.',
37
38
  apis: [api]
38
39
 
@@ -40,6 +41,7 @@ describe Useless::Doc::Serialization::Dump do
40
41
  hash = Useless::Doc::Serialization::Load.json_to_hash(json)
41
42
  hash['name'].should == 'Useless'
42
43
  hash['url'].should == 'http://useless.io'
44
+ hash['timestamp'].should == Time.parse('2013-03-06 11:13 PM').iso8601
43
45
  hash['description'].should == 'A collection of useless APIs.'
44
46
 
45
47
  api_hash = Useless::Doc::Serialization::Load.json_to_hash(hash['apis'][0])
@@ -59,6 +61,7 @@ describe Useless::Doc::Serialization::Dump do
59
61
  api = Useless::Doc::Core::API.new \
60
62
  name: 'Twiddles API',
61
63
  url: 'twiddles.useless.io',
64
+ timestamp: Time.parse('2013-03-06 11:13 PM'),
62
65
  description: 'Pretty much, like, everything you\'re looking for',
63
66
  resources: [resource]
64
67
 
@@ -66,6 +69,7 @@ describe Useless::Doc::Serialization::Dump do
66
69
  hash = Useless::Doc::Serialization::Load.json_to_hash(json)
67
70
  hash['name'].should == 'Twiddles API'
68
71
  hash['url'].should == 'twiddles.useless.io'
72
+ hash['timestamp'].should == Time.parse('2013-03-06 11:13 PM').iso8601
69
73
  hash['description'].should == 'Pretty much, like, everything you\'re looking for'
70
74
 
71
75
  resource_hash = Useless::Doc::Serialization::Load.json_to_hash(hash['resources'][0])
@@ -45,6 +45,7 @@ describe Useless::Doc::Serialization::Load do
45
45
  domain = Useless::Doc::Serialization::Load.domain document.read
46
46
  domain.name.should == 'Useless'
47
47
  domain.url.should == 'http://useless.io'
48
+ domain.timestamp.should == Time.parse('2013-03-06T23:13:00-05:00')
48
49
  domain.description.should == 'A collection of useless APIs.'
49
50
  domain.apis.first.name.should == 'The Jah API'
50
51
  domain.apis.first.url.should == 'http://jah.useless.io'
@@ -58,6 +59,7 @@ describe Useless::Doc::Serialization::Load do
58
59
  api = Useless::Doc::Serialization::Load.api document.read
59
60
  api.name.should == 'Twonk API'
60
61
  api.url.should == 'twonk.useless.io'
62
+ api.timestamp.should == Time.parse('2013-03-06T23:13:00-05:00')
61
63
  api.description.should == 'Twonk information. Duh.'
62
64
  api.resources.first.path.should == '/twonks/:id'
63
65
  api.resources.first.description.should == 'The most critical aspect.'
@@ -2,6 +2,8 @@ require File.dirname(__FILE__) + '/../../../spec_helper'
2
2
 
3
3
  require 'nokogiri'
4
4
  require 'useless/doc'
5
+ require 'useless/doc/core/domain'
6
+ require 'useless/doc/core/api'
5
7
  require 'useless/doc/router'
6
8
  require 'useless/doc/ui/godel'
7
9
 
@@ -9,10 +11,10 @@ describe Useless::Doc::UI::Godel do
9
11
  describe '.html' do
10
12
  context 'for a Core::Domain instance' do
11
13
  before(:all) do
12
- router = Useless::Doc::Router.default
14
+ @router = Useless::Doc::Router.default
13
15
  json = load_document('domain.json').read
14
16
  domain = Useless::Doc.load.domain(json)
15
- result = Useless::Doc::UI::Godel.new(router).html(domain)
17
+ result = Useless::Doc::UI::Godel.new(@router).html(domain)
16
18
  @doc = Nokogiri::HTML(result)
17
19
  end
18
20
 
@@ -23,11 +25,25 @@ describe Useless::Doc::UI::Godel do
23
25
  end
24
26
 
25
27
  it 'should render the domain description in an article p' do
26
- description = @doc.css('article p.description.domain')
28
+ description = @doc.css('article.description.domain p')
27
29
  description.length.should == 1
28
30
  description.first.content.should == 'A collection of useless APIs.'
29
31
  end
30
32
 
33
+ it 'should not escape the description text' do
34
+ domain = Useless::Doc::Core::Domain.new description: '<span>The description</span>'
35
+ result = Useless::Doc::UI::Godel.new(@router).html(domain)
36
+ description = Nokogiri::HTML(result).css('article.description.domain p')
37
+ description.first.inner_html.should == '<span>The description</span>'
38
+ end
39
+
40
+ it 'should interpret Markdown for the description' do
41
+ domain = Useless::Doc::Core::Domain.new description: '*The description*'
42
+ result = Useless::Doc::UI::Godel.new(@router).html(domain)
43
+ description = Nokogiri::HTML(result).css('article.description.domain p')
44
+ description.first.inner_html.should == '<em>The description</em>'
45
+ end
46
+
31
47
  it 'should render API names as a\'s, with correct doc URLs' do
32
48
  as = @doc.css('a.name')
33
49
  as.length.should == 2
@@ -58,11 +74,25 @@ describe Useless::Doc::UI::Godel do
58
74
  end
59
75
 
60
76
  it 'should render the API description in an article p' do
61
- description = @doc.css('article p.description.api')
77
+ description = @doc.css('article.description.api p')
62
78
  description.length.should == 1
63
79
  description.first.content.should == 'Twonk information. Duh.'
64
80
  end
65
81
 
82
+ it 'should not escape the description text' do
83
+ domain = Useless::Doc::Core::API.new description: '<span>The description</span>'
84
+ result = Useless::Doc::UI::Godel.new(@router).html(domain)
85
+ description = Nokogiri::HTML(result).css('article.description.api p')
86
+ description.first.inner_html.should == '<span>The description</span>'
87
+ end
88
+
89
+ it 'should interpret Markdown for the description' do
90
+ domain = Useless::Doc::Core::API.new description: '*The description*'
91
+ result = Useless::Doc::UI::Godel.new(@router).html(domain)
92
+ description = Nokogiri::HTML(result).css('article.description.api p')
93
+ description.first.inner_html.should == '<em>The description</em>'
94
+ end
95
+
66
96
  it 'should render resouce paths as a\'s' do
67
97
  as = @doc.css('a.path')
68
98
  as.length.should == 2
data/useless-doc.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency 'low', '~> 0.0.17'
21
21
  gem.add_dependency 'sinatra', '~> 1.3.4'
22
22
  gem.add_dependency 'mustache', '~> 0.99.4'
23
+ gem.add_dependency 'redcarpet', '~> 2.2.2'
23
24
 
24
25
  gem.add_development_dependency 'rspec', '~> 2.12.0'
25
26
  gem.add_development_dependency 'rack-test', '~> 0.6.2'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: useless-doc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-23 00:00:00.000000000 Z
12
+ date: 2013-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: oj
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ~>
108
108
  - !ruby/object:Gem::Version
109
109
  version: 0.99.4
110
+ - !ruby/object:Gem::Dependency
111
+ name: redcarpet
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 2.2.2
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 2.2.2
110
126
  - !ruby/object:Gem::Dependency
111
127
  name: rspec
112
128
  requirement: !ruby/object:Gem::Requirement