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
@@ -0,0 +1,47 @@
1
+ require 'forwardable'
2
+
3
+ # :nodoc:
4
+ module Prmd
5
+ # Specialized Hash for handling loaded Schema data
6
+ class SchemaHash
7
+ extend Forwardable
8
+
9
+ # @return [Hash]
10
+ attr_reader :data
11
+ # @return [String]
12
+ attr_reader :filename
13
+
14
+ def_delegator :@data, :[]
15
+ def_delegator :@data, :[]=
16
+ def_delegator :@data, :delete
17
+ def_delegator :@data, :each
18
+
19
+ # @param [Hash] data
20
+ # @param [Hash<Symbol, Object>] options
21
+ def initialize(data, options = {})
22
+ @data = data
23
+ @filename = options.fetch(:filename, '')
24
+ end
25
+
26
+ # @param [Prmd::SchemaHash] other
27
+ # @return [self]
28
+ def initialize_copy(other)
29
+ super
30
+ @data = other.data.dup
31
+ @filename = other.filename.dup
32
+ end
33
+
34
+ # @param [String] key
35
+ # @return [self]
36
+ def fetch(key)
37
+ @data.fetch(key) { abort "Missing key #{key} in #{filename}" }
38
+ end
39
+
40
+ # @return [Hash]
41
+ def to_h
42
+ @data.dup
43
+ end
44
+
45
+ protected :data
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ require 'optparse'
2
+
3
+ # Extension of the standard library OptionParser
4
+ class OptionParser
5
+ alias :to_str :to_s
6
+ end
@@ -0,0 +1,38 @@
1
+ # :nodoc:
2
+ module Prmd
3
+ # Hash helper methods
4
+ #
5
+ # @api private
6
+ module HashHelpers
7
+ # Attempts to convert all keys in the hash to a Symbol.
8
+ # This operation is recursive with subhashes
9
+ #
10
+ # @param [Hash] hash
11
+ # @return [Hash]
12
+ def self.deep_symbolize_keys(hash)
13
+ deep_transform_keys(hash) do |key|
14
+ if key.respond_to?(:to_sym)
15
+ key.to_sym
16
+ else
17
+ key
18
+ end
19
+ end
20
+ end
21
+
22
+ # Think of this as hash.keys.map! { |key| }, that actually maps recursively.
23
+ #
24
+ # @param [Hash] hash
25
+ # @return [Hash]
26
+ # @yield [Object] key
27
+ def self.deep_transform_keys(hash, &block)
28
+ result = {}
29
+ hash.each do |key, value|
30
+ new_key = yield(key)
31
+ new_value = value
32
+ new_value = deep_transform_keys(value, &block) if value.is_a?(Hash)
33
+ result[new_key] = new_value
34
+ end
35
+ result
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ # :nodoc:
5
+ module Prmd
6
+ # Attempts to load either a json or yaml file, the type is determined by
7
+ # filename extension.
8
+ #
9
+ # @param [String] filename
10
+ # @return [Object] data
11
+ def self.load_schema_file(filename)
12
+ extname = File.extname(filename)
13
+ File.open(filename) do |file|
14
+ case extname.downcase
15
+ when '.yaml', '.yml'
16
+ YAML.load(file.read)
17
+ when '.json'
18
+ JSON.load(file.read)
19
+ else
20
+ abort "Cannot load schema file #{filename}" \
21
+ "(unsupported file extension #{extname})"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ # :nodoc:
5
+ module Prmd
6
+ # :nodoc:
7
+ module RakeTasks
8
+ # Common class for Prmd rake tasks
9
+ #
10
+ # @api private
11
+ class Base < Rake::TaskLib
12
+ # The name of the task
13
+ # @return [String] the task name
14
+ attr_accessor :name
15
+
16
+ # Options to pass to command
17
+ # @return [Hash<Symbol, Object>] the options passed to the command
18
+ attr_accessor :options
19
+
20
+ # Creates a new task with name +name+.
21
+ #
22
+ # @param [String, Symbol] name the name of the rake task
23
+ def initialize(name)
24
+ @name = name
25
+ @options = {}
26
+
27
+ yield self if block_given?
28
+
29
+ define
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ require 'prmd/commands/combine'
2
+ require 'prmd/rake_tasks/base'
3
+
4
+ # :nodoc:
5
+ module Prmd
6
+ # :nodoc:
7
+ module RakeTasks
8
+ # Schema combine rake task
9
+ #
10
+ # @example
11
+ # Prmd::RakeTasks::Combine.new do |t|
12
+ # t.options[:meta] = 'schema/meta.json'
13
+ # t.paths << 'schema/schemata/api'
14
+ # t.output_file = 'schema/api.json'
15
+ # end
16
+ class Combine < Base
17
+ #
18
+ # @return [Array<String>] list of paths
19
+ attr_accessor :paths
20
+
21
+ # target file the combined result should be written
22
+ # @return [String>] target filename
23
+ attr_accessor :output_file
24
+
25
+ # Creates a new task with name +name+.
26
+ #
27
+ # @param [String, Symbol] name the name of the rake task
28
+ def initialize(name = :combine)
29
+ @paths = []
30
+ super
31
+ end
32
+
33
+ protected
34
+
35
+ # Defines the rake task
36
+ # @return [void]
37
+ def define
38
+ desc 'Combine schemas' unless Rake.application.last_comment
39
+ task(name) do
40
+ result = Prmd.combine(paths, options)
41
+ if output_file
42
+ File.open(output_file, 'w') do |file|
43
+ file.write(result)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,73 @@
1
+ require 'prmd/commands/render'
2
+ require 'prmd/rake_tasks/base'
3
+ require 'prmd/load_schema_file'
4
+ require 'prmd/template'
5
+ require 'prmd/schema'
6
+
7
+ # :nodoc:
8
+ module Prmd
9
+ # :nodoc:
10
+ module RakeTasks
11
+ # Documentation rake task
12
+ #
13
+ # @example
14
+ # Prmd::RakeTasks::Doc.new do |t|
15
+ # t.files = { 'schema/api.json' => 'schema/api.md' }
16
+ # end
17
+ class Doc < Base
18
+ # Schema files that should be verified
19
+ # @return [Array<String>, Hash<String, String>] list of files
20
+ attr_accessor :files
21
+
22
+ # Creates a new task with name +name+.
23
+ #
24
+ # @param [String, Symbol] name the name of the rake task
25
+ def initialize(name = :doc, &block)
26
+ @files = []
27
+ super name, &block
28
+ @options[:template] ||= Prmd::Template.template_dirname
29
+ end
30
+
31
+ private
32
+
33
+ # Render file to markdown
34
+ #
35
+ # @param [String] filename
36
+ # @return (see Prmd.render)
37
+ def render_file(filename)
38
+ data = Prmd.load_schema_file(filename)
39
+ schema = Prmd::Schema.new(data)
40
+ Prmd.render(schema, options)
41
+ end
42
+
43
+ # @param [String] infile
44
+ # @param [String] outfile
45
+ # @return [void]
46
+ def render_to_file(infile, outfile)
47
+ result = render_file(infile)
48
+ File.open(outfile, 'w') do |file|
49
+ file.write(result)
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ # Defines the rake task
56
+ # @return [void]
57
+ def define
58
+ desc 'Verifying schemas' unless Rake.application.last_comment
59
+ task(name) do
60
+ if files.is_a?(Hash)
61
+ files.each do |infile, outfile|
62
+ render_to_file(infile, outfile)
63
+ end
64
+ else
65
+ files.each do |infile|
66
+ render_to_file(infile, infile.ext('md'))
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,60 @@
1
+ require 'prmd/commands/verify'
2
+ require 'prmd/rake_tasks/base'
3
+ require 'prmd/load_schema_file'
4
+
5
+ # :nodoc:
6
+ module Prmd
7
+ # :nodoc:
8
+ module RakeTasks
9
+ # Schema Verify rake task
10
+ #
11
+ # @example
12
+ # Prmd::RakeTasks::Verify.new do |t|
13
+ # t.files << 'schema/api.json'
14
+ # end
15
+ class Verify < Base
16
+ # Schema files that should be verified
17
+ # @return [Array<String>] list of files
18
+ attr_accessor :files
19
+
20
+ # Creates a new task with name +name+.
21
+ #
22
+ # @param [String, Symbol] name the name of the rake task
23
+ def initialize(name = :verify)
24
+ @files = []
25
+ super
26
+ end
27
+
28
+ private
29
+
30
+ # Defines the rake task
31
+ #
32
+ # @param [String] filename
33
+ # @return [Array<String>] list of errors produced
34
+ def verify_file(filename)
35
+ data = Prmd.load_schema_file(filename)
36
+ errors = Prmd.verify(data)
37
+ unless errors.empty?
38
+ errors.map! { |error| "#{filename}: #{error}" } if filename
39
+ errors.each { |error| $stderr.puts(error) }
40
+ end
41
+ errors
42
+ end
43
+
44
+ protected
45
+
46
+ # Defines the rake task
47
+ # @return [void]
48
+ def define
49
+ desc 'Verifying schemas' unless Rake.application.last_comment
50
+ task(name) do
51
+ all_errors = []
52
+ files.each do |filename|
53
+ all_errors.concat(verify_file(filename))
54
+ end
55
+ fail unless all_errors.empty?
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,35 +1,67 @@
1
+ require 'json'
2
+ require 'yaml'
3
+
4
+ # :nodoc:
1
5
  module Prmd
