brainstem 1.0.0.pre.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +383 -32
  5. data/bin/brainstem +6 -0
  6. data/brainstem.gemspec +2 -0
  7. data/docs/api_doc_generator.markdown +175 -0
  8. data/docs/brainstem_executable.markdown +32 -0
  9. data/docs/docgen.png +0 -0
  10. data/docs/docgen_ascii.txt +63 -0
  11. data/docs/executable.png +0 -0
  12. data/docs/executable_ascii.txt +10 -0
  13. data/lib/brainstem/api_docs.rb +146 -0
  14. data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
  15. data/lib/brainstem/api_docs/atlas.rb +158 -0
  16. data/lib/brainstem/api_docs/builder.rb +167 -0
  17. data/lib/brainstem/api_docs/controller.rb +122 -0
  18. data/lib/brainstem/api_docs/controller_collection.rb +40 -0
  19. data/lib/brainstem/api_docs/endpoint.rb +234 -0
  20. data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
  21. data/lib/brainstem/api_docs/exceptions.rb +8 -0
  22. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
  23. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
  24. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
  25. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
  26. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
  27. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
  28. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
  29. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
  30. data/lib/brainstem/api_docs/presenter.rb +225 -0
  31. data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
  32. data/lib/brainstem/api_docs/resolver.rb +73 -0
  33. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
  34. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
  35. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
  36. data/lib/brainstem/cli.rb +146 -0
  37. data/lib/brainstem/cli/abstract_command.rb +97 -0
  38. data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
  39. data/lib/brainstem/concerns/controller_dsl.rb +300 -0
  40. data/lib/brainstem/concerns/controller_param_management.rb +30 -9
  41. data/lib/brainstem/concerns/formattable.rb +38 -0
  42. data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
  43. data/lib/brainstem/concerns/optional.rb +43 -0
  44. data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
  45. data/lib/brainstem/controller_methods.rb +6 -3
  46. data/lib/brainstem/dsl/association.rb +6 -3
  47. data/lib/brainstem/dsl/associations_block.rb +6 -3
  48. data/lib/brainstem/dsl/base_block.rb +2 -4
  49. data/lib/brainstem/dsl/conditional.rb +7 -3
  50. data/lib/brainstem/dsl/conditionals_block.rb +4 -4
  51. data/lib/brainstem/dsl/configuration.rb +184 -8
  52. data/lib/brainstem/dsl/field.rb +6 -3
  53. data/lib/brainstem/dsl/fields_block.rb +2 -3
  54. data/lib/brainstem/help_text.txt +8 -0
  55. data/lib/brainstem/presenter.rb +27 -6
  56. data/lib/brainstem/presenter_validator.rb +5 -2
  57. data/lib/brainstem/time_classes.rb +1 -1
  58. data/lib/brainstem/version.rb +1 -1
  59. data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
  60. data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
  61. data/spec/brainstem/api_docs/builder_spec.rb +100 -0
  62. data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
  63. data/spec/brainstem/api_docs/controller_spec.rb +225 -0
  64. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
  65. data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
  66. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
  67. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
  68. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
  69. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
  70. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
  71. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
  72. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
  73. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
  74. data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
  75. data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
  76. data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
  77. data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
  78. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
  79. data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
  80. data/spec/brainstem/api_docs_spec.rb +58 -0
  81. data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
  82. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
  83. data/spec/brainstem/cli_spec.rb +67 -0
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
  85. data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
  86. data/spec/brainstem/concerns/formattable_spec.rb +30 -0
  87. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
  88. data/spec/brainstem/concerns/optional_spec.rb +48 -0
  89. data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
  90. data/spec/brainstem/dsl/association_spec.rb +18 -2
  91. data/spec/brainstem/dsl/conditional_spec.rb +25 -2
  92. data/spec/brainstem/dsl/configuration_spec.rb +1 -1
  93. data/spec/brainstem/dsl/field_spec.rb +18 -2
  94. data/spec/brainstem/presenter_collection_spec.rb +10 -2
  95. data/spec/brainstem/presenter_spec.rb +32 -0
  96. data/spec/brainstem/presenter_validator_spec.rb +12 -7
  97. data/spec/dummy/rails.rb +49 -0
  98. data/spec/shared/atlas_taker.rb +18 -0
  99. data/spec/shared/formattable.rb +14 -0
  100. data/spec/spec_helper.rb +2 -0
  101. data/spec/spec_helpers/db.rb +1 -1
  102. data/spec/spec_helpers/presenters.rb +20 -14
  103. metadata +106 -6
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/builder'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe Builder do
7
+ let(:dummy_environment_file) do
8
+ File.expand_path('../../../../spec/dummy/rails.rb', __FILE__)
9
+ end
10
+
11
+ let(:default_options) do
12
+ { args_for_introspector: { rails_environment_file: dummy_environment_file } }
13
+ end
14
+
15
+ let(:options) { {} }
16
+
17
+ subject { Builder.new(default_options.merge(options)) }
18
+
19
+
20
+ describe "#initialize" do
21
+ let(:introspector_method) { Object.new }
22
+ let(:options) { { introspector_method: introspector_method } }
23
+
24
+ before do
25
+ any_instance_of(Builder) do |instance|
26
+ stub(instance) do |k|
27
+ k.build_introspector!
28
+ k.build_atlas!
29
+ end
30
+ end
31
+ end
32
+
33
+ it "sets all valid values given to it as options" do
34
+ expect(subject.introspector_method).to eq introspector_method
35
+ end
36
+ end
37
+
38
+
39
+ describe "introspection" do
40
+ let(:introspector_method) { Object.new }
41
+ let(:args_for_introspector) { { blah: true } }
42
+ let(:introspector) { Object.new }
43
+ let(:options) do
44
+ {
45
+ args_for_introspector: args_for_introspector,
46
+ introspector_method: introspector_method
47
+ }
48
+ end
49
+
50
+ before do
51
+ any_instance_of(Builder) do |instance|
52
+ stub(instance) do |k|
53
+ k.build_atlas!
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ it "passes the introspector method the introspector options" do
60
+ mock(introspector_method).call(args_for_introspector)
61
+ subject
62
+ end
63
+
64
+ it "creates an introspector" do
65
+ stub(introspector_method).call(args_for_introspector) { introspector }
66
+ expect(subject.introspector).to eq introspector
67
+ end
68
+ end
69
+
70
+
71
+ describe "modeling" do
72
+ let(:introspector) { Object.new }
73
+ let(:atlas_method) { Object.new }
74
+ let(:args_for_atlas) { { blah: true } }
75
+ let(:atlas) { Object.new }
76
+ let(:options) { { atlas_method: atlas_method, args_for_atlas: args_for_atlas } }
77
+
78
+ before do
79
+ any_instance_of(Builder) do |instance|
80
+ stub(instance) do |inst|
81
+ inst.introspector { introspector }
82
+ inst.build_introspector!
83
+ inst.build_formatter!
84
+ end
85
+ end
86
+ end
87
+
88
+ it "passes the atlas method the introspector and the atlas options" do
89
+ mock(atlas_method).call(introspector, args_for_atlas)
90
+ subject
91
+ end
92
+
93
+ it "creates an atlas" do
94
+ mock(atlas_method).call(introspector, args_for_atlas) { atlas }
95
+ expect(subject.atlas).to eq atlas
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/controller_collection'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe ControllerCollection do
7
+ let(:controller) { Object.new }
8
+ let(:atlas) { Object.new }
9
+ let(:options) { {} }
10
+
11
+ subject { described_class.new(atlas, options) }
12
+
13
+ describe "#find_by_route" do
14
+ before do
15
+ stub(controller) do |c|
16
+ c.path { "/posts" }
17
+ c.const { Object }
18
+ c.action { "index" }
19
+ end
20
+
21
+ subject << controller
22
+ end
23
+
24
+ context "when matches route" do
25
+ it "returns the matching controller" do
26
+ route = { controller: Object }
27
+ expect(subject.find_by_route(route)).to eq controller
28
+ end
29
+ end
30
+
31
+ context "when does not match route" do
32
+ it "returns nil" do
33
+ route = { controller: TrueClass }
34
+ expect(subject.find_by_route(route)).to eq nil
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ describe "#create_from_route" do
41
+ it "creates a new controller, adding it to the members" do
42
+ controller = subject.create_from_route(
43
+ path: "/posts",
44
+ controller: Object,
45
+ action: "index",
46
+ controller_name: "object"
47
+ )
48
+
49
+ expect(subject.first).to eq controller
50
+ expect(controller.const).to eq Object
51
+ expect(controller.name).to eq "object"
52
+ expect(controller.endpoints).to \
53
+ be_a Brainstem::ApiDocs::EndpointCollection
54
+ expect(controller.endpoints.count).to eq 0
55
+ end
56
+ end
57
+
58
+
59
+ describe "#find_or_create_from_route" do
60
+ let!(:existing_controller) { subject.create_from_route(
61
+ path: "/posts",
62
+ controller: Object,
63
+ action: "index",
64
+ controller_name: "object"
65
+ ) }
66
+
67
+ context "when has matching controller" do
68
+ let(:route) { { controller: Object, controller_name: "object" } }
69
+
70
+ it "returns that controller" do
71
+ subject.find_or_create_from_route(route)
72
+ expect(subject.count).to eq 1
73
+ expect(subject.last).to eq existing_controller
74
+ end
75
+ end
76
+
77
+ context "when no matching controller" do
78
+ let(:route) { { controller: TrueClass, controller_name: "true_class" } }
79
+
80
+ it "returns a new controller" do
81
+ new_controller = subject.find_or_create_from_route(route)
82
+ expect(subject.count).to eq 2
83
+ expect(subject.last).to eq new_controller
84
+ end
85
+ end
86
+ end
87
+
88
+ it_behaves_like "formattable"
89
+ it_behaves_like "atlas taker"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/controller'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe Controller do
7
+ subject { described_class.new(atlas, options) }
8
+ let(:atlas) { Object.new }
9
+ let(:options) { {} }
10
+
11
+ describe "#initialize" do
12
+ it "yields self if given a block" do
13
+ block = Proc.new { |s| s.name = "bork bork" }
14
+ expect(described_class.new(atlas, &block).name).to eq "bork bork"
15
+ end
16
+ end
17
+
18
+
19
+ describe "#add_endpoint" do
20
+ let(:endpoint) { Object.new }
21
+
22
+ it "adds the endpoint to its list" do
23
+ expect(subject.endpoints.count).to eq 0
24
+ subject.add_endpoint(endpoint)
25
+ expect(subject.endpoints.count).to eq 1
26
+ end
27
+ end
28
+
29
+
30
+ describe "derived fields" do
31
+ let(:lorem) { "lorem ipsum dolor sit amet" }
32
+ let(:const) { Object.new }
33
+ let(:default_config) { {} }
34
+ let(:show_config) { {} }
35
+ let(:nodoc) { false }
36
+ let(:options) { { const: const } }
37
+
38
+ before do
39
+ stub(const) do |constant|
40
+ constant.configuration { {
41
+ :_default => default_config,
42
+ :show => show_config
43
+ } }
44
+
45
+ constant.to_s { "Api::V1::ClassName" }
46
+ end
47
+ end
48
+
49
+
50
+ describe "configuration helpers" do
51
+ describe "#contextual_documentation" do
52
+ let(:default_config) { { title: { info: info, nodoc: nodoc } } }
53
+ let(:info) { lorem }
54
+
55
+ context "when has the key" do
56
+ let(:key) { :title }
57
+
58
+ context "when not nodoc" do
59
+ context "when has info" do
60
+ it "is truthy" do
61
+ expect(subject.contextual_documentation(key)).to be_truthy
62
+ end
63
+
64
+ it "is the info" do
65
+ expect(subject.contextual_documentation(key)).to eq lorem
66
+ end
67
+ end
68
+
69
+ context "when has no info" do
70
+ let(:info) { nil }
71
+
72
+ it "is falsey" do
73
+ expect(subject.contextual_documentation(key)).to be_falsey
74
+ end
75
+ end
76
+ end
77
+
78
+ context "when nodoc" do
79
+ let(:nodoc) { true }
80
+
81
+ it "is falsey" do
82
+ expect(subject.contextual_documentation(key)).to be_falsey
83
+ end
84
+ end
85
+ end
86
+
87
+ context "when doesn't have the key" do
88
+ let(:key) { :herp }
89
+
90
+ it "is falsey" do
91
+ expect(subject.contextual_documentation(key)).to be_falsey
92
+ end
93
+ end
94
+ end
95
+
96
+
97
+ describe "#default_configuration" do
98
+ let(:default_config) { { title: nil } }
99
+
100
+ it "returns the default key of the configuration" do
101
+ expect(subject.default_configuration).to eq default_config
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ describe "#nodoc?" do
108
+ let(:default_config) { { nodoc: nodoc } }
109
+
110
+ context "when nodoc in default" do
111
+ let(:nodoc) { true }
112
+
113
+ it "is true" do
114
+ expect(subject.nodoc?).to eq true
115
+ end
116
+ end
117
+
118
+ context "when not nodoc in default" do
119
+ it "is false" do
120
+ expect(subject.nodoc?).to eq false
121
+ end
122
+ end
123
+ end
124
+
125
+
126
+ describe "#title" do
127
+ context "when present" do
128
+ let(:default_config) { { title: { info: lorem, nodoc: nodoc } } }
129
+
130
+ context "when nodoc" do
131
+ let(:nodoc) { true }
132
+
133
+ it "falls back to the controller class" do
134
+ expect(subject.title).to eq "ClassName"
135
+ end
136
+ end
137
+
138
+ context "when documentable" do
139
+ it "shows the title" do
140
+ expect(subject.title).to eq lorem
141
+ end
142
+ end
143
+ end
144
+
145
+ context "when absent" do
146
+ it "falls back to the controller class" do
147
+ expect(subject.title).to eq "ClassName"
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+ describe "#description" do
154
+ context "when present" do
155
+ let(:default_config) { { description: { info: lorem, nodoc: nodoc } } }
156
+
157
+ context "when nodoc" do
158
+ let(:nodoc) { true }
159
+
160
+ it "shows nothing" do
161
+ expect(subject.description).to eq ""
162
+ end
163
+ end
164
+
165
+ context "when documentable" do
166
+ it "shows the description" do
167
+ expect(subject.description).to eq lorem
168
+ end
169
+ end
170
+ end
171
+
172
+ context "when absent" do
173
+ it "shows nothing" do
174
+ expect(subject.description).to eq ""
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+
181
+ describe "#suggested_filename" do
182
+ let(:const) { Object.new }
183
+
184
+ before do
185
+ stub(const) do |constant|
186
+ constant.to_s { "Api::V1::ClassName" }
187
+ end
188
+ end
189
+
190
+
191
+ it "gsubs namespace, filename and extension" do
192
+ instance = described_class.new(atlas,
193
+ filename_pattern: "controllers/{{namespace}}/{{name}}_controller.{{extension}}",
194
+ name: 'api/v1/abc',
195
+ const: const,
196
+ )
197
+
198
+ stub(instance).extension { "xyz" }
199
+
200
+ expect(instance.suggested_filename(:xyz)).to \
201
+ eq "controllers/api/v1/abc_controller.xyz"
202
+ end
203
+ end
204
+
205
+
206
+ describe "#suggested_filename_link" do
207
+ it "gsubs filename and extension" do
208
+
209
+ instance = described_class.new(atlas,
210
+ filename_link_pattern: "controllers/{{name}}_controller.{{extension}}.foo",
211
+ name: 'abc'
212
+ )
213
+
214
+ stub(instance).extension { "xyz" }
215
+
216
+ expect(instance.suggested_filename_link(:xyz)).to eq "controllers/abc_controller.xyz.foo"
217
+ end
218
+ end
219
+
220
+
221
+ it_behaves_like "formattable"
222
+ it_behaves_like "atlas taker"
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/endpoint_collection'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe EndpointCollection do
7
+ let(:atlas) { Object.new }
8
+ let(:options) { {} }
9
+ let(:endpoint) { Object.new }
10
+
11
+ subject { described_class.new(atlas, options) }
12
+
13
+
14
+ describe "#find_from_route" do
15
+ let(:controller) { stub!.const { Object }.subject }
16
+
17
+ before do
18
+ stub(endpoint) do |c|
19
+ c.path { "/posts" }
20
+ c.http_methods { ["GET", "POST"] }
21
+ c.controller { controller }
22
+ c.controller_name { "object" }
23
+ c.action { "index" }
24
+ end
25
+
26
+ subject << endpoint
27
+ end
28
+
29
+ context "when matches route" do
30
+ it "returns the matching controller" do
31
+ route = { controller: Object, path: "/posts", action: "index" }
32
+ expect(subject.find_by_route(route)).to eq endpoint
33
+ end
34
+ end
35
+
36
+ context "when does not match route" do
37
+ it "returns nil" do
38
+ routes = [
39
+ { controller: TrueClass, path: "/posts", action: "index" },
40
+ { controller: Object, path: "/wrong", action: "index" },
41
+ { controller: Object, path: "/posts", action: "wrong" },
42
+ ]
43
+
44
+ routes.each do |route|
45
+ expect(subject.find_by_route(route)).to eq nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+
52
+ describe "#create_from_route" do
53
+ it "creates a new endpoint, adding it to the members" do
54
+ controller = Object.new
55
+
56
+ endpoint = subject.create_from_route({
57
+ path: "/posts",
58
+ http_methods: ["GET"],
59
+ controller_name: "object",
60
+ action: "index",
61
+ }, controller)
62
+
63
+ expect(subject.first).to eq endpoint
64
+ expect(endpoint.controller).to eq controller
65
+ expect(endpoint.controller_name).to eq "object"
66
+ expect(endpoint.action).to eq "index"
67
+ expect(endpoint.path).to eq "/posts"
68
+ expect(endpoint.http_methods).to eq ["GET"]
69
+ end
70
+ end
71
+
72
+
73
+ describe "#only_documentable" do
74
+ let(:endpoint_2) { Object.new }
75
+
76
+ before do
77
+ stub(endpoint).nodoc? { false }
78
+ stub(endpoint_2).nodoc? { true }
79
+
80
+ subject << endpoint
81
+ subject << endpoint_2
82
+ end
83
+
84
+ it "returns a new collection with members that are not nodoc" do
85
+ new_clxn = subject.only_documentable
86
+
87
+ expect(new_clxn).to be_a described_class
88
+ expect(new_clxn).to include endpoint
89
+ expect(new_clxn).not_to include endpoint_2
90
+ end
91
+ end
92
+
93
+
94
+ describe "#with_declared_presented_class" do
95
+ let(:endpoint_2) { Object.new }
96
+
97
+ before do
98
+ stub(endpoint).declared_presented_class { Class.new }
99
+ stub(endpoint_2).declared_presented_class { nil }
100
+ subject << endpoint
101
+ subject << endpoint_2
102
+ end
103
+
104
+ it "returns a new collection with members that have declared presents" do
105
+ new_clxn = subject.with_declared_presented_class
106
+
107
+ expect(new_clxn).to be_a described_class
108
+ expect(new_clxn).to include endpoint
109
+ expect(new_clxn).not_to include endpoint_2
110
+ end
111
+ end
112
+
113
+
114
+ describe "#with_actions_in_controller" do
115
+ let(:endpoint_2) { Object.new }
116
+ let(:const) do
117
+ Class.new do
118
+ def show
119
+ end
120
+ end
121
+ end
122
+
123
+ before do
124
+ stub(endpoint).action { :show }
125
+ stub(endpoint_2).action { :index }
126
+ subject << endpoint
127
+ subject << endpoint_2
128
+ end
129
+
130
+ it "returns a new collection with members that the const responds to" do
131
+ new_clxn = subject.with_actions_in_controller(const)
132
+
133
+ expect(new_clxn).to be_a described_class
134
+ expect(new_clxn).to include endpoint
135
+ expect(new_clxn).not_to include endpoint_2
136
+ end
137
+ end
138
+
139
+
140
+ it_behaves_like "formattable"
141
+ it_behaves_like "atlas taker"
142
+ end
143
+ end
144
+ end