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,107 +1,39 @@
1
+ require 'json'
2
+ require 'prmd/template'
3
+ require 'prmd/core/generator'
4
+
5
+ # :nodoc:
1
6
  module Prmd
2
- def self.init(resource, options={})
3
- data = {
4
- '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
5
- 'title' => 'FIXME',
6
- 'definitions' => {},
7
- 'description' => 'FIXME',
8
- 'links' => [],
9
- 'properties' => {},
10
- 'type' => ['object']
11
- }
7
+ # Schema generation
8
+ module Generate
9
+ # Creates a default Prmd::Generator using default templates
10
+ #
11
+ # @return [Prmd::Generator]
12
+ def self.make_generator
13
+ base = Prmd::Template.load_json('init_default.json')
14
+ template = Prmd::Template.load_template('init_resource.json.erb', '')
15
+ Prmd::Generator.new(base: base, template: template)
16
+ end
17
+ end
12
18
 
13
- schema = Prmd::Schema.new(data)
19
+ # Generate a schema template
20
+ #
21
+ # @param [String] resource
22
+ # @param [Hash<Symbol, Object>] options
23
+ # @return [String] schema template in YAML (yaml option was enabled) else JSON
24
+ def self.init(resource, options = {})
25
+ gen = Generate.make_generator
14
26
 
27
+ generator_options = { resource: nil, parent: nil }
15
28
  if resource
16
- if resource.include?('/')
17
- parent, resource = resource.split('/')
18
- end
19
- schema['id'] = "schemata/#{resource}"
20
- schema['title'] = "FIXME - #{resource[0...1].upcase}#{resource[1..-1]}"
21
- schema['definitions'] = {
22
- "created_at" => {
23
- "description" => "when #{resource} was created",
24
- "example" => "2012-01-01T12:00:00Z",
25
- "format" => "date-time",
26
- "type" => ["string"]
27
- },
28
- "id" => {
29
- "description" => "unique identifier of #{resource}",
30
- "example" => "01234567-89ab-cdef-0123-456789abcdef",
31
- "format" => "uuid",
32
- "type" => ["string"]
33
- },
34
- "identity" => {
35
- "$ref" => "/schemata/#{resource}#/definitions/id"
36
- },
37
- "updated_at" => {
38
- "description" => "when #{resource} was updated",
39
- "example" => "2012-01-01T12:00:00Z",
40
- "format" => "date-time",
41
- "type" => ["string"]
42
- }
43
- }
44
- schema['links'] = [
45
- {
46
- "description" => "Create a new #{resource}.",
47
- "href" => "/#{resource}s",
48
- "method" => "POST",
49
- "rel" => "create",
50
- "schema" => {
51
- "properties" => {},
52
- "type" => ["object"]
53
- },
54
- "title" => "Create"
55
- },
56
- {
57
- "description" => "Delete an existing #{resource}.",
58
- "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
59
- "method" => "DELETE",
60
- "rel" => "destroy",
61
- "title" => "Delete"
62
- },
63
- {
64
- "description" => "Info for existing #{resource}.",
65
- "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
66
- "method" => "GET",
67
- "rel" => "self",
68
- "title" => "Info"
69
- },
70
- {
71
- "description" => "List existing #{resource}s.",
72
- "href" => "/#{resource}s",
73
- "method" => "GET",
74
- "rel" => "instances",
75
- "title" => "List"
76
- },
77
- {
78
- "description" => "Update an existing #{resource}.",
79
- "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
80
- "method" => "PATCH",
81
- "rel" => "update",
82
- "schema" => {
83
- "properties" => {},
84
- "type" => ["object"]
85
- },
86
- "title" => "Update"
87
- }
88
- ]
89
- if parent
90
- schema['links'] << {
91
- "description" => "List existing #{resource}s for existing #{parent}.",
92
- "href" => "/#{parent}s/{(%2Fschemata%2F#{parent}%23%2Fdefinitions%2Fidentity)}/#{resource}s",
93
- "method" => "GET",
94
- "rel" => "instances",
95
- "title" => "List"
96
- }
97
- end
98
- schema['properties'] = {
99
- "created_at" => { "$ref" => "/schemata/#{resource}#/definitions/created_at" },
100
- "id" => { "$ref" => "/schemata/#{resource}#/definitions/id" },
101
- "updated_at" => { "$ref" => "/schemata/#{resource}#/definitions/updated_at" }
102
- }
29
+ parent = nil
30
+ parent, resource = resource.split('/') if resource.include?('/')
31
+ generator_options[:parent] = parent
32
+ generator_options[:resource] = resource
103
33
  end
