praxis 0.20.1 → 0.21
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/CHANGELOG.md +36 -0
- data/lib/api_browser/Gruntfile.js +33 -3
- data/lib/api_browser/app/index.html +3 -0
- data/lib/api_browser/app/js/factories/Example.js +4 -0
- data/lib/api_browser/app/js/factories/template_for.js +14 -0
- data/lib/api_browser/app/js/filters/attribute_name.js +3 -2
- data/lib/api_browser/app/js/filters/header_info.js +9 -0
- data/lib/api_browser/app/views/action.html +11 -2
- data/lib/api_browser/app/views/controller.html +5 -5
- data/lib/api_browser/app/views/menu.html +1 -1
- data/lib/api_browser/app/views/type.html +4 -23
- data/lib/api_browser/app/views/type/details.html +7 -4
- data/lib/api_browser/app/views/types/main/array.html +22 -0
- data/lib/api_browser/app/views/types/main/default.html +23 -0
- data/lib/api_browser/app/views/types/main/hash.html +23 -0
- data/lib/praxis.rb +2 -0
- data/lib/praxis/action_definition.rb +0 -8
- data/lib/praxis/api_definition.rb +11 -6
- data/lib/praxis/api_general_info.rb +13 -0
- data/lib/praxis/bootloader.rb +11 -5
- data/lib/praxis/docs/generator.rb +31 -11
- data/lib/praxis/docs/link_builder.rb +30 -0
- data/lib/praxis/extensions/field_expansion.rb +2 -2
- data/lib/praxis/media_type.rb +1 -1
- data/lib/praxis/middleware_app.rb +30 -0
- data/lib/praxis/resource_definition.rb +24 -2
- data/lib/praxis/response.rb +2 -1
- data/lib/praxis/response_definition.rb +2 -2
- data/lib/praxis/responses/http.rb +28 -92
- data/lib/praxis/responses/validation_error.rb +4 -1
- data/lib/praxis/tasks/api_docs.rb +11 -24
- data/lib/praxis/trait.rb +12 -7
- data/lib/praxis/validation_handler.rb +2 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +11 -7
- data/spec/api_browser/filters/attribute_name_spec.js +2 -2
- data/spec/praxis/action_definition_spec.rb +23 -1
- data/spec/praxis/bootloader_spec.rb +28 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +10 -0
- data/spec/praxis/middleware_app_spec.rb +55 -0
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +8 -3
- data/spec/praxis/resource_definition_spec.rb +51 -2
- data/spec/praxis/response_definition_spec.rb +16 -4
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/trait_spec.rb +13 -0
- data/spec/spec_app/config/environment.rb +11 -1
- metadata +30 -25
- data/lib/praxis/restful_doc_generator.rb +0 -439
@@ -117,7 +117,7 @@ describe Praxis::Response do
|
|
117
117
|
end
|
118
118
|
|
119
119
|
context 'multipart responses' do
|
120
|
-
let(:part) { Praxis::MultipartPart.new('not so ok', {'Status' => 400, "Location" => "somewhere"}) }
|
120
|
+
let(:part) { Praxis::MultipartPart.new('not so ok', {'Status' => 400, "Location" => "somewhere"}, {}) }
|
121
121
|
|
122
122
|
context '#add_part' do
|
123
123
|
|
data/spec/praxis/trait_spec.rb
CHANGED
@@ -13,8 +13,11 @@ describe Praxis::Trait do
|
|
13
13
|
response :something
|
14
14
|
response :nothing
|
15
15
|
|
16
|
+
# Double params to test "additive behavior"
|
16
17
|
params do
|
17
18
|
attribute :app_name, String
|
19
|
+
end
|
20
|
+
params do
|
18
21
|
attribute :order, String,
|
19
22
|
description: "Field to sort by."
|
20
23
|
end
|
@@ -48,4 +51,14 @@ describe Praxis::Trait do
|
|
48
51
|
|
49
52
|
end
|
50
53
|
|
54
|
+
context 'apply!' do
|
55
|
+
let(:target) { double("Target") }
|
56
|
+
it 'does' do
|
57
|
+
expect(target).to receive(:routing).once
|
58
|
+
expect(target).to receive(:response).twice
|
59
|
+
expect(target).to receive(:params).twice
|
60
|
+
expect(target).to receive(:headers).once
|
61
|
+
subject.apply!(target)
|
62
|
+
end
|
63
|
+
end
|
51
64
|
end
|
@@ -21,9 +21,19 @@ Praxis::Application.configure do |application|
|
|
21
21
|
application.bootloader.use SimpleAuthenticationPlugin, config_file: 'config/authentication.yml'
|
22
22
|
application.bootloader.use AuthorizationPlugin
|
23
23
|
|
24
|
+
|
25
|
+
adapter_name = 'sqlite'
|
26
|
+
db_name = ':memory:'
|
27
|
+
connection_opts = if RUBY_PLATFORM !~ /java/
|
28
|
+
{ adapter: adapter_name , database: db_name }
|
29
|
+
else
|
30
|
+
require 'jdbc/sqlite3'
|
31
|
+
{ adapter: 'jdbc', uri: "jdbc:#{adapter_name}:#{db_name}" }
|
32
|
+
end
|
33
|
+
|
24
34
|
application.bootloader.use Praxis::Plugins::PraxisMapperPlugin, {
|
25
35
|
config_data: {
|
26
|
-
repositories: { default:
|
36
|
+
repositories: { default: connection_opts },
|
27
37
|
log_stats: 'detailed'
|
28
38
|
}
|
29
39
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.21'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-08-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -73,42 +73,42 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '4.
|
76
|
+
version: '4.3'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '4.
|
83
|
+
version: '4.3'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: praxis-blueprints
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - "~>"
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: '3.
|
90
|
+
version: '3.3'
|
91
91
|
type: :runtime
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '3.
|
97
|
+
version: '3.3'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: attributor
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
101
101
|
requirements:
|
102
102
|
- - "~>"
|
103
103
|
- !ruby/object:Gem::Version
|
104
|
-
version: 5.
|
104
|
+
version: '5.1'
|
105
105
|
type: :runtime
|
106
106
|
prerelease: false
|
107
107
|
version_requirements: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - "~>"
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version: 5.
|
111
|
+
version: '5.1'
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
113
|
name: thor
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,6 +235,20 @@ dependencies:
|
|
235
235
|
- - "~>"
|
236
236
|
- !ruby/object:Gem::Version
|
237
237
|
version: '0'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: sqlite3
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
requirements:
|
242
|
+
- - "~>"
|
243
|
+
- !ruby/object:Gem::Version
|
244
|
+
version: '1'
|
245
|
+
type: :development
|
246
|
+
prerelease: false
|
247
|
+
version_requirements: !ruby/object:Gem::Requirement
|
248
|
+
requirements:
|
249
|
+
- - "~>"
|
250
|
+
- !ruby/object:Gem::Version
|
251
|
+
version: '1'
|
238
252
|
- !ruby/object:Gem::Dependency
|
239
253
|
name: rspec
|
240
254
|
requirement: !ruby/object:Gem::Requirement
|
@@ -375,20 +389,6 @@ dependencies:
|
|
375
389
|
- - "~>"
|
376
390
|
- !ruby/object:Gem::Version
|
377
391
|
version: '0'
|
378
|
-
- !ruby/object:Gem::Dependency
|
379
|
-
name: sqlite3
|
380
|
-
requirement: !ruby/object:Gem::Requirement
|
381
|
-
requirements:
|
382
|
-
- - "~>"
|
383
|
-
- !ruby/object:Gem::Version
|
384
|
-
version: '1'
|
385
|
-
type: :development
|
386
|
-
prerelease: false
|
387
|
-
version_requirements: !ruby/object:Gem::Requirement
|
388
|
-
requirements:
|
389
|
-
- - "~>"
|
390
|
-
- !ruby/object:Gem::Version
|
391
|
-
version: '1'
|
392
392
|
- !ruby/object:Gem::Dependency
|
393
393
|
name: coveralls
|
394
394
|
requirement: !ruby/object:Gem::Requirement
|
@@ -456,6 +456,7 @@ files:
|
|
456
456
|
- lib/api_browser/app/js/factories/template_for.js
|
457
457
|
- lib/api_browser/app/js/filters/attribute_name.js
|
458
458
|
- lib/api_browser/app/js/filters/friendly_json.js
|
459
|
+
- lib/api_browser/app/js/filters/header_info.js
|
459
460
|
- lib/api_browser/app/js/filters/is_empty.js
|
460
461
|
- lib/api_browser/app/js/filters/markdown.js
|
461
462
|
- lib/api_browser/app/js/filters/resource_name.js
|
@@ -497,6 +498,9 @@ files:
|
|
497
498
|
- lib/api_browser/app/views/types/label/primitive_collection.html
|
498
499
|
- lib/api_browser/app/views/types/label/type.html
|
499
500
|
- lib/api_browser/app/views/types/label/type_collection.html
|
501
|
+
- lib/api_browser/app/views/types/main/array.html
|
502
|
+
- lib/api_browser/app/views/types/main/default.html
|
503
|
+
- lib/api_browser/app/views/types/main/hash.html
|
500
504
|
- lib/api_browser/app/views/types/standalone/array.html
|
501
505
|
- lib/api_browser/app/views/types/standalone/default.html
|
502
506
|
- lib/api_browser/app/views/types/standalone/struct.html
|
@@ -524,6 +528,7 @@ files:
|
|
524
528
|
- lib/praxis/controller.rb
|
525
529
|
- lib/praxis/dispatcher.rb
|
526
530
|
- lib/praxis/docs/generator.rb
|
531
|
+
- lib/praxis/docs/link_builder.rb
|
527
532
|
- lib/praxis/error_handler.rb
|
528
533
|
- lib/praxis/exception.rb
|
529
534
|
- lib/praxis/exceptions/config.rb
|
@@ -548,6 +553,7 @@ files:
|
|
548
553
|
- lib/praxis/media_type.rb
|
549
554
|
- lib/praxis/media_type_collection.rb
|
550
555
|
- lib/praxis/media_type_identifier.rb
|
556
|
+
- lib/praxis/middleware_app.rb
|
551
557
|
- lib/praxis/multipart/parser.rb
|
552
558
|
- lib/praxis/multipart/part.rb
|
553
559
|
- lib/praxis/notifications.rb
|
@@ -570,7 +576,6 @@ files:
|
|
570
576
|
- lib/praxis/responses/internal_server_error.rb
|
571
577
|
- lib/praxis/responses/multipart_ok.rb
|
572
578
|
- lib/praxis/responses/validation_error.rb
|
573
|
-
- lib/praxis/restful_doc_generator.rb
|
574
579
|
- lib/praxis/route.rb
|
575
580
|
- lib/praxis/router.rb
|
576
581
|
- lib/praxis/router/rack.rb
|
@@ -621,6 +626,7 @@ files:
|
|
621
626
|
- spec/praxis/media_type_collection_spec.rb
|
622
627
|
- spec/praxis/media_type_identifier_spec.rb
|
623
628
|
- spec/praxis/media_type_spec.rb
|
629
|
+
- spec/praxis/middleware_app_spec.rb
|
624
630
|
- spec/praxis/multipart/parser_spec.rb
|
625
631
|
- spec/praxis/notifications_spec.rb
|
626
632
|
- spec/praxis/plugin_concern_spec.rb
|
@@ -725,9 +731,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
725
731
|
version: '0'
|
726
732
|
requirements: []
|
727
733
|
rubyforge_project:
|
728
|
-
rubygems_version: 2.
|
734
|
+
rubygems_version: 2.5.1
|
729
735
|
signing_key:
|
730
736
|
specification_version: 4
|
731
737
|
summary: Building APIs the way you want it.
|
732
738
|
test_files: []
|
733
|
-
has_rdoc:
|
@@ -1,439 +0,0 @@
|
|
1
|
-
module Praxis
|
2
|
-
class RestfulDocGenerator
|
3
|
-
|
4
|
-
class << self
|
5
|
-
attr_reader :inspected_types
|
6
|
-
end
|
7
|
-
|
8
|
-
@inspected_types = Set.new
|
9
|
-
API_DOCS_DIRNAME = 'docs/api'
|
10
|
-
|
11
|
-
EXCLUDED_TYPES_FROM_TOP_LEVEL = Set.new([
|
12
|
-
Attributor::Boolean,
|
13
|
-
Attributor::CSV,
|
14
|
-
Attributor::DateTime,
|
15
|
-
Attributor::Float,
|
16
|
-
Attributor::Hash,
|
17
|
-
Attributor::Ids,
|
18
|
-
Attributor::Integer,
|
19
|
-
Attributor::Object,
|
20
|
-
Attributor::String,
|
21
|
-
Attributor::Symbol
|
22
|
-
]).freeze
|
23
|
-
|
24
|
-
def self.inspect_attributes(the_type)
|
25
|
-
|
26
|
-
reachable = Set.new
|
27
|
-
return reachable if the_type.nil? || the_type.is_a?(Praxis::SimpleMediaType)
|
28
|
-
|
29
|
-
# If an attribute comes in, get its type
|
30
|
-
the_type = the_type.type if the_type.is_a? Attributor::Attribute
|
31
|
-
|
32
|
-
# Collection types are special since they wrap a member type, so let's reach in and grab it
|
33
|
-
if the_type < Attributor::Collection
|
34
|
-
if the_type.member_attribute.nil?
|
35
|
-
the_type = the_type.member_type
|
36
|
-
else
|
37
|
-
the_type = the_type.member_attribute.type
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if @inspected_types.include? the_type
|
42
|
-
# We're done if we've already inspected it
|
43
|
-
return reachable
|
44
|
-
else
|
45
|
-
# Mark it as inspected (before recursing)
|
46
|
-
@inspected_types << the_type unless the_type.name == nil # Don't bother with anon structs
|
47
|
-
end
|
48
|
-
#puts "Inspecting type: #{the_type.name}" if the_type.name != nil
|
49
|
-
|
50
|
-
reachable << the_type unless the_type.name == nil # Don't bother with anon structs
|
51
|
-
if the_type.respond_to? :attributes
|
52
|
-
the_type.attributes.each do |name, attr|
|
53
|
-
attr_type = attr.type
|
54
|
-
#puts "Inspecting attr: #{name} (class: #{attr_type.name}) #{attr_type.inspect}"
|
55
|
-
reachable += self.inspect_attributes(attr_type)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
reachable
|
59
|
-
end
|
60
|
-
|
61
|
-
class Resource
|
62
|
-
|
63
|
-
attr_accessor :media_type, :reachable_types, :version, :controller_config
|
64
|
-
|
65
|
-
def initialize( definition )
|
66
|
-
@controller_config = definition
|
67
|
-
if controller_config.version == 'n/a'
|
68
|
-
@version = 'unversioned'
|
69
|
-
else
|
70
|
-
@version = controller_config.version
|
71
|
-
end
|
72
|
-
@media_type = controller_config.media_type
|
73
|
-
@reachable_types = Set.new
|
74
|
-
|
75
|
-
# Collect reachable types from the media_type if any (plus itself)
|
76
|
-
if @media_type && ! @media_type.is_a?(Praxis::SimpleMediaType)
|
77
|
-
add_to_reachable RestfulDocGenerator.inspect_attributes(@media_type)
|
78
|
-
@generated_example = @media_type.example(self.id)
|
79
|
-
end
|
80
|
-
|
81
|
-
# Collect reachable types from the params and payload definitions
|
82
|
-
@controller_config.actions.each do |name, action_config|
|
83
|
-
# skip actions with doc_visibility of :none
|
84
|
-
next if action_config.metadata[:doc_visibility] == :none
|
85
|
-
|
86
|
-
add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.params)
|
87
|
-
add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.payload)
|
88
|
-
|
89
|
-
action_config.responses.values.each do |response|
|
90
|
-
add_to_reachable RestfulDocGenerator.inspect_attributes(response.media_type)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
# TODO: I think that the "id"/"name" of a resource should be provided by the definition/controller...not derived here
|
97
|
-
def id
|
98
|
-
if @controller_config.controller
|
99
|
-
@controller_config.controller.id
|
100
|
-
else
|
101
|
-
# If an API doesn't quite have the controller defined, let's use the name from the resource definition
|
102
|
-
@controller_config.id
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def name
|
107
|
-
if @controller_config.controller
|
108
|
-
@controller_config.controller.name
|
109
|
-
else
|
110
|
-
# If an API doesn't quite have the controller defined, let's use the name from the resource definition
|
111
|
-
@controller_config.name
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def parent
|
116
|
-
@controller_config.parent
|
117
|
-
end
|
118
|
-
|
119
|
-
def friendly_name
|
120
|
-
# FIXME: is it really about the controller? or the attached resource definition?
|
121
|
-
segments = self.name.split("::")
|
122
|
-
# FIXME: Crappy hack to derive a friendly name
|
123
|
-
if ["Collection","Links"].include? segments.last
|
124
|
-
segments[-2] + segments[-1] # concat the last 2
|
125
|
-
else
|
126
|
-
segments.last
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def add_to_reachable( found )
|
131
|
-
return if found == nil
|
132
|
-
@reachable_types += found
|
133
|
-
end
|
134
|
-
|
135
|
-
def display_name
|
136
|
-
@controller_config.display_name
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def initialize(root_dir)
|
141
|
-
@root_dir = root_dir
|
142
|
-
@doc_root_dir = File.join(@root_dir, API_DOCS_DIRNAME)
|
143
|
-
@resources = []
|
144
|
-
|
145
|
-
Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
|
146
|
-
|
147
|
-
remove_previous_doc_data
|
148
|
-
load_resources
|
149
|
-
|
150
|
-
# Gather all reachable types (grouped by version)
|
151
|
-
types_for = Hash.new
|
152
|
-
@resources.each do |r|
|
153
|
-
types_for[r.version] ||= Set.new
|
154
|
-
types_for[r.version] += r.reachable_types
|
155
|
-
end
|
156
|
-
|
157
|
-
write_resources
|
158
|
-
write_types(types_for)
|
159
|
-
write_index(types_for)
|
160
|
-
write_info(types_for)
|
161
|
-
write_templates(types_for)
|
162
|
-
end
|
163
|
-
|
164
|
-
def load_resources
|
165
|
-
Praxis::Application.instance.resource_definitions.map do |resource|
|
166
|
-
# skip resources with doc_visibility of :none
|
167
|
-
next if resource.metadata[:doc_visibility] == :none
|
168
|
-
|
169
|
-
@resources << Resource.new(resource)
|
170
|
-
end
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
def dump_example_for(context_name, object)
|
175
|
-
example = object.example(Array(context_name))
|
176
|
-
if object.is_a? Praxis::Blueprint
|
177
|
-
example.render(view: :master)
|
178
|
-
elsif object.is_a? Attributor::Attribute
|
179
|
-
object.dump(example)
|
180
|
-
else
|
181
|
-
raise "Do not know how to dump this object (it is not a Blueprint or an Attribute): #{object}"
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def write_resources
|
186
|
-
@resources.each do |r|
|
187
|
-
|
188
|
-
filename = File.join(@doc_root_dir, r.version, "resources","#{r.id}.json")
|
189
|
-
#puts "Dumping #{r.id} to #{filename}"
|
190
|
-
base = File.dirname(filename)
|
191
|
-
FileUtils.mkdir_p base unless File.exists? base
|
192
|
-
resource_description = r.controller_config.describe
|
193
|
-
|
194
|
-
# strip actions with doc_visibility of :none
|
195
|
-
resource_description[:actions].reject! { |a| a[:metadata][:doc_visibility] == :none }
|
196
|
-
|
197
|
-
# Go through the params/payload of each action and generate an example for them (then stick it into the description hash)
|
198
|
-
r.controller_config.actions.each do |action_name, action|
|
199
|
-
# skip actions with doc_visibility of :none
|
200
|
-
next if action.metadata[:doc_visibility] == :none
|
201
|
-
|
202
|
-
generated_examples = {}
|
203
|
-
if action.params
|
204
|
-
generated_examples[:params] = dump_example_for( r.id, action.params )
|
205
|
-
end
|
206
|
-
if action.payload
|
207
|
-
generated_examples[:payload] = dump_example_for( r.id, action.payload )
|
208
|
-
end
|
209
|
-
action_description = resource_description[:actions].find{|a| a[:name] == action_name }
|
210
|
-
action_description[:params][:example] = generated_examples[:params] if generated_examples[:params]
|
211
|
-
action_description[:payload][:example] = generated_examples[:payload] if generated_examples[:payload]
|
212
|
-
end
|
213
|
-
|
214
|
-
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(resource_description))}
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def write_types( versioned_types )
|
219
|
-
versioned_types.each do |version, types|
|
220
|
-
dirname = File.join(@doc_root_dir, version, "types")
|
221
|
-
FileUtils.mkdir_p dirname unless File.exists? dirname
|
222
|
-
reportable_types = types - EXCLUDED_TYPES_FROM_TOP_LEVEL
|
223
|
-
reportable_types.each do |type|
|
224
|
-
filename = File.join(dirname, "#{type.id}.json")
|
225
|
-
#puts "Dumping #{type.name} to #{filename}"
|
226
|
-
type_output = type.describe
|
227
|
-
example_data = type.example(type.to_s)
|
228
|
-
if type_output[:views]
|
229
|
-
type_output[:views].delete(:master)
|
230
|
-
type_output[:views].each do |view_name, view_info|
|
231
|
-
# Add and example for each view
|
232
|
-
unless( type < Praxis::Links ) #TODO: why do we need to skip an example for links?
|
233
|
-
view_info[:example] = example_data.render(view: view_name)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
# Save a full type example
|
238
|
-
# ...but not for links or link classes (there's no object container context if done alone!!)
|
239
|
-
unless( type < Praxis::Links ) #TODO: again, why is this special?
|
240
|
-
type_output[:example] = if example_data.respond_to? :render
|
241
|
-
example_data.render(view: :master)
|
242
|
-
else
|
243
|
-
type.dump(example_data)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# add an example for each attribute??
|
248
|
-
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(type_output))}
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
# index looks like something like this:
|
254
|
-
# {'1.0':
|
255
|
-
# {
|
256
|
-
# // Typical entry for controller with an associated mediatype
|
257
|
-
# "Post" : { media_type: "V1::MT:Post" , controller: "V1:Ctrl:Post"}
|
258
|
-
# // Unusual entry for controller without an associated mediatype
|
259
|
-
# "Admin" : { controller: "V1:Ctrl:Admin" }
|
260
|
-
# // Entry for mediatype that somehow is not associated with any controller...
|
261
|
-
# "RemoteMT" : { media_type: "V1:Ctrl:RemoteMT" }
|
262
|
-
# // Entry to a non-primitive type (but not a mediatype), that it is not covered by any related controller or mt
|
263
|
-
# "Locale" : { kind: "Module::Locale"}
|
264
|
-
# }
|
265
|
-
#
|
266
|
-
# '2.0': { ... }
|
267
|
-
# }
|
268
|
-
def write_index( versioned_types )
|
269
|
-
index = Hash.new
|
270
|
-
media_types_seen_from_controllers = Set.new
|
271
|
-
# Process the resources first
|
272
|
-
|
273
|
-
@resources.each do |r|
|
274
|
-
index[r.version] ||= Hash.new
|
275
|
-
info = {controller: r.id, name: r.name}
|
276
|
-
info[:parent] = r.parent.controller ? r.parent.controller.id : r.parent.id if r.parent
|
277
|
-
if r.media_type
|
278
|
-
info[:media_type] = r.media_type.id
|
279
|
-
media_types_seen_from_controllers << r.media_type
|
280
|
-
end
|
281
|
-
display_name = r.display_name #.name.split("::").last
|
282
|
-
index[r.version][display_name] = info
|
283
|
-
end
|
284
|
-
|
285
|
-
versioned_types.each do |version, types|
|
286
|
-
# Discard any mediatypes that we've already seen and processed as controller related
|
287
|
-
reportable_types = types - media_types_seen_from_controllers - EXCLUDED_TYPES_FROM_TOP_LEVEL
|
288
|
-
#TODO: think about these special cases, is it needed?
|
289
|
-
reportable_types.reject!{|type| type < Praxis::Links || type < Praxis::MediaTypeCollection }
|
290
|
-
|
291
|
-
reportable_types.each do |type|
|
292
|
-
index[version] ||= Hash.new
|
293
|
-
next unless type.respond_to?(:display_name)
|
294
|
-
display_name = type.display_name #.name.split("::").last
|
295
|
-
if index[version].has_key? display_name
|
296
|
-
raise "Display name already taken for version #{version}! #{display_name}"
|
297
|
-
end
|
298
|
-
index[version][display_name] = if type < Praxis::MediaType
|
299
|
-
{media_type: type.id }
|
300
|
-
else
|
301
|
-
{kind: type.id}
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
305
|
-
filename = File.join(@doc_root_dir, "index.json")
|
306
|
-
dirname = File.dirname(filename)
|
307
|
-
FileUtils.mkdir_p dirname unless File.exists? dirname
|
308
|
-
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(index))}
|
309
|
-
end
|
310
|
-
|
311
|
-
# Writes an "index" type file inside each version, with some higher level information about the API
|
312
|
-
def write_info( versioned_types )
|
313
|
-
|
314
|
-
resources_by_version = Hash.new do |hash, v|
|
315
|
-
hash[v] = Set.new
|
316
|
-
end
|
317
|
-
types_by_version = Hash.new do |hash, v|
|
318
|
-
hash[v] = Set.new
|
319
|
-
end
|
320
|
-
|
321
|
-
@resources.each do |r|
|
322
|
-
resources_by_version[r.version] << { id: r.id, name: r.name, friendly_name: r.friendly_name}
|
323
|
-
end
|
324
|
-
|
325
|
-
versioned_types.each do |version, types|
|
326
|
-
# # Discard any mediatypes that we've already seen and processed as controller related
|
327
|
-
reportable_types = types - EXCLUDED_TYPES_FROM_TOP_LEVEL
|
328
|
-
# #TODO: think about these special cases, is it needed?
|
329
|
-
# reportable_types.reject!{|type| type < Praxis::Links || type < Praxis::MediaTypeCollection }
|
330
|
-
|
331
|
-
reportable_types.each do |type|
|
332
|
-
segments = type.name.split("::")
|
333
|
-
# FIXME: Crappy hack to derive a friendly name
|
334
|
-
friendly_name = if ["Collection","Links"].include? segments.last
|
335
|
-
segments[-2] + segments[-1] # concat the last 2
|
336
|
-
else
|
337
|
-
segments.last
|
338
|
-
end
|
339
|
-
|
340
|
-
types_by_version[version] << { id: type.id, name: type.name, friendly_name: friendly_name}
|
341
|
-
end
|
342
|
-
end
|
343
|
-
###############################
|
344
|
-
|
345
|
-
diff = resources_by_version.keys - types_by_version.keys - versioned_types.keys
|
346
|
-
raise "!!!!!!!! somehow we have a list of different versions from the types vs. resources seen" unless diff.empty?
|
347
|
-
|
348
|
-
infos = {}
|
349
|
-
versioned_types.each do |version, types|
|
350
|
-
infos[version] = {info:{}}
|
351
|
-
end
|
352
|
-
infos.merge!(ApiDefinition.instance.describe)
|
353
|
-
|
354
|
-
# Add resources and types list
|
355
|
-
versioned_types.keys.each do |v|
|
356
|
-
|
357
|
-
infos[v][:resources] = resources_by_version[v].each_with_object({}) do |element,hash|
|
358
|
-
hash[element[:id]] = { name: element[:name], friendly_name: element[:friendly_name] }
|
359
|
-
end
|
360
|
-
infos[v][:schemas] = types_by_version[v].each_with_object({}) do |element,hash|
|
361
|
-
hash[element[:id]] = { name: element[:name], friendly_name: element[:friendly_name] }
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
versioned_types.each do |version, types|
|
366
|
-
dirname = File.join(@doc_root_dir, version)
|
367
|
-
filename = File.join(dirname, "version_index.json")
|
368
|
-
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(infos[version]))}
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
|
373
|
-
def write_templates(versioned_types)
|
374
|
-
# Calculate and write top-level (non-versioned) templates
|
375
|
-
top_templates = write_template("")
|
376
|
-
# Calculate and write versioned templates (passing the top level ones for inheritance)
|
377
|
-
versioned_types.keys.each do |version|
|
378
|
-
write_template(version,top_templates)
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
def write_template(version,top_templates=nil)
|
383
|
-
# Collect template filenames (grouped by type: embedded vs. standalone)
|
384
|
-
templates_dir = File.join(@root_dir,"doc_browser","templates",version)
|
385
|
-
|
386
|
-
# Slurp in any top level (unversioned) templates if any
|
387
|
-
# Top level templates will apply to any versioned one (and can be overwritten
|
388
|
-
# if the version defines their own)
|
389
|
-
templates = {embedded: {}, standalone: {} }
|
390
|
-
if top_templates
|
391
|
-
templates[:embedded] = top_templates[:embedded].clone
|
392
|
-
templates[:standalone] = top_templates[:standalone].clone
|
393
|
-
end
|
394
|
-
|
395
|
-
dual = Dir.glob(File.join(templates_dir,"*.tmpl"))
|
396
|
-
embedded = Dir.glob(File.join(templates_dir,"embedded","*.tmpl"))
|
397
|
-
standalone = Dir.glob(File.join(templates_dir,"standalone","*.tmpl"))
|
398
|
-
|
399
|
-
# TODO: Encode the contents more appropriately rather than dumping a string
|
400
|
-
# Templates defined at the top will apply to both embedded and standalone
|
401
|
-
# But it can be overriden if the same type exists in the more specific directory
|
402
|
-
dual.each do |filename|
|
403
|
-
type_key = File.basename(filename).gsub(/.tmpl$/,'')
|
404
|
-
contents = IO.read(filename)
|
405
|
-
templates[:embedded][type_key] = contents
|
406
|
-
templates[:standalone][type_key] = contents
|
407
|
-
end
|
408
|
-
|
409
|
-
# For each embedded one, create a key in the embedded section, and encode the file contents in the value
|
410
|
-
embedded.each do |filename|
|
411
|
-
type_key = File.basename(filename).gsub(/.tmpl$/,'')
|
412
|
-
templates[:embedded][type_key] = IO.read(filename)
|
413
|
-
end
|
414
|
-
# For each standalone one, create a key in the standalone section, and encode the file contents in the value
|
415
|
-
standalone.each do |filename|
|
416
|
-
type_key = File.basename(filename).gsub(/.tmpl$/,'')
|
417
|
-
templates[:standalone][type_key] = IO.read(filename)
|
418
|
-
end
|
419
|
-
|
420
|
-
[:embedded,:standalone].each do |type|
|
421
|
-
v = version.empty? ? "top level": version
|
422
|
-
puts "Packaging #{v} #{type} templates for: #{templates[type].keys}" unless templates[type].keys.empty?
|
423
|
-
end
|
424
|
-
|
425
|
-
# Write the resulting hash to the final file in the docs directory
|
426
|
-
filename = File.join(@doc_root_dir, version, "templates.json")
|
427
|
-
dirname = File.dirname(filename)
|
428
|
-
FileUtils.mkdir_p dirname unless File.exists? dirname
|
429
|
-
File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(templates))}
|
430
|
-
return templates
|
431
|
-
end
|
432
|
-
private
|
433
|
-
|
434
|
-
def remove_previous_doc_data
|
435
|
-
FileUtils.rm_rf @doc_root_dir if File.exists?(@doc_root_dir)
|
436
|
-
end
|
437
|
-
|
438
|
-
end
|
439
|
-
end
|