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,151 @@
1
+ require 'json'
2
+ require 'prmd/core_ext/optparse'
3
+ require 'prmd/load_schema_file'
4
+
5
+ module Prmd
6
+ module CLI
7
+ # Base module for CLI commands.
8
+ # @api
9
+ module Base
10
+ # Create a parser specific for this command.
11
+ # The parsers produced by this method should yield their options.
12
+ #
13
+ # @example Overwriting
14
+ # def make_parser(options = {})
15
+ # OptionParser.new do |opts|
16
+ # opts.on("-v", "--verbose", "set verbose debugging") do |v|
17
+ # yield :verbose, v
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # @param [Hash<Symbol, Object>] options
23
+ # @return [OptionParser] newly created parser
24
+ # @abstract
25
+ def make_parser(options = {})
26
+ #
27
+ end
28
+
29
+ # Runs the provided parser with the provided argv.
30
+ # This method can be overwritten to use a different parser method.
31
+ #
32
+ # @param [OptionParser] parser
33
+ # @param [Array<String>] argv
34
+ # @return [Array<String>] remaining arguments
35
+ # @private
36
+ def execute_parser(parser, argv)
37
+ parser.parse(argv)
38
+ end
39
+
40
+ # Set the given key and value in the given options Hash.
41
+ # This method can ben overwritten to support special keys or values
42
+ #
43
+ # @example Handling special keys
44
+ # def self.set_option(options, key, value)
45
+ # if key == :settings
46
+ # options.replace(value.merge(options))
47
+ # else
48
+ # super
49
+ # end
50
+ # end
51
+ #
52
+ # @param [Hash<Symbol, Object>] options
53
+ # @param [Symbol] key
54
+ # @param [Object] value
55
+ # @return [void]
56
+ def set_option(options, key, value)
57
+ options[key] = value
58
+ end
59
+
60
+ # Parse the given argv and produce a options Hash specific to the command.
61
+ # The returned options Hash will include an :argv key which contains
62
+ # the remaining args from the parse operation.
63
+ #
64
+ # @param [Array<String>] argv
65
+ # @param [Hash<Symbol, Object>] options
66
+ # @return [Hash<Symbol, Object>] parsed options
67
+ def parse_options(argv, options = {})
68
+ opts = {}
69
+ parser = make_parser(options) do |key, value|
70
+ set_option(opts, key, value)
71
+ end
72
+ argv = execute_parser(parser, argv)
73
+ opts[:argv] = argv
74
+ opts
75
+ end
76
+
77
+ # Helper method for writing command results to a file or STD* IO.
78
+ #
79
+ # @param [String] data to be written
80
+ # @param [Hash<Symbol, Object>] options
81
+ # @return [void]
82
+ def write_result(data, options = {})
83
+ output_file = options[:output_file]
84
+ if output_file
85
+ File.open(output_file, 'w') do |f|
86
+ f.write(data)
87
+ end
88
+ else
89
+ $stdout.puts data
90
+ end
91
+ end
92
+
93
+ # Helper method for reading schema data from a file or STD* IO.
94
+ #
95
+ # @param [String] filename file to read
96
+ # @return [Array[Symbol, String]] source, data
97
+ def try_read(filename = nil)
98
+ if filename && !filename.empty?
99
+ return :file, Prmd.load_schema_file(filename)
100
+ elsif !$stdin.tty?
101
+ return :io, JSON.load($stdin.read)
102
+ else
103
+ abort 'Nothing to read'
104
+ end
105
+ end
106
+
107
+ # Method called to actually execute the command provided with an
108
+ # options Hash from the commands #parse_options.
109
+ #
110
+ # @param [Hash<Symbol, Object>] options
111
+ # @return [void]
112
+ # @abstract
113
+ def execute(options = {})
114
+ #
115
+ end
116
+
117
+ # Method called when the command is ran with the :noop option enabled.
118
+ # As the option implies, this should do absolutely nothing.
119
+ #
120
+ # @param [Hash<Symbol, Object>] options
121
+ # @return [void]
122
+ def noop_execute(options = {})
123
+ $stderr.puts options
124
+ end
125
+
126
+ # Run this command given a argv and optional options Hash.
127
+ # If all you have is the options from the #parse_options method, use
128
+ # #execute instead.
129
+ #
130
+ # @see #execute
131
+ # @see #parse_options
132
+ #
133
+ # @param [Array<String>] argv
134
+ # @param [Hash<Symbol, Object>] options
135
+ # @return [void]
136
+ def run(argv, options = {})
137
+ options = options.merge(parse_options(argv, options))
138
+ if options[:noop]
139
+ noop_execute(options)
140
+ else
141
+ execute(options)
142
+ end
143
+ end
144
+
145
+ private :execute_parser
146
+ private :set_option
147
+ private :write_result
148
+ private :try_read
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,42 @@
1
+ require 'prmd/cli/base'
2
+ require 'prmd/commands/combine'
3
+
4
+ module Prmd
5
+ module CLI
6
+ # 'combine' command module.
7
+ module Combine
8
+ extend CLI::Base
9
+
10
+ # Returns a OptionParser for parsing 'combine' command options.
11
+ #
12
+ # @param (see Prmd::CLI::Base#make_parser)
13
+ # @return (see Prmd::CLI::Base#make_parser)
14
+ def self.make_parser(options = {})
15
+ binname = options.fetch(:bin, 'prmd')
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = "#{binname} combine [options] <file or directory>"
19
+ opts.on('-m', '--meta FILENAME', String, 'Set defaults for schemata') do |m|
20
+ yield :meta, m
21
+ end
22
+ opts.on('-o', '--output-file FILENAME', String, 'File to write result to') do |n|
23
+ yield :output_file, n
24
+ end
25
+ end
26
+ end
27
+
28
+ # Executes the 'combine' command.
29
+ #
30
+ # @example Usage
31
+ # Prmd::CLI::Combine.execute(argv: ['schema/schemata/api'],
32
+ # meta: 'schema/meta.json',
33
+ # output_file: 'schema/api.json')
34
+ #
35
+ # @param (see Prmd::CLI::Base#execute)
36
+ # @return (see Prmd::CLI::Base#execute)
37
+ def self.execute(options = {})
38
+ write_result Prmd.combine(options[:argv], options).to_s, options
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,69 @@
1
+ require 'prmd/cli/base'
2
+ require 'prmd/commands/render'
3
+ require 'prmd/hash_helpers'
4
+
5
+ module Prmd
6
+ module CLI
7
+ # 'doc' command module.
8
+ module Doc
9
+ extend CLI::Base
10
+
11
+ # Returns a OptionParser for parsing 'doc' command options.
12
+ #
13
+ # @param (see Prmd::CLI::Base#make_parser)
14
+ # @return (see Prmd::CLI::Base#make_parser)
15
+ def self.make_parser(options = {})
16
+ binname = options.fetch(:bin, 'prmd')
17
+
18
+ OptionParser.new do |opts|
19
+ opts.banner = "#{binname} doc [options] <combined schema>"
20
+ opts.on('-s', '--settings FILENAME', String, 'Config file to use') do |s|
21
+ settings = Prmd.load_schema_file(s) || {}
22
+ options = HashHelpers.deep_symbolize_keys(settings)
23
+ yield :settings, options
24
+ end
25
+ opts.on('-p', '--prepend header,overview', Array, 'Prepend files to output') do |p|
26
+ yield :prepend, p
27
+ end
28
+ opts.on('-c', '--content-type application/json', String, 'Content-Type header') do |c|
29
+ yield :content_type, c
30
+ end
31
+ opts.on('-o', '--output-file FILENAME', String, 'File to write result to') do |n|
32
+ yield :output_file, n
33
+ end
34
+ end
35
+ end
36
+
37
+ # Overwritten to support :settings merging.
38
+ #
39
+ # @see Prmd::CLI::Base#set_option
40
+ #
41
+ # @param (see Prmd::CLI::Base#set_option)
42
+ # @return (see Prmd::CLI::Base#set_option)
43
+ def self.set_option(options, key, value)
44
+ if key == :settings
45
+ options.replace(value.merge(options))
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ # Executes the 'doc' command.
52
+ #
53
+ # @example Usage
54
+ # Prmd::CLI::Doc.execute(argv: ['schema/api.json'],
55
+ # output_file: 'schema/api.md')
56
+ #
57
+ # @param (see Prmd::CLI::Base#execute)
58
+ # @return (see Prmd::CLI::Base#execute)
59
+ def self.execute(options = {})
60
+ filename = options.fetch(:argv).first
61
+ template = File.expand_path('templates', File.dirname(__FILE__))
62
+ _, data = try_read(filename)
63
+ schema = Prmd::Schema.new(data)
64
+ opts = options.merge(template: template)
65
+ write_result Prmd.render(schema, opts), options
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,44 @@
1
+ require 'prmd/cli/base'
2
+ require 'prmd/commands/init'
3
+
4
+ module Prmd
5
+ module CLI
6
+ # 'init' command module.
7
+ # Though this is called, Generate, it is used by the init method for
8
+ # creating new Schema files
9
+ module Generate
10
+ extend CLI::Base
11
+
12
+ # Returns a OptionParser for parsing 'init' command options.
13
+ #
14
+ # @param (see Prmd::CLI::Base#make_parser)
15
+ # @return (see Prmd::CLI::Base#make_parser)
16
+ def self.make_parser(options = {})
17
+ binname = options.fetch(:bin, 'prmd')
18
+
19
+ OptionParser.new do |opts|
20
+ opts.banner = "#{binname} init [options] <resource name>"
21
+ opts.on('-y', '--yaml', 'Generate YAML') do |y|
22
+ yield :yaml, y
23
+ end
24
+ opts.on('-o', '--output-file FILENAME', String, 'File to write result to') do |n|
25
+ yield :output_file, n
26
+ end
27
+ end
28
+ end
29
+
30
+ # Executes the 'init' command.
31
+ #
32
+ # @example Usage
33
+ # Prmd::CLI::Generate.execute(argv: ['bread'],
34
+ # output_file: 'schema/schemata/bread.json')
35
+ #
36
+ # @param (see Prmd::CLI::Base#execute)
37
+ # @return (see Prmd::CLI::Base#execute)
38
+ def self.execute(options = {})
39
+ name = options.fetch(:argv).first
40
+ write_result Prmd.init(name, options), options
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'prmd/cli/base'
2
+ require 'prmd/commands/render'
3
+
4
+ module Prmd
5
+ module CLI
6
+ # 'render' command module.
7
+ module Render
8
+ extend CLI::Base
9
+
10
+ # Returns a OptionParser for parsing 'render' command options.
11
+ #
12
+ # @param (see Prmd::CLI::Base#make_parser)
13
+ # @return (see Prmd::CLI::Base#make_parser)
14
+ def self.make_parser(options = {})
15
+ binname = options.fetch(:bin, 'prmd')
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = "#{binname} render [options] <combined schema>"
19
+ opts.on('-p', '--prepend header,overview', Array, 'Prepend files to output') do |p|
20
+ yield :prepend, p
21
+ end
22
+ opts.on('-t', '--template templates', String, 'Use alternate template') do |t|
23
+ yield :template, t
24
+ end
25
+ opts.on('-o', '--output-file FILENAME', String, 'File to write result to') do |n|
26
+ yield :output_file, n
27
+ end
28
+ end
29
+ end
30
+
31
+ # Executes the 'render' command.
32
+ #
33
+ # @example Usage
34
+ # Prmd::CLI::Render.execute(argv: ['schema/api.json'],
35
+ # template: 'my_template.md.erb',
36
+ # output_file: 'schema/api.md')
37
+ #
38
+ # @param (see Prmd::CLI::Base#execute)
39
+ # @return (see Prmd::CLI::Base#execute)
40
+ def self.execute(options = {})
41
+ filename = options.fetch(:argv).first
42
+ _, data = try_read(filename)
43
+ schema = Prmd::Schema.new(data)
44
+ write_result Prmd.render(schema, options), options
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ require 'prmd/cli/base'
2
+ require 'prmd/commands/verify'
3
+
4
+ module Prmd
5
+ module CLI
6
+ # 'verify' command module.
7
+ module Verify
8
+ extend CLI::Base
9
+
10
+ # Returns a OptionParser for parsing 'verify' command options.
11
+ #
12
+ # @param (see Prmd::CLI::Base#make_parser)
13
+ # @return (see Prmd::CLI::Base#make_parser)
14
+ def self.make_parser(options = {})
15
+ binname = options.fetch(:bin, 'prmd')
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = "#{binname} verify [options] <combined schema>"
19
+ opts.on('-y', '--yaml', 'Generate YAML') do |y|
20
+ yield :yaml, y
21
+ end
22
+ opts.on('-o', '--output-file FILENAME', String, 'File to write result to') do |n|
23
+ yield :output_file, n
24
+ end
25
+ end
26
+ end
27
+
28
+ # Executes the 'verify' command.
29
+ #
30
+ # @example Usage
31
+ # Prmd::CLI::Verify.execute(argv: ['schema/api.json'])
32
+ #
33
+ # @param (see Prmd::CLI::Base#execute)
34
+ # @return (see Prmd::CLI::Base#execute)
35
+ def self.execute(options = {})
36
+ filename = options.fetch(:argv).first
37
+ _, data = try_read(filename)
38
+ errors = Prmd.verify(data)
39
+ unless errors.empty?
40
+ errors.map! { |error| "#{filename}: #{error}" } if filename
41
+ errors.each { |error| $stderr.puts(error) }
42
+ exit(1)
43
+ end
44
+ result = options[:yaml] ? data.to_yaml : JSON.pretty_generate(data)
45
+ write_result result, options
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ require 'prmd/commands/combine'
2
+ require 'prmd/commands/init'
3
+ require 'prmd/commands/render'
4
+ require 'prmd/commands/verify'
@@ -1,73 +1,100 @@
1
+ require 'prmd/load_schema_file'
2
+ require 'prmd/core/schema_hash'
3
+ require 'prmd/core/combiner'
4
+
5
+ # :nodoc:
1
6
  module Prmd