104
34
 
35
+ schema = gen.generate(generator_options)
36
+
105
37
  if options[:yaml]
106
38
  schema.to_yaml
107
39
  else
@@ -1,24 +1,37 @@
1
- module Prmd
2
- def self.render(schema, options={})
3
- doc = ''
4
-
5
- options[:content_type] ||= 'application/json'
6
- options[:style] ||= 'default'
1
+ require 'prmd/core/renderer'
7
2
 
8
- if options[:prepend]
9
- doc << options[:prepend].map {|path| File.read(path)}.join("\n") << "\n"
3
+ # :nodoc:
4
+ module Prmd
5
+ # Render helper module
6
+ module Render
7
+ # Retrieve the schema template
8
+ #
9
+ # @param [Hash<Symbol, Object>] options
10
+ # @return (see Prmd::Template.load_template)
11
+ def self.get_template(options)
12
+ template = options.fetch(:template) do
13
+ abort 'render: Template was not provided'
14
+ end
15
+ template_dir = File.expand_path(template)
16
+ # to keep backward compatibility
17
+ template_dir = File.dirname(template) unless File.directory?(template_dir)
18
+ Prmd::Template.load_template('schema.erb', template_dir)
10
19
  end
20
+ end
11
21
 
12
- template_dir = File::expand_path(options[:template])
13
- if not File.directory?(template_dir) # to keep backward compatibility
14
- template_dir = File.dirname(options[:template])
22
+ # Render provided schema to Markdown
23
+ #
24
+ # @param [Prmd::Schema] schema
25
+ # @return [String] rendered schema in Markdown
26
+ def self.render(schema, options = {})
27
+ renderer = Prmd::Renderer.new(template: Render.get_template(options))
28
+ doc = ''
29
+ if options[:prepend]
30
+ doc <<
31
+ options[:prepend].map { |path| File.read(path) }.join("\n") <<
32
+ "\n"
15
33
  end
16
-
17
- doc << Prmd::Template::render('schema.erb', template_dir, {
18
- options: options,
19
- schema: schema
20
- })
21
-
34
+ doc << renderer.render(schema, options)
22
35
  doc
23
36
  end
24
37
  end
@@ -1,51 +1,94 @@
1
- require "json"
2
- require "json_schema"
1
+ require 'json'
2
+ require 'json_schema'
3
3
 
4
+ # :nodoc:
4
5
  module Prmd
5
- # These schemas are listed manually and in order because they reference each
6
- # other.
7
- SCHEMAS = [
8
- "schema.json",
9
- "hyper-schema.json",
10
- "interagent-hyper-schema.json"
11
- ]
6
+ # Schema Verification
7
+ module Verification
8
+ # These schemas are listed manually and in order because they reference each
9
+ # other.
10
+ SCHEMAS = [
11
+ 'schema.json',
12
+ 'hyper-schema.json',
13
+ 'interagent-hyper-schema.json'
14
+ ]
12
15
 
13
- def self.verify(schema_data)
14
- store = init_document_store
16
+ # @return [JsonSchema::DocumentStore]
17
+ def self.init_document_store
18
+ store = JsonSchema::DocumentStore.new
19
+ SCHEMAS.each do |file|
20
+ file = File.expand_path("../../../../schemas/#{file}", __FILE__)
21
+ data = JSON.parse(File.read(file))
22
+ schema = JsonSchema::Parser.new.parse!(data)
23
+ schema.expand_references!(store: store)
24
+ store.add_schema(schema)
25
+ end
26
+ store
27
+ end
15
28
 
16
- if !(schema_uri = schema_data["$schema"])
17
- return ["Missing $schema."]
29
+ # @return [JsonSchema::DocumentStore]
30
+ def self.document_store
31
+ @document_store ||= init_document_store
18
32
  end
19
33
 