6
+ # Schema object
2
7
  class Schema
8
+ # @return [Hash] data
9
+ attr_reader :data
10
+
11
+ # @param [Hash<String, Object>] new_data
12
+ def initialize(new_data = {})
13
+ @data = convert_type_to_array(new_data)
14
+ @schemata_examples = {}
15
+ end
3
16
 
17
+ #
18
+ # @param [Object] datum
19
+ # @return [Object] same type as the input object
20
+ def convert_type_to_array(datum)
21
+ case datum
22
+ when Array
23
+ datum.map { |element| convert_type_to_array(element) }
24
+ when Hash
25
+ if datum.key?('type') && datum['type'].is_a?(String)
26
+ datum['type'] = [*datum['type']]
27
+ end
28
+ datum.each_with_object({}) do |(k, v), hash|
29
+ hash[k] = convert_type_to_array(v)
30
+ end
31
+ else
32
+ datum
33
+ end
34
+ end
35
+
36
+ # @param [String] key
37
+ # @return [Object]
4
38
  def [](key)
5
39
  @data[key]
6
40
  end
7
41
 
42
+ # @param [String] key
43
+ # @param [Object] value
8
44
  def []=(key, value)
9
45
  @data[key] = value
10
46
  end
11
47
 
12
- def initialize(new_data = {})
13
- convert_type_to_array = lambda do |datum|
14
- case datum
15
- when Array
16
- datum.map { |element| convert_type_to_array.call(element) }
17
- when Hash
18
- if datum.has_key?('type') && datum['type'].is_a?(String)
19
- datum['type'] = [*datum['type']]
20
- end
21
- datum.each { |k,v| datum[k] = convert_type_to_array.call(v) }
22
- else
23
- datum
24
- end
48
+ # Merge schema data with provided schema
49
+ #
50
+ # @param [Hash, Prmd::Schema] schema
51
+ # @return [void]
52
+ def merge!(schema)
53
+ if schema.is_a?(Schema)
54
+ @data.merge!(schema.data)
55
+ else
56
+ @data.merge!(schema)
25
57
  end