2
- def self.combine(path, options={})
3
- files = if File.directory?(path)
4
- Dir.glob(File.join(path, '**', '*.json')) +
5
- Dir.glob(File.join(path, '**', '*.yaml')) -
6
- [options[:meta]]
7
- else
8
- [path]
9
- end
10
- # sort for stable loading on any platform
11
- schemata = []
12
- files.sort.each do |file|
13
- begin
14
- schemata << [file, YAML.load(File.read(file))]
15
- rescue
16
- $stderr.puts "unable to parse #{file}"
7
+ # Schema combine
8
+ module Combine
9
+ # @api private
10
+ # @param [#size] given
11
+ # @param [#size] expected
12
+ # @return [void]
13
+ def self.handle_faulty_load(given, expected)
14
+ unless given.size == expected.size
15
+ abort 'Somes files have failed to parse. ' \
16
+ 'If you wish to continue without them,' \
17
+ 'please enable faulty_load using --faulty-load'
17
18
  end
18
19
  end
19
- unless schemata.length == files.length
20
- exit(1) # one or more files failed to parse
21
- end
22
-
23
- data = {
24
- '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
25
- 'definitions' => {},
26
- 'properties' => {},
27
- 'type' => ['object']
28
- }
29
-
30
- # tracks which entities where defined in which file
31
- schemata_map = {}
32
20
 