20
- # for good measure, make sure that the schema parses and that its
21
- # references can be expanded
22
- schema, errors = JsonSchema.parse!(schema_data)
23
- return JsonSchema::SchemaError.aggregate(errors) if !schema
34
+ # @param [Hash] schema_data
35
+ # @return [Array<String>] errors from failed verfication
36
+ def self.verify_parsable(schema_data)
37
+ # for good measure, make sure that the schema parses and that its
38
+ # references can be expanded
39
+ schema, errors = JsonSchema.parse!(schema_data)
40
+ return JsonSchema::SchemaError.aggregate(errors) unless schema
24
41
 
25
- valid, errors = schema.expand_references(store: store)
26
- return JsonSchema::SchemaError.aggregate(errors) if !valid
42
+ valid, errors = schema.expand_references(store: document_store)
43
+ return JsonSchema::SchemaError.aggregate(errors) unless valid
27
44
 
28
- if !(meta_schema = store.lookup_schema(schema_uri))
29
- return ["Unknown $schema: #{schema_uri}."]
45
+ []
30
46
  end
31
47
 
32
- valid, errors = meta_schema.validate(schema_data)
33
- return JsonSchema::SchemaError.aggregate(errors) if !valid
48
+ # @param [Hash] schema_data
49
+ # @return [Array<String>] errors from failed verfication
50
+ def self.verify_meta_schema(meta_schema, schema_data)
51
+ valid, errors = meta_schema.validate(schema_data)
52
+ return JsonSchema::SchemaError.aggregate(errors) unless valid
34
53
 
35
- []
36
- end
54
+ []
55
+ end
56
+
57
+ # @param [Hash] schema_data
58
+ # @return [Array<String>] errors from failed verfication
59
+ def self.verify_schema(schema_data)
60
+ schema_uri = schema_data['$schema']
61
+ return ['Missing $schema key.'] unless schema_uri
62
+
63
+ meta_schema = document_store.lookup_schema(schema_uri)
64
+ return ["Unknown $schema: #{schema_uri}."] unless meta_schema
37
65
 
38
- private
66
+ verify_meta_schema(meta_schema, schema_data)
67
+ end
68
+
69
+ # Verfies that a given schema is valid
70
+ #
71
+ # @param [Hash] schema_data
72
+ # @return [Array<String>] errors from failed verification
73
+ def self.verify(schema_data)
74
+ a = verify_schema(schema_data)
75
+ return a unless a.empty?
76
+ b = verify_parsable(schema_data)
77
+ return b unless b.empty?
78
+ []
79
+ end
39
80
 
40
- def self.init_document_store
41
- store = JsonSchema::DocumentStore.new
42
- SCHEMAS.each do |file|
43
- file = File.expand_path("../../../../schemas/#{file}", __FILE__)
44
- data = JSON.parse(File.read(file))
45
- schema = JsonSchema::Parser.new.parse!(data)
46
- schema.expand_references!(store: store)
47
- store.add_schema(schema)
81
+ class << self
82
+ private :init_document_store
83
+ private :document_store
84
+ private :verify_parsable
85
+ private :verify_schema
86
+ private :verify_meta_schema
48
87
  end
49
- store
88
+ end
89
+
90
+ # (see Prmd::Verification.verify)
91
+ def self.verify(schema_data)
92
+ Verification.verify(schema_data)
50
93
  end
51
94
  end
