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
@@ -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