33
- if options[:meta] && File.exists?(options[:meta])
34
- data.merge!(YAML.load(File.read(options[:meta])))
21
+ # @api private
22
+ # @param [Array<String>] paths
23
+ # @param [Hash<Symbol, Object>] options
24
+ # @return [Array<String>] list of filenames from paths
25
+ def self.crawl_map(paths, options = {})
26
+ files = [*paths].map do |path|
27
+ if File.directory?(path)
28
+ Dir.glob(File.join(path, '**', '*.{json,yml,yaml}'))
29
+ else
30
+ path
31
+ end
32
+ end
33
+ files.flatten!
34
+ files.delete(options[:meta])
35
+ files
35
36
  end
36
37
 
37
- schemata.each do |schema_file, schema_data|
38
- id = schema_data['id'].split('/').last
38
+ # @api private
39
+ # @param [String] filename
40
+ # @return [SchemaHash]
41
+ def self.load_schema_hash(filename)
42
+ data = Prmd.load_schema_file(filename)
43
+ SchemaHash.new(data, filename: filename)
44
+ end
39
45
 
40
- if file = schemata_map[schema_data['id']]
41
- $stderr.puts "`#{schema_data['id']}` (from #{schema_file}) was already defined in `#{file}` and will overwrite the first definition"
46
+ # @api private
47
+ # @param [Array<String>] files
48
+ # @param [Hash<Symbol, Object>] options
49
+ # @return [Array<SchemaHash>] schema hashes
50
+ def self.load_files(files, options = {})
51
+ files.each_with_object([]) do |filename, result|
52
+ begin
53
+ result << load_schema_hash(filename)
54
+ rescue JSON::ParserError, Psych::SyntaxError => ex
55
+ $stderr.puts "unable to parse #{filename} (#{ex.inspect})"
56
+ end
42
57
  end
