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