fdoc 0.2.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.
Files changed (36) hide show
  1. data/Gemfile +2 -0
  2. data/README.md +120 -0
  3. data/Rakefile +7 -0
  4. data/bin/fdoc_to_html +77 -0
  5. data/fdoc.gemspec +36 -0
  6. data/lib/endpoint-schema.yaml +30 -0
  7. data/lib/fdoc.rb +46 -0
  8. data/lib/fdoc/endpoint.rb +110 -0
  9. data/lib/fdoc/endpoint_scaffold.rb +132 -0
  10. data/lib/fdoc/meta_service.rb +46 -0
  11. data/lib/fdoc/presenters/endpoint_presenter.rb +152 -0
  12. data/lib/fdoc/presenters/html_presenter.rb +58 -0
  13. data/lib/fdoc/presenters/meta_service_presenter.rb +65 -0
  14. data/lib/fdoc/presenters/response_code_presenter.rb +32 -0
  15. data/lib/fdoc/presenters/schema_presenter.rb +138 -0
  16. data/lib/fdoc/presenters/service_presenter.rb +56 -0
  17. data/lib/fdoc/service.rb +88 -0
  18. data/lib/fdoc/spec_watcher.rb +48 -0
  19. data/lib/fdoc/templates/endpoint.html.erb +75 -0
  20. data/lib/fdoc/templates/meta_service.html.erb +60 -0
  21. data/lib/fdoc/templates/service.html.erb +54 -0
  22. data/lib/fdoc/templates/styles.css +63 -0
  23. data/spec/fdoc/endpoint_scaffold_spec.rb +242 -0
  24. data/spec/fdoc/endpoint_spec.rb +243 -0
  25. data/spec/fdoc/presenters/endpoint_presenter_spec.rb +93 -0
  26. data/spec/fdoc/presenters/service_presenter_spec.rb +18 -0
  27. data/spec/fdoc/service_spec.rb +63 -0
  28. data/spec/fixtures/members/add-PUT.fdoc +20 -0
  29. data/spec/fixtures/members/draft-POST.fdoc +5 -0
  30. data/spec/fixtures/members/list/GET.fdoc +50 -0
  31. data/spec/fixtures/members/list/complex-params-GET.fdoc +94 -0
  32. data/spec/fixtures/members/list/filter-GET.fdoc +60 -0
  33. data/spec/fixtures/members/members.fdoc.service +11 -0
  34. data/spec/fixtures/sample_group.fdoc.meta +9 -0
  35. data/spec/spec_helper.rb +2 -0
  36. metadata +174 -0