43
- schemata_map[schema_data['id']] = schema_file
58
+ end
44
59
 
45
- # schemas are now in a single scope by combine
46
- schema_data.delete('id')
60
+ # @api private
61
+ # @param [Array<String>] paths
62
+ # @param [Hash<Symbol, Object>] options
63
+ # @return (see .load_files)
64
+ def self.load_schemas(paths, options = {})
65
+ files = crawl_map(paths, options)
66
+ # sort for stable loading on any platform
67
+ schemata = load_files(files.sort, options)
68
+ handle_faulty_load(schemata, files) unless options[:faulty_load]
69
+ schemata
70
+ end
47
71
 
48
- data['definitions']
49
- data['definitions'][id] = schema_data
50
- reference_localizer = lambda do |datum|
51
- case datum
52
- when Array
53
- datum.map {|element| reference_localizer.call(element)}
54
- when Hash
55
- if datum.has_key?('$ref')
56
- datum['$ref'] = '#/definitions' + datum['$ref'].gsub('#', '').gsub('/schemata', '')
57
- end
58
- if datum.has_key?('href') && datum['href'].is_a?(String)
59
- datum['href'] = datum['href'].gsub('%23', '').gsub(%r{%2Fschemata(%2F[^%]*%2F)}, '%23%2Fdefinitions\1')
60
- end
61
- datum.each { |k,v| datum[k] = reference_localizer.call(v) }
62
- else
63
- datum
64
- end
65
- end
66
- reference_localizer.call(data['definitions'][id])
72
+ # Merges all found schema files in the given paths into a single Schema
73
+ #
74
+ # @param [Array<String>] paths
75
+ # @param [Hash<Symbol, Object>] options
76
+ # @return (see Prmd::Combiner#combine)
77
+ def self.combine(paths, options = {})
78
+ schemata = load_schemas(paths)
79
+ base = Prmd::Template.load_json('combine_head.json')
80
+ schema = base['$schema']
81
+ meta = {}
82
+ meta = Prmd.load_schema_file(options[:meta]) if options[:meta]
83
+ combiner = Prmd::Combiner.new(meta: meta, base: base, schema: schema)
84
+ combiner.combine(*schemata)
85
+ end
67
86
 
68
- data['properties'][id] = { '$ref' => "#/definitions/#{id}" }
87
+ class << self
88
+ private :handle_faulty_load
89
+ private :crawl_map
90
+ private :load_schema_hash
91
+ private :load_files
92
+ private :load_schemas
69
93
  end
94
+ end
70
95
 
71
- Prmd::Schema.new(data)
96
+ # (see Prmd::Combine.combine)
97
+ def self.combine(paths, options = {})
98
+ Combine.combine(paths, { faulty_load: false }.merge(options))
72
99
  end
73
100
  end