26
- @data = convert_type_to_array.call(new_data)
27
- @schemata_examples = {}
28
58
  end
29
59
 
60
+ #
61
+ # @param [Hash, String] reference
30
62
  def dereference(reference)
31
63
  if reference.is_a?(Hash)
32
- if reference.has_key?('$ref')
64
+ if reference.key?('$ref')
33
65
  value = reference.dup
34
66
  key = value.delete('$ref')
35
67
  else
@@ -40,7 +72,7 @@ module Prmd
40
72
  end
41
73
  begin
42
74
  datum = @data
43
- key.gsub(%r{[^#]*#/}, '').split('/').each do |fragment|
75
+ key.gsub(/[^#]*#\//, '').split('/').each do |fragment|
44
76
  datum = datum[fragment]
45
77
  end
46
78
  # last dereference will have nil key, so compact it out
@@ -48,25 +80,29 @@ module Prmd
48
80
  dereferenced_key, dereferenced_value = dereference(datum)
49
81
  [
50
82
  [key, dereferenced_key].compact.last,
51
- [dereferenced_value, value].inject({}) { |composite, element| composite.merge(element) }
83
+ [dereferenced_value, value].inject({}, &:merge)
52
84
  ]
53
85
  rescue => error
54
86
  $stderr.puts("Failed to dereference `#{key}`")
55
- raise(error)
87
+ raise error
56
88
  end
57
89
  end
58
90
 
91
+ # @param [Hash] value
59
92
  def schema_value_example(value)
60
- if value.has_key?('example')
93
+ if value.key?('example')
61
94
  value['example']
62
- elsif value.has_key?('anyOf')
63
- ref = value['anyOf'].detect {|ref| ref['$ref'].split('/').last == 'id'} || value['anyOf'].first
95
+ elsif value.key?('anyOf')
96
+ id_ref = value['anyOf'].find do |ref|
97
+ ref['$ref'] && ref['$ref'].split('/').last == 'id'
98
+ end
99
+ ref = id_ref || value['anyOf'].first
64
100
  schema_example(ref)
65
- elsif value.has_key?('properties') # nested properties
101
+ elsif value.key?('properties') # nested properties
66
102
  schema_example(value)
67
- elsif value.has_key?('items') # array of objects
103
+ elsif value.key?('items') # array of objects
68
104
  _, items = dereference(value['items'])
69
- if value['items'].has_key?('example')
105
+ if value['items'].key?('example')
70
106
  [items['example']]
71
107
  else
72
108
  [schema_example(items)]
@@ -74,23 +110,25 @@ module Prmd
74
110
  end
75
111
  end
76
112
 
113
+ # @param [Hash, String] schema
77
114
  def schema_example(schema)
78
- _, _schema = dereference(schema)
115
+ _, dff_schema = dereference(schema)
79
116
 
80
- if _schema.has_key?('example')
81
- _schema['example']
82
- elsif _schema.has_key?('properties')
117
+ if dff_schema.key?('example')
118
+ dff_schema['example']
119
+ elsif dff_schema.key?('properties')
83
120
  example = {}
84
- _schema['properties'].each do |key, value|
121
+ dff_schema['properties'].each do |key, value|
85
122
  _, value = dereference(value)
86
123
  example[key] = schema_value_example(value)
87
124
  end
88
125
  example
89
- elsif _schema.has_key?('items')
90
- schema_value_example(_schema)
126
+ elsif dff_schema.key?('items')
127
+ schema_value_example(dff_schema)
91
128
  end
92
129
  end
93
130
 
131
+ # @param [String] schemata_id
94
132
  def schemata_example(schemata_id)
95
133
  _, schema = dereference("#/definitions/#{schemata_id}")
96
134
  @schemata_examples[schemata_id] ||= begin
@@ -98,24 +136,38 @@ module Prmd
98
136
  end
99
137
  end
100
138
 
139
+ # Retrieve this schema's href
140
+ #
141
+ # @return [String, nil]
101
142
  def href
102
- (@data['links'].detect { |link| link['rel'] == 'self' } || {})['href']
143
+ (@data['links'] && @data['links'].find { |link| link['rel'] == 'self' } || {})['href']
103
144
  end
104
145
 
146
+ # Convert Schema to JSON
147
+ #
148
+ # @return [String]
105
149
  def to_json
106
150
  new_json = JSON.pretty_generate(@data)
107
151
  # nuke empty lines
108
- new_json = new_json.split("\n").delete_if {|line| line.empty?}.join("\n") + "\n"
152
+ new_json = new_json.split("\n").reject(&:empty?).join("\n") + "\n"
109
153
  new_json
110
154
  end
111
155
 
156
+ # Convert Schema to YAML
157
+ #
158
+ # @return [String]
112
159
  def to_yaml
113
160
  YAML.dump(@data)
114
161
  end
115
162
 
163
+ # Convert Schema to String
164
+ #
165
+ # @return [String]
116
166
  def to_s
117
167
  to_json
118
168
  end
119
169
 
170
+ private :convert_type_to_array
171
+ protected :data
120
172
  end
121
173
  end