prmd 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -13
  2. data/CONTRIBUTORS.md +8 -0
  3. data/Gemfile.lock +6 -6
  4. data/README.md +57 -8
  5. data/Rakefile +4 -4
  6. data/bin/prmd +3 -117
  7. data/docs/schemata.md +11 -11
  8. data/lib/prmd.rb +7 -18
  9. data/lib/prmd/cli.rb +108 -0
  10. data/lib/prmd/cli/base.rb +151 -0
  11. data/lib/prmd/cli/combine.rb +42 -0
  12. data/lib/prmd/cli/doc.rb +69 -0
  13. data/lib/prmd/cli/generate.rb +44 -0
  14. data/lib/prmd/cli/render.rb +48 -0
  15. data/lib/prmd/cli/verify.rb +49 -0
  16. data/lib/prmd/commands.rb +4 -0
  17. data/lib/prmd/commands/combine.rb +85 -58
  18. data/lib/prmd/commands/init.rb +30 -98
  19. data/lib/prmd/commands/render.rb +30 -17
  20. data/lib/prmd/commands/verify.rb +78 -35
  21. data/lib/prmd/core/combiner.rb +91 -0
  22. data/lib/prmd/core/generator.rb +27 -0
  23. data/lib/prmd/core/renderer.rb +56 -0
  24. data/lib/prmd/core/schema_hash.rb +47 -0
  25. data/lib/prmd/core_ext/optparse.rb +6 -0
  26. data/lib/prmd/hash_helpers.rb +38 -0
  27. data/lib/prmd/load_schema_file.rb +25 -0
  28. data/lib/prmd/rake_tasks/base.rb +33 -0
  29. data/lib/prmd/rake_tasks/combine.rb +50 -0
  30. data/lib/prmd/rake_tasks/doc.rb +73 -0
  31. data/lib/prmd/rake_tasks/verify.rb +60 -0
  32. data/lib/prmd/schema.rb +86 -34
  33. data/lib/prmd/template.rb +65 -8
  34. data/lib/prmd/templates/combine_head.json +6 -0
  35. data/lib/prmd/templates/init_default.json +9 -0
  36. data/lib/prmd/templates/init_resource.json.erb +90 -0
  37. data/lib/prmd/templates/link_schema_properties.md.erb +5 -0
  38. data/lib/prmd/templates/schema.erb +2 -2
  39. data/lib/prmd/templates/schemata.md.erb +37 -0
  40. data/lib/prmd/templates/schemata/helper.erb +29 -15
  41. data/lib/prmd/templates/schemata/link.md.erb +74 -0
  42. data/lib/prmd/templates/schemata/{link_curl_example.erb → link_curl_example.md.erb} +8 -2
  43. data/lib/prmd/url_generator.rb +11 -69
  44. data/lib/prmd/url_generators/generators/default.rb +66 -0
  45. data/lib/prmd/url_generators/generators/json.rb +30 -0
  46. data/lib/prmd/version.rb +10 -1
  47. data/prmd.gemspec +15 -15
  48. data/test/cli/combine_test.rb +23 -0
  49. data/test/cli/doc_test.rb +25 -0
  50. data/test/cli/generate_test.rb +23 -0
  51. data/test/cli/render_test.rb +25 -0
  52. data/test/cli/verify_test.rb +21 -0
  53. data/test/commands/init_test.rb +7 -0
  54. data/test/commands/render_test.rb +93 -0
  55. data/test/commands/verify_test.rb +60 -60
  56. data/test/helpers.rb +61 -6
  57. data/test/schema_test.rb +17 -11
  58. data/test/schemata/input/doc-settings.json +4 -0
  59. metadata +73 -28
  60. data/lib/prmd/commands/expand.rb +0 -108
  61. data/lib/prmd/templates/link_schema_properties.erb +0 -16
  62. data/lib/prmd/templates/schemata.erb +0 -47
  63. data/lib/prmd/templates/schemata/link.erb +0 -61