@@ -0,0 +1,91 @@
1
+ require 'prmd/schema'
2
+ require 'prmd/core/schema_hash'
3
+
4
+ # :nodoc:
5
+ module Prmd
6
+ # Schema combiner
7
+ class Combiner
8
+ #
9
+ # @param [Hash<Symbol, Object>] properties
10
+ def initialize(properties = {})
11
+ @properties = properties
12
+ @schema = properties.fetch(:schema)
13
+ @base = properties.fetch(:base, {})
14
+ @meta = properties.fetch(:meta, {})
15
+ end
16
+
17
+ # @param [Array] array
18
+ # @return [Array]
19
+ def reference_localizer_array(array)
20
+ array.map { |element| reference_localizer(element) }
21
+ end
22
+
23
+ # @param [Hash] hash
24
+ # @return [Hash]
25
+ def reference_localizer_hash(hash)
26
+ if hash.key?('$ref')
27
+ hash['$ref'] = '#/definitions' + hash['$ref'].gsub('#', '')
28
+ .gsub('/schemata', '')
29
+ end
30
+ if hash.key?('href') && hash['href'].is_a?(String)
31
+ hash['href'] = hash['href'].gsub('%23', '')
32
+ .gsub(/%2Fschemata(%2F[^%]*%2F)/,
33
+ '%23%2Fdefinitions\1')
34
+ end
35
+ hash.each_with_object({}) { |(k, v), r| r[k] = reference_localizer(v) }
36
+ end
37
+
38
+ #
39
+ # @param [Object] datum
40
+ # @return [Object]
41
+ def reference_localizer(datum)
42
+ case datum
43
+ when Array
44
+ reference_localizer_array(datum)
45
+ when Hash
46
+ reference_localizer_hash(datum)
47
+ else
48
+ datum
49
+ end
50
+ end
51
+
52
+ #
53
+ # @param [Prmd::SchemaHash] schemata
54
+ # @return [Prmd::Schema]
55
+ def combine(*schemata)
56
+ # tracks which entities where defined in which file
57
+ schemata_map = {}
58
+
59
+ data = {}
60
+ data.merge!(@base)
61
+ data.merge!(@meta)
62
+
63
+ schemata.each do |schema|
64
+ id = schema.fetch('id')
65
+ id_ary = id.split('/').last
66
+
67
+ if s = schemata_map[id]
68
+ $stderr.puts "`#{id}` (from #{schema.filename}) was already defined" \
69
+ "in `#{s.filename}` and will overwrite the first" \
70
+ "definition"
71
+ end
72
+ # avoinding damaging the original schema
73
+ embedded_schema = schema.dup
74
+ # schemas are now in a single scope by combine
75
+ embedded_schema.delete('id')
76
+ schemata_map[id] = embedded_schema
77
+
78
+ data['definitions'][id_ary] = embedded_schema.to_h
79
+ data['properties'][id_ary] = { '$ref' => "#/definitions/#{id_ary}" }
80
+
81
+ reference_localizer(data['definitions'][id_ary])
82
+ end
83
+
84
+ Prmd::Schema.new(data)
85
+ end
86
+
87
+ private :reference_localizer_array
88
+ private :reference_localizer_hash
89
+ private :reference_localizer
90
+ end
91
+ end
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+ require 'prmd/schema'
3
+
4
+ # :nodoc:
5
+ module Prmd
6
+ # Schema generator
7
+ class Generator
8
+ #
9
+ # @param [Hash<Symbol, Object>] properties
10
+ def initialize(properties = {})
11
+ @properties = properties
12
+ @base = properties.fetch(:base, {})
13
+ @template = properties.fetch(:template)
14
+ end
15
+
16
+ #
17
+ # @param [Hash<Symbol, Object>] options
18
+ def generate(options = {})
19
+ res = @template.result(options)
20
+ resource_schema = JSON.parse(res)
21
+ schema = Prmd::Schema.new
22
+ schema.merge!(@base)
23
+ schema.merge!(resource_schema)
24
+ schema
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ require 'prmd/template'
2
+
3
+ # :nodoc:
4
+ module Prmd
5
+ # Schema Generator
6
+ class Renderer
7
+ #
8
+ # @param [Hash<Symbol, Object>] properties
9
+ def initialize(properties = {})
10
+ @properties = properties
11
+ @template = @properties.fetch(:template)
12
+ end
13
+
14
+ #
15
+ # @return [Hash<Symbol, Object>]
16
+ def default_options
17
+ {
18
+ http_header: {},
19
+ content_type: 'application/json',
20
+ doc: {},
21
+ prepend: nil
22
+ }
23
+ end
24
+
25
+ #
26
+ # @param [Hash<Symbol, Object>] options
27
+ # @return [void]
28
+ def append_default_options(options)
29
+ options[:doc] = {
30
+ url_style: 'default',
31
+ disable_title_and_description: false
32
+ }.merge(options[:doc])
33
+ end
34
+
35
+ #
36
+ # @param [Hash<Symbol, Object>] options
37
+ # @return [Hash<Symbol, Object>]
38
+ def setup_options(options)
39
+ opts = default_options
40
+ opts.merge!(options)
41
+ append_default_options(opts)
42
+ opts
43
+ end
44
+
45
+ #
46
+ # @param [Prmd::Schema] schema
47
+ # @param [Hash<Symbol, Object>] options
48
+ def render(schema, options = {})
49
+ @template.result(schema: schema, options: setup_options(options))
50
+ end
51
+
52
+ private :default_options
53
+ private :append_default_options
54
+ private :setup_options
55
+ end
56
+ end