prmd 0.6.2 → 0.7.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.
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