@@ -1,19 +1,76 @@
1
+ require 'erubis'
2
+ require 'json'
3
+
4
+ # :nodoc:
1
5
  module Prmd
6
+ # Template management
7
+ #
8
+ # @api private
2
9
  class Template
10
+ @cache = {}
11
+
12
+ # @return [String] location of the prmd templates directory
13
+ def self.template_dirname
14
+ File.join(File.dirname(__FILE__), 'templates')
15
+ end
16
+
17
+ # @param [String] args
18
+ # @return [String] path in prmd's template directory
19
+ def self.template_path(*args)
20
+ File.expand_path(File.join(*args), template_dirname)
21
+ end
22
+
23
+ # Clear internal template cache
24
+ #
25
+ # @return [void]
26
+ def self.clear_cache
27
+ @cache.clear
28
+ end
29
+
30
+ # Attempts to load a template from the given path and base, if the template
31
+ # was previously loaded, the cached template is returned instead
32
+ #
33
+ # @param [String] path
34
+ # @param [String] base
35
+ # @return [Erubis::Eruby] eruby template
3
36
  def self.load(path, base)
4
- fallback = File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
37
+ @cache[[path, base]] ||= begin
38
+ fallback = template_path
39
+
40
+ resolved = File.join(base, path)
41
+ unless File.exist?(resolved)
42
+ resolved = File.join(fallback, path)
43
+ end
5
44
 
6
- resolved = File.join(base, path)
7
- if not File.exist?(resolved)
8
- resolved = File.join(fallback, path)
45
+ Erubis::Eruby.new(File.read(resolved), filename: resolved)
9
46
  end
47
+ end
48
+
49
+ #
50
+ # @param [String] path
51
+ # @param [String] base
52
+ # @return (see .load)
53
+ def self.load_template(path, base)
54
+ load(path, base)
55
+ end
10
56
 
11
- return File.read(resolved)
57
+ # Render a template given args or block.
58
+ # args and block are passed to the template
59
+ #
60
+ # @param [String] path
61
+ # @param [String] base
62
+ # @return [String] result from template render
63
+ def self.render(path, base, *args, &block)
64
+ load_template(path, base).result(*args, &block)
12
65
  end
13
66
 
14
- def self.render(path, base, *args)
15
- template = self.load(path, base)
16
- Erubis::Eruby.new(template).result(*args)
67
+ # Load a JSON file from prmd's templates directory.
68
+ # These files are not cached and are intended to be loaded on demand.
69
+ #
70
+ # @param [String] filename
71
+ # @return [Object] data
72
+ def self.load_json(filename)
73
+ JSON.parse(File.read(template_path(filename)))
17
74
  end
18
75
  end
