useless-doc 0.3.1 → 0.4.0

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.
@@ -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