@@ -0,0 +1,46 @@
1
+ require 'yaml'
2
+
3
+ # MetaServices are collections of services
4
+ class Fdoc::MetaService
5
+ attr_reader :meta_service_dir
6
+
7
+ def initialize(meta_service_dir)
8
+ @meta_service_dir = meta_service_dir
9
+
10
+ service_path = Dir["#{meta_service_dir}/*.fdoc.meta"].first
11
+ @schema = if service_path
12
+ YAML.load_file(service_path)
13
+ else
14
+ {}
15
+ end
16
+ end
17
+
18
+ def empty?
19
+ @schema.empty?
20
+ end
21
+
22
+ def services
23
+ @schema['services'].map do |path|
24
+ service_path = if path.start_with?('/') || path.start_with?('~')
25
+ path
26
+ else
27
+ File.join(meta_service_dir, path)
28
+ end
29
+ serv = Fdoc::Service.new(service_path)
30
+ serv.meta_service = self
31
+ serv
32
+ end
33
+ end
34
+
35
+ def name
36
+ @schema['name']
37
+ end
38
+
39
+ def description
40
+ @schema['description']
41
+ end
42
+
43
+ def discussion
44
+ @schema['discussion']
45
+ end
46
+ end
@@ -0,0 +1,152 @@
1
+ # HtmlPresenter for an Endpoint
2
+ class Fdoc::EndpointPresenter < Fdoc::HtmlPresenter
3
+ attr_accessor :service_presenter, :endpoint
4
+
5
+ def initialize(endpoint, options = {})
6
+ super(options)
7
+ @endpoint = endpoint
8
+ end
9
+
10
+ def to_html
11
+ render_erb('endpoint.html.erb')
12
+ end
13
+
14
+ def name
15
+ <<-EOS
16
+ <span class="endpoint-name #{@endpoint.deprecated? ? 'deprecated' : nil}">
17
+ <span class="verb">#{@endpoint.verb}</span>
18
+ <span class="root">#{zws_ify(@endpoint.service.base_path)}</span><span
19
+ class="path">#{zws_ify(@endpoint.path)}</span>
20
+ #{@endpoint.deprecated? ? '(deprecated)' : nil}
21
+ </span>
22
+ EOS
23
+ end
24
+
25
+ def name_as_link
26
+ <<-EOS
27
+ <a href="#{url}">
28
+ #{name}
29
+ </a>
30
+ EOS
31
+ end
32
+
33
+ def url(extension = ".html")
34
+ '%s%s-%s%s' % [ options[:prefix], endpoint.path, endpoint.verb, extension ]
35
+ end
36
+
37
+ def title
38
+ '%s %s - %s' % [ endpoint.verb, endpoint.path, endpoint.service.name ]
39
+ end
40
+
41
+ def prefix
42
+ endpoint.path.split("/").first
43
+ end
44
+
45
+ def zws_ify(str)
46
+ # zero-width-space, makes long lines friendlier for breaking
47
+ str.gsub(/\//, '&#8203;/') if str
48
+ end
49
+
50
+ def description
51
+ render_markdown(endpoint.description)
52
+ end
53
+
54
+ def show_request?
55
+ !endpoint.request_parameters.empty?
56
+ end
57
+
58
+ def show_response?
59
+ !endpoint.response_parameters.empty?
60
+ end
61
+
62
+ def request_parameters
63
+ Fdoc::SchemaPresenter.new(endpoint.request_parameters,
64
+ options.merge(:request => true)
65
+ ).to_html
66
+ end
67
+
68
+ def response_parameters
69
+ Fdoc::SchemaPresenter.new(endpoint.response_parameters, options).to_html
70
+ end
71
+
72
+ def response_codes
73
+ @response_codes ||= endpoint.response_codes.map do |response_code|
74
+ Fdoc::ResponseCodePresenter.new(response_code, options)
75
+ end
76
+ end
77
+
78
+ def successful_response_codes
79
+ response_codes.select { |response_code| response_code.successful? }
80
+ end
81
+
82
+ def failure_response_codes
83
+ response_codes.select { |response_code| !response_code.successful? }
84
+ end
85
+
86
+ def example_request
87
+ render_json(example_from_schema(endpoint.request_parameters))
88
+ end
89
+
90
+ def example_response
91
+ render_json(example_from_schema(endpoint.response_parameters))
92
+ end
93
+
94
+ ATOMIC_TYPES = %w(string integer number boolean null)
95
+
96
+ def example_from_schema(schema)
97
+ if schema.nil?
98
+ return nil
99
+ end
100
+
101
+ type = Array(schema["type"])
102
+
103
+ if type.any? { |t| ATOMIC_TYPES.include?(t) }
104
+ schema["example"] || schema["default"] || example_from_atom(schema)
105
+ elsif type.include?("object") || schema["properties"]
106
+ example_from_object(schema)
107
+ elsif type.include?("array") || schema["items"]
108
+ example_from_array(schema)
109
+ else
110
+ {}
111
+ end
112
+ end
113
+
114
+ def example_from_atom(schema)
115
+ type = Array(schema["type"])
116
+ hash = schema.hash
117
+
118
+ if type.include?("boolean")
119
+ [true, false][hash % 2]
120
+ elsif type.include?("integer")
121
+ hash % 1000
122
+ elsif type.include?("number")
123
+ Math.sqrt(hash % 1000).round 2
124
+ elsif type.include?("string")
125
+ ""
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ def example_from_object(object)
132
+ example = {}
133
+ if object["properties"]
134
+ object["properties"].each do |key, value|
135
+ example[key] = example_from_schema(value)
136
+ end
137
+ end
138
+ example
139
+ end
140
+
141
+ def example_from_array(array)
142
+ if array["items"].kind_of? Array
143
+ example = []
144
+ array["items"].each do |item|
145
+ example << example_from_schema(item)
146
+ end
147
+ example
148
+ else
149
+ [ example_from_schema(array["items"]) ]
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,58 @@
1
+ require 'erb'
2
+ require 'kramdown'
3
+
4
+ # HtmlPresenters assist in generating Html for fdoc classes.
5
+ # HtmlPresenters is an abstract class with a lot of helper methods
6
+ # for URLs and common text styling tasks (like #render_markdown
7
+ # and #render_json)
8
+ class Fdoc::HtmlPresenter
9
+ attr_reader :options
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def render_erb(erb_name)
16
+ template_path = File.join(File.dirname(__FILE__), "../templates", erb_name)
17
+ template = ERB.new(File.read(template_path))
18
+ template.result(binding)
19
+ end
20
+
21
+ def render_markdown(markdown_str)
22
+ if markdown_str
23
+ Kramdown::Document.new(markdown_str, :entity_output => :numeric).to_html
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def render_json(json)
30
+ if json.kind_of? String
31
+ '<tt>&quot;%s&quot;</tt>' % json.gsub(/\"/, 'quot;')
32
+ elsif json.kind_of?(Numeric) ||
33
+ json.kind_of?(TrueClass) ||
34
+ json.kind_of?(FalseClass)
35
+ '<tt>%s</tt>' % json
36
+ elsif json.kind_of?(Hash) ||
37
+ json.kind_of?(Array)
38
+ '<pre><code>%s</code></pre>' % JSON.pretty_generate(json)
39
+ end
40
+ end
41
+
42
+ def html_directory
43
+ options[:url_base_path] || options[:html_directory] || ""
44
+ end
45
+
46
+ def css_path
47
+ File.join(html_directory, "styles.css")
48
+ end
49
+
50
+ def index_path(subdirectory = "")
51
+ html_path = File.join(html_directory, subdirectory)
52
+ if options[:static_html]
53
+ File.join(html_path, 'index.html')
54
+ else
55
+ html_path
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,65 @@
1
+ # HtmlPresenter for Fdoc::MetaService
2
+ class Fdoc::MetaServicePresenter < Fdoc::HtmlPresenter
3
+ attr_reader :meta_service
4
+
5
+ def initialize(meta_service, options = {})
6
+ super(options)
7
+ @meta_service = meta_service
8
+ end
9
+
10
+ def to_html
11
+ render_erb('meta_service.html.erb')
12
+ end
13
+
14
+ def services
15
+ @services ||= meta_service.services.
16
+ sort_by(&:name).
17
+ map do |service|
18
+ Fdoc::ServicePresenter.new(service, options)
19
+ end
20
+ end
21
+
22
+ def endpoints
23
+ if !@endpoints
24
+ @endpoints = []
25
+ prefix = nil
26
+
27
+ ungrouped_endpoints.each do |endpoint|
28
+ presenter = presenter_from_endpoint(endpoint)
29
+ current_prefix = presenter.prefix
30
+
31
+ @endpoints << [] if prefix != current_prefix
32
+ @endpoints.last << presenter
33
+
34
+ prefix = current_prefix
35
+ end
36
+ end
37
+
38
+ @endpoints
39
+ end
40
+
41
+ def description
42
+ render_markdown(meta_service.description)
43
+ end
44
+
45
+ def discussion
46
+ render_markdown(meta_service.discussion)
47
+ end
48
+
49
+ private
50
+
51
+ def ungrouped_endpoints
52
+ meta_service.services.
53
+ map(&:endpoints).
54
+ flatten.
55
+ sort_by(&:endpoint_path)
56
+ end
57
+
58
+ def presenter_from_endpoint(endpoint)
59
+ service_presenter = Fdoc::ServicePresenter.new(endpoint.service)
60
+
61
+ presenter = Fdoc::EndpointPresenter.new(endpoint,
62
+ options.merge(:prefix => (service_presenter.slug_name + "/")))
63
+ presenter.service_presenter = service_presenter
64
+ end
65
+ end
@@ -0,0 +1,32 @@
1
+ # An HtmlPresenter for ResponseCodes
2
+ class Fdoc::ResponseCodePresenter < Fdoc::HtmlPresenter
3
+ attr_reader :response_code
4
+
5
+ def initialize(response_code, options)
6
+ super(options)
7
+ @response_code = response_code
8
+ end
9
+
10
+ def to_html
11
+ <<-EOS
12
+ <div class="response-code">
13
+ <span class="status">
14
+ #{status}
15
+ </span>
16
+ #{description}
17
+ </div>
18
+ EOS
19
+ end
20
+
21
+ def successful?
22
+ response_code["successful"]
23
+ end
24
+
25
+ def status
26
+ response_code["status"]
27
+ end
28
+
29
+ def description
30
+ render_markdown(response_code["description"])
31
+ end
32
+ end
@@ -0,0 +1,138 @@
1
+ # An HtmlPresenter for a JSON Schema fragment. Like most JSON
2
+ # schema things, has a tendency to recurse.
3
+ class Fdoc::SchemaPresenter < Fdoc::HtmlPresenter
4
+ FORMATTED_KEYS = %w(
5
+ description
6
+ type
7
+ required
8
+ example
9
+ deprecated
10
+ default
11
+ format
12
+ enum
13
+ items
14
+ properties
15
+ )
16
+
17
+ def initialize(schema, options)
18
+ super(options)
19
+ @schema = schema
20
+ end
21
+
22
+ def request?
23
+ options[:request]
24
+ end
25
+
26
+ def nested?
27
+ options[:nested]
28
+ end
29
+
30
+ def to_html
31
+ html = StringIO.new
32
+
33
+ html << '<span class="deprecated">Deprecated</span>' if deprecated?
34
+
35
+ html << '<div class="schema">'
36
+ html << render_markdown(@schema["description"])
37
+
38
+ html << '<ul>'
39
+ begin
40
+ html << '<li>Type: %s</li>' % type if type
41
+ html << '<li>Format: %s</li>' % format if format
42
+ html << '<li>Required: %s</li>' % required? if nested?
43
+ html << '<li>Example: %s</li>' % example if example
44
+ html << enum_html
45
+
46
+ (@schema.keys - FORMATTED_KEYS).each do |key|
47
+ html << '<li>%s: %s</li>' % [ key, @schema[key] ]
48
+ end
49
+
50
+ html << items_html
51
+ html << properties_html
52
+ end
53
+
54
+
55
+ html << '</ul>'
56
+ html << '</div>'
57
+
58
+ html.string
59
+ end
60
+
61
+ def type
62
+ t = @schema["type"]
63
+ if t.kind_of? Array
64
+ t.join(", ")
65
+ elsif t != "object"
66
+ t
67
+ end
68
+ end
69
+
70
+ def format
71
+ @schema["format"]
72
+ end
73
+
74
+ def example
75
+ return unless e = @schema["example"]
76
+
77
+ render_json(e)
78
+ end
79
+
80
+ def deprecated?
81
+ @schema["deprecated"]
82
+ end
83
+
84
+ def required?
85
+ @schema["required"] ? "yes" : "no"
86
+ end
87
+
88
+ def enum_html
89
+ enum = @schema["enum"]
90
+ return unless enum
91
+
92
+ list = enum.map do |e|
93
+ '<tt>%s</tt>' % e
94
+ end.join(", ")
95
+
96
+ html = StringIO.new
97
+ html << '<li>Enum: '
98
+ html << list
99
+ html << '</li>'
100
+ html.string
101
+ end
102
+
103
+ def items_html
104
+ return unless items = @schema["items"]
105
+
106
+ html = ""
107
+ html << '<li>Items'
108
+
109
+ sub_options = options.merge(:nested => true)
110
+
111
+ if items.kind_of? Array
112
+ item.compact.each do |item|
113
+ html << self.class.new(item, sub_options).to_html
114
+ end
115
+ else
116
+ html << self.class.new(items, sub_options).to_html
117
+ end
118
+
119
+ html << '</li>'
120
+ html
121
+ end
122
+
123
+ def properties_html
124
+ return unless properties = @schema["properties"]
125
+
126
+ html = ""
127
+
128
+ properties.each do |key, property|
129
+ next if property.nil?
130
+ html << '<li>'
131
+ html << '<tt>%s</tt>' % key
132
+ html << self.class.new(property, options.merge(:nested => true)).to_html
133
+ html << '</li>'
134
+ end
135
+
136
+ html
137
+ end
138
+ end