19
76
  end
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/hyper-schema",
3
+ "definitions": {},
4
+ "properties": {},
5
+ "type": ["object"]
6
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/hyper-schema",
3
+ "title": "FIXME",
4
+ "definitions": {},
5
+ "description": "FIXME",
6
+ "links": [],
7
+ "properties": {},
8
+ "type": ["object"]
9
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "id": "schemata/<%= resource %>",
3
+ "title": "FIXME - <%= resource.capitalize %>",
4
+ "definitions": {
5
+ "id": {
6
+ "description": "unique identifier of <%= resource %>",
7
+ "example": "01234567-89ab-cdef-0123-456789abcdef",
8
+ "format": "uuid",
9
+ "type": ["string"]
10
+ },
11
+ "identity": {
12
+ "$ref": "/schemata/<%= resource %>#/definitions/id"
13
+ },
14
+ "created_at": {
15
+ "description": "when <%= resource %> was created",
16
+ "example": "2012-01-01T12:00:00Z",
17
+ "format": "date-time",
18
+ "type": ["string"]
19
+ },
20
+ "updated_at": {
21
+ "description": "when <%= resource %> was updated",
22
+ "example": "2012-01-01T12:00:00Z",
23
+ "format": "date-time",
24
+ "type": ["string"]
25
+ }
26
+ },
27
+ "properties": {
28
+ "created_at": {
29
+ "$ref": "/schemata/<%= resource %>#/definitions/created_at"
30
+ },
31
+ "id": {
32
+ "$ref": "/schemata/<%= resource %>#/definitions/id"
33
+ },
34
+ "updated_at": {
35
+ "$ref": "/schemata/<%= resource %>#/definitions/updated_at"
36
+ }
37
+ },
38
+ "links": [
39
+ {
40
+ "description": "Create a new <%= resource %>.",
41
+ "href": "/<%= resource %>s",
42
+ "method": "POST",
43
+ "rel": "create",
44
+ "schema": {
45
+ "properties": {},
46
+ "type": ["object"]
47
+ },
48
+ "title": "Create"
49
+ },
50
+ {
51
+ "description": "Delete an existing <%= resource %>.",
52
+ "href": "/<%= resource %>s/{(%2Fschemata%2F<%= resource %>%23%2Fdefinitions%2Fidentity)}",
53
+ "method": "DELETE",
54
+ "rel": "destroy",
55
+ "title": "Delete"
56
+ },
57
+ {
58
+ "description": "Info for existing <%= resource %>.",
59
+ "href": "/<%= resource %>s/{(%2Fschemata%2F<%= resource %>%23%2Fdefinitions%2Fidentity)}",
60
+ "method": "GET",
61
+ "rel": "self",
62
+ "title": "Info"
63
+ },
64
+ {
65
+ "description": "List existing <%= resource %>s.",
66
+ "href": "/<%= resource %>s",
67
+ "method": "GET",
68
+ "rel": "instances",
69
+ "title": "List"
70
+ },
71
+ {
72
+ "description": "Update an existing <%= resource %>.",
73
+ "href": "/<%= resource %>s/{(%2Fschemata%2F<%= resource %>%23%2Fdefinitions%2Fidentity)}",
74
+ "method": "PATCH",
75
+ "rel": "update",
76
+ "schema": {
77
+ "properties": {},
78
+ "type": ["object"]
79
+ },
80
+ "title": "Update"
81
+ }<% if parent %>,
82
+ {
83
+ "description": "List existing <%= resource %>s for existing <%= parent %>.",
84
+ "href": "/<%= parent %>s/{(%2Fschemata%2F<%= parent %>%23%2Fdefinitions%2Fidentity)}/<%= resource %>s",
85
+ "method": "GET",
86
+ "rel": "instances",
87
+ "title": "List"
88
+ }<% end %>
89
+ ]
90
+ }
@@ -0,0 +1,5 @@
1
+ | Name | Type | Description | Example |
2
+ | ------- | ------- | ------- | ------- |
3
+ <%- extract_attributes(schema, params).each do |(key, type, description, example)| %>
4
+ | **<%= key %>** | *<%= type %>* | <%= description %> | <%= example %> |
5
+ <%- end %>
@@ -1,10 +1,10 @@
1
1
  <%=
2
- schemata_template = Prmd::Template::load('schemata.erb', options[:template])
2
+ schemata_template = Prmd::Template::load('schemata.md.erb', options[:template])
3
3
 
4
4
  schema['properties'].map do |resource, property|
5
5
  begin
6
6
  _, schemata = schema.dereference(property)
7
- Erubis::Eruby.new(schemata_template).result({
7
+ schemata_template.result({
8
8
  options: options,
9
9
  resource: resource,
10
10
  schema: schema,
@@ -0,0 +1,37 @@
1
+ <%-
2
+ return unless schemata.has_key?('links') && !schemata['links'].empty?
3
+
4
+ Prmd::Template.render('schemata/helper.erb', options[:template], {
5
+ options: options,
6
+ resource: resource,
7
+ schema: schema,
8
+ schemata: schemata
9
+ })
10
+
11
+ title = schemata['title'].split(' - ', 2).last
12
+ -%>
13
+ <%- unless options[:doc][:disable_title_and_description] %>
14
+ ## <%= title %>
15
+ <%= schemata['description'] %>
16
+ <%- end -%>
17
+
18
+ <%- if schemata['properties'] && !schemata['properties'].empty? %>
19
+ ### Attributes
20
+ | Name | Type | Description | Example |
21
+ | ------- | ------- | ------- | ------- |
22
+ <%- extract_attributes(schema, schemata['properties']).each do |(key, type, description, example)| %>
23
+ | **<%= key %>** | *<%= type %>* | <%= description %> | <%= example %> |
24
+ <%- end %>
25
+ <%- end %>
26
+ <%- schemata['links'].each do |link, datum| %>
27
+ <%=
28
+ Prmd::Template.render('schemata/link.md.erb', options[:template], {
29
+ options: options,
30
+ resource: resource,
31
+ schema: schema,
32
+ schemata: schemata,
33
+ link: link,
34
+ title: title
35
+ })
36
+ %>
37
+ <%- end -%>
@@ -1,7 +1,6 @@
1
1
  <%-
2
2
  def extract_attributes(schema, properties)
3
3
  attributes = []
4
- properties = properties.sort_by {|k,v| k} # ensure consistent ordering
5
4
 
6
5
  properties.each do |key, value|
7
6
  # found a reference to another element:
@@ -16,15 +15,12 @@
16
15
  descriptions = []
17
16
  examples = []
18
17
 
19
- # sort anyOf! always show unique identifier first
20
- anyof = value['anyOf'].sort_by do |property|
21
- property['$ref'].split('/').last.gsub('id', 'a')
22
- end
18
+ anyof = value['anyOf']
23
19
 
24
20
  anyof.each do |ref|
25
21
  _, nested_field = schema.dereference(ref)
26
- descriptions << nested_field['description']
27
- examples << nested_field['example']
22
+ descriptions << nested_field['description'] if nested_field['description']
23
+ examples << nested_field['example'] if nested_field['example']
28
24
  end
29
25
 
30
26
  # avoid repetition :}
@@ -36,7 +32,7 @@
36
32
  description = descriptions.last
37
33
  end
38
34
 
39
- example = [*examples].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
35
+ example = [*examples].map { |e| "`#{e.to_json}`" }.join(" or ")
40
36
  attributes << [key, "string", description, example]
41
37
 
42
38
  # found a nested object
@@ -62,25 +58,43 @@
62
58
  end
63
59
 
64
60
  def build_attribute(schema, key, value)
65
- description = value['description']
61
+ description = value['description'] || ""
66
62
  if value['default']
67
- description += "<br/><b>default:</b> <code>#{value['default'].to_json}</code>"
63
+ description += "<br/> **default:** `#{value['default'].to_json}`"
64
+ end
65
+
66
+ if value['minimum'] || value['maximum']
67
+ description += "<br/> **Range:** `"
68
+ if value['minimum']
69
+ comparator = value['exclusiveMinimum'] ? "<" : "<="
70
+ description += "#{value['minimum'].to_json} #{comparator} "
71
+ end
72
+ description += "value"
73
+ if value['maximum']
74
+ comparator = value['exclusiveMaximum'] ? "<" : "<="
75
+ description += " #{comparator} #{value['maximum'].to_json}"
76
+ end
77
+ description += "`"
68
78
  end
69
79
 
70
80
  if value['enum']
71
- description += '<br/><b>one of:</b>' + [*value['enum']].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
81
+ description += '<br/> **one of:**' + [*value['enum']].map { |e| "`#{e.to_json}`" }.join(" or ")
82
+ end
83
+
84
+ if value['pattern']
85
+ description += "<br/> **pattern:** <code>#{value['pattern'].gsub /\|/, '&#124;'}</code>"
72
86
  end
73
87
 
74
88
  if value.has_key?('example')
75
89
  example = if value['example'].is_a?(Hash) && value['example'].has_key?('oneOf')
76
- value['example']['oneOf'].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
90
+ value['example']['oneOf'].map { |e| "`#{e.to_json}`" }.join(" or ")
77
91
  else
78
- "<code>#{value['example'].to_json}</code>"
92
+ "`#{value['example'].to_json}`"
79
93
  end
80
94
  elsif value['type'] == ['array'] && value.has_key?('items')
81
- example = "<code>#{schema.schema_value_example(value)}</code>"
95
+ example = "`#{schema.schema_value_example(value)}`"
82
96
  elsif value['type'].include?('null')
83
- example = "<code>null</code>"
97
+ example = "`null`"
84
98
  end
85
99
 
86
100
  type = if value['type'].include?('null')
@@ -0,0 +1,74 @@
1
+ <%-
2
+ path = build_link_path(schema, link)
3
+ response_example = link['response_example']
4
+ link_schema_properties_template = Prmd::Template.load_template('link_schema_properties.md.erb', options[:template])
5
+ -%>
6
+ ### <%= title %> <%= link['title'] %>
7
+ <%= link['description'] %>
8
+
9
+ ```
10
+ <%= link['method'] %> <%= path %>
11
+ ```
12
+
13
+ <%- if link.has_key?('schema') && link['schema'].has_key?('properties') %>
14
+ <%-
15
+ required, optional = link['schema']['properties'].partition do |k, v|
16
+ (link['schema']['required'] || []).include?(k)
17
+ end.map { |partition| Hash[partition] }
18
+ %>
19
+ <%- unless required.empty? %>
20
+ #### Required Parameters
21
+ <%= link_schema_properties_template.result(params: required, schema: schema, options: options) %>
22
+
23
+ <%- end %>
24
+ <%- unless optional.empty? %>
25
+ #### Optional Parameters
26
+ <%= link_schema_properties_template.result(params: optional, schema: schema, options: options) %>
27
+ <%- end %>
28
+ <%- end %>
29
+
30
+ #### Curl Example
31
+ <%=
32
+ curl_options = options.dup
33
+ http_header = link['http_header'] || {}
34
+ curl_options[:http_header] = curl_options[:http_header].merge(http_header)
35
+ Prmd::Template.render('schemata/link_curl_example.md.erb', File.dirname(options[:template]), {
36
+ options: curl_options,
37
+ resource: resource,
38
+ schema: schema,
39
+ schemata: schemata,
40
+ link: link,
41
+ path: path
42
+ })
43
+ %>
44
+
45
+ #### Response Example
46
+ ```
47
+ <%- if response_example %>
48
+ <%= response_example['head'] %>
49
+ <%- else %>
50
+ HTTP/1.1 <%=
51
+ case link['rel']
52
+ when 'create'
53
+ '201 Created'
54
+ when 'empty'
55
+ '202 Accepted'
56
+ else
57
+ '200 OK'
58
+ end %>
59
+ <%- end %>
60
+ ```
61
+ ```json
62
+ <%- if response_example %>
63
+ <%= response_example['body'] %>
64
+ <%- else %>
65
+ <%- if link['rel'] == 'empty' %>
66
+ <%- elsif link.has_key?('targetSchema') %>
67
+ <%= JSON.pretty_generate(schema.schema_example(link['targetSchema'])) %>
68
+ <%- elsif link['rel'] == 'instances' %>
69
+ <%= JSON.pretty_generate([schema.schemata_example(resource)]) %>
70
+ <%- else %>
71
+ <%= JSON.pretty_generate(schema.schemata_example(resource)) %>
72
+ <%- end %>
73
+ <%- end %>
74
+ ```
@@ -11,10 +11,16 @@
11
11
  get_params << Prmd::UrlGenerator.new({schema: schema, link: link, options: options}).url_params
12
12
  end
13
13
  end
14
+ if link['method'].upcase != 'GET'
15
+ options = options.dup
16
+ options[:http_header] = { 'Content-Type' => options[:content_type] }.merge(options[:http_header])
17
+ end
14
18
  %>
15
- $ curl -n -X <%= link['method'] %> <%= schema.href %><%= path -%>
19
+ $ curl -n -X <%= link['method'] %> <%= schema.href %><%= path -%><%- unless options[:http_header].empty? %> \<%- end %>
20
+ <%- options[:http_header].each do |key, value| %>
21
+ -H "<%= key %>: <%= value %>" \
22
+ <%- end %>
16
23
  <%- if !data.empty? && link['method'].upcase != 'GET' %> \
17
- -H "Content-Type: <%= options[:content_type] %>" \
18
24
  -d '<%= JSON.pretty_generate(data) %>'
19
25
  <%- elsif !get_params.empty? && link['method'].upcase == 'GET' %> -G \
20
26
  -d <%= get_params.join(" \\\n -d ") %>
@@ -1,85 +1,27 @@
1
+ require 'prmd/url_generators/generators/default'
2
+ require 'prmd/url_generators/generators/json'
3
+
4
+ # :nodoc:
1
5
  module Prmd
6
+ # Schema URL Generation
7
+ # @api private
2
8
  class UrlGenerator
3
-
9
+ # @param [Hash<Symbol, Object>] params
4
10
  def initialize(params)
5
11
  @schema = params[:schema]
6
12
  @link = params[:link]
7
- @options = params[:options]
13
+ @options = params.fetch(:options)
8
14
  end
9
15
 
16
+ # @return [Array]
10
17
  def url_params
11
- if @options[:style].downcase == 'json'
18
+ if @options[:doc][:url_style].downcase == 'json'
12
19
  klass = Generators::JSON
13
20
  else
14
21
  klass = Generators::Default
15
22
  end
16
23
 
17
- klass.generate({schema: @schema, link: @link})
18
- end
19
-
20
- private
21
-
22
- module Generators
23
- class Default
24
- class << self
25
- def generate(params)
26
- data = {}
27
- data.merge!(params[:schema].schema_example(params[:link]['schema']))
28
- generate_params(data)
29
- end
30
-
31
- private
32
-
33
- def param_name(key, prefix, array = false)
34
- result = if prefix
35
- "#{prefix}[#{key}]"
36
- else
37
- key
38
- end
39
-
40
- result += "[]" if array
41
- result
42
- end
43
-
44
- def generate_params(obj, prefix = nil)
45
- result = []
46
- obj.each do |key,value|
47
- if value.is_a?(Hash)
48
- newprefix = if prefix
49
- "#{prefix}[#{key}]"
50
- else
51
- key
52
- end
53
- result << generate_params(value, newprefix)
54
- elsif value.is_a?(Array)
55
- value.each do |val|
56
- result << [param_name(key, prefix, true), CGI.escape(val.to_s)].join('=')
57
- end
58
- else
59
- next unless value # ignores parameters with empty examples
60
- result << [param_name(key, prefix), CGI.escape(value.to_s)].join('=')
61
- end
62
- end
63
- result.flatten
64
- end
65
- end
66
- end
67
-
68
- class JSON
69
- def self.generate(params)
70
- data = {}
71
- data.merge!(params[:schema].schema_example(params[:link]['schema']))
72
-
73
- result = []
74
- data.sort_by {|k,_| k.to_s }.each do |key, values|
75
- [values].flatten.each do |value|
76
- result << [key.to_s, CGI.escape(value.to_s)].join('=')
77
- end
78
- end
79
-
80
- result
81
- end
82
- end
24
+ klass.generate(schema: @schema, link: @link)
83
25
  end
84
26
  end
85
27
  end