app_archetype 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.doxie.json +25 -0
  3. data/.github/workflows/build.yml +25 -0
  4. data/.gitignore +22 -0
  5. data/.rubocop.yml +35 -0
  6. data/.ruby-version +1 -0
  7. data/CONTRIBUTING.md +51 -0
  8. data/Gemfile +3 -0
  9. data/Gemfile.lock +172 -0
  10. data/LICENSE +21 -0
  11. data/README.md +138 -0
  12. data/Rakefile +19 -0
  13. data/app_archetype.gemspec +39 -0
  14. data/bin/archetype +20 -0
  15. data/lib/app_archetype.rb +14 -0
  16. data/lib/app_archetype/cli.rb +204 -0
  17. data/lib/app_archetype/cli/presenters.rb +106 -0
  18. data/lib/app_archetype/cli/prompts.rb +152 -0
  19. data/lib/app_archetype/generators.rb +95 -0
  20. data/lib/app_archetype/logger.rb +69 -0
  21. data/lib/app_archetype/renderer.rb +116 -0
  22. data/lib/app_archetype/template.rb +12 -0
  23. data/lib/app_archetype/template/helpers.rb +216 -0
  24. data/lib/app_archetype/template/manifest.rb +193 -0
  25. data/lib/app_archetype/template/plan.rb +172 -0
  26. data/lib/app_archetype/template/source.rb +39 -0
  27. data/lib/app_archetype/template/variable.rb +237 -0
  28. data/lib/app_archetype/template/variable_manager.rb +75 -0
  29. data/lib/app_archetype/template_manager.rb +113 -0
  30. data/lib/app_archetype/version.rb +6 -0
  31. data/lib/core_ext/string.rb +67 -0
  32. data/spec/app_archetype/cli/presenters_spec.rb +99 -0
  33. data/spec/app_archetype/cli/prompts_spec.rb +292 -0
  34. data/spec/app_archetype/cli_spec.rb +132 -0
  35. data/spec/app_archetype/generators_spec.rb +119 -0
  36. data/spec/app_archetype/logger_spec.rb +86 -0
  37. data/spec/app_archetype/renderer_spec.rb +291 -0
  38. data/spec/app_archetype/template/helpers_spec.rb +251 -0
  39. data/spec/app_archetype/template/manifest_spec.rb +245 -0
  40. data/spec/app_archetype/template/plan_spec.rb +191 -0
  41. data/spec/app_archetype/template/source_spec.rb +60 -0
  42. data/spec/app_archetype/template/variable_manager_spec.rb +103 -0
  43. data/spec/app_archetype/template/variable_spec.rb +245 -0
  44. data/spec/app_archetype/template_manager_spec.rb +221 -0
  45. data/spec/core_ext/string_spec.rb +143 -0
  46. data/spec/spec_helper.rb +29 -0
  47. metadata +370 -0
@@ -0,0 +1,251 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::Template::Helpers do
4
+ subject { Object.new.extend(described_class) }
5
+
6
+ describe '#dot' do
7
+ it 'returns an empty string' do
8
+ expect(subject.dot).to eq ''
9
+ end
10
+ end
11
+
12
+ describe '#this_year' do
13
+ let(:time) { Time.new(2002, 10, 31, 2, 2, 2, 0) }
14
+
15
+ before do
16
+ allow(Time).to receive(:now)
17
+ .and_return(time)
18
+ end
19
+
20
+ it 'returns year' do
21
+ expect(subject.this_year).to eq '2002'
22
+ end
23
+ end
24
+
25
+ describe '#timestamp_now' do
26
+ let(:time) { Time.new(2002, 10, 31, 2, 2, 2, 0) }
27
+
28
+ before do
29
+ allow(Time).to receive(:now)
30
+ .and_return(time)
31
+ end
32
+
33
+ it 'returns timestamp' do
34
+ expect(subject.timestamp_now).to eq '20021031020202000'
35
+ end
36
+ end
37
+
38
+ describe '#timestamp_utc_now' do
39
+ let(:time) { Time.new(2002, 10, 31, 2, 2, 2, 0) }
40
+
41
+ before do
42
+ allow(Time).to receive(:now)
43
+ .and_return(time)
44
+ end
45
+
46
+ it 'returns timestamp' do
47
+ expect(subject.timestamp_utc_now).to eq '20021031020202000'
48
+ end
49
+ end
50
+
51
+ describe '#random_string' do
52
+ let(:length) { '10' }
53
+
54
+ before do
55
+ allow(Random).to receive(:rand).and_return 1
56
+ @string = subject.random_string(length)
57
+ end
58
+
59
+ it 'returns random string of specified length' do
60
+ expect(@string).to eq 'bbbbbbbbbb'
61
+ end
62
+ end
63
+
64
+ describe '#randomize' do
65
+ let(:hex) { '922665344123e551ca40bc4f16ac8e17' }
66
+ let(:str) { 'a_type_of_thing' }
67
+
68
+ before do
69
+ allow(SecureRandom).to receive(:hex).and_return(hex)
70
+ end
71
+
72
+ it 'adds 5 characters from generated hex on the end of the string' do
73
+ expect(subject.randomize(str)).to eq 'a_type_of_thing_c8e17'
74
+ end
75
+
76
+ context 'when size is specified' do
77
+ it 'adds specified number characters from hex on the end of the string' do
78
+ expect(subject.randomize(str, '3')).to eq 'a_type_of_thing_e17'
79
+ end
80
+ end
81
+
82
+ context 'when specified size is not an integer' do
83
+ it 'raises an error' do
84
+ expect do
85
+ subject.randomize(str, 'abc')
86
+ end.to raise_error(RuntimeError, 'size must be an integer')
87
+ end
88
+ end
89
+
90
+ context 'when size exceeds 32' do
91
+ it 'raises an error' do
92
+ expect do
93
+ subject.randomize(str, '33')
94
+ end.to raise_error(
95
+ RuntimeError,
96
+ 'randomize supports up to 32 characters'
97
+ )
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#upcase' do
103
+ let(:string) { 'aAaAa' }
104
+
105
+ it 'converts string to upper case' do
106
+ expect(subject.upcase(string)).to eq 'AAAAA'
107
+ end
108
+ end
109
+
110
+ describe '#downcase' do
111
+ let(:string) { 'aAaAa' }
112
+
113
+ it 'converts string to lower case' do
114
+ expect(subject.downcase(string)).to eq 'aaaaa'
115
+ end
116
+ end
117
+
118
+ describe '#join' do
119
+ it 'joins given strings together by delimiter' do
120
+ expect(subject.join('|', 'a', 'b', 'c'))
121
+ .to eq 'a|b|c'
122
+ end
123
+ end
124
+
125
+ describe '#snake_case' do
126
+ let(:str) { 'MyProject' }
127
+
128
+ it 'snake cases string' do
129
+ expect(subject.snake_case(str))
130
+ .to eq 'my_project'
131
+ end
132
+
133
+ context 'when given a title case string' do
134
+ let(:str) { 'My Project' }
135
+
136
+ it 'snake cases string' do
137
+ expect(subject.snake_case(str))
138
+ .to eq 'my_project'
139
+ end
140
+ end
141
+
142
+ context 'when given a dash case string' do
143
+ let(:str) { 'My-Project' }
144
+
145
+ it 'snake cases string' do
146
+ expect(subject.snake_case(str))
147
+ .to eq 'my_project'
148
+ end
149
+ end
150
+
151
+ context 'when given a snake case string' do
152
+ let(:str) { 'my_project' }
153
+
154
+ it 'returns string as is' do
155
+ expect(subject.snake_case(str)).to eq str
156
+ end
157
+ end
158
+ end
159
+
160
+ describe '#dash_case' do
161
+ let(:str) { 'my Project' }
162
+
163
+ it 'dash cases string' do
164
+ expect(subject.dash_case(str))
165
+ .to eq 'my-project'
166
+ end
167
+
168
+ context 'when given a title case string' do
169
+ let(:str) { 'My Project' }
170
+
171
+ it 'dash cases string' do
172
+ expect(subject.dash_case(str))
173
+ .to eq 'my-project'
174
+ end
175
+ end
176
+
177
+ context 'when given a snake case string' do
178
+ let(:str) { 'my_project' }
179
+
180
+ it 'dash cases string' do
181
+ expect(subject.dash_case(str))
182
+ .to eq 'my-project'
183
+ end
184
+ end
185
+
186
+ context 'when given a dash case string' do
187
+ let(:str) { 'my-project' }
188
+
189
+ it 'returns string as is' do
190
+ expect(subject.dash_case(str)).to eq str
191
+ end
192
+ end
193
+ end
194
+
195
+ describe '#camel_case' do
196
+ let(:str) { 'My Project' }
197
+
198
+ it 'camel cases string' do
199
+ expect(subject.camel_case(str))
200
+ .to eq 'MyProject'
201
+ end
202
+
203
+ context 'when given a camel case string' do
204
+ let(:str) { 'MyProject' }
205
+
206
+ it 'returns string as is' do
207
+ expect(subject.camel_case(str)).to eq str
208
+ end
209
+ end
210
+ end
211
+
212
+ describe '#snake_to_camel' do
213
+ let(:input) { 'a_type_of_thing' }
214
+
215
+ it 'camelizes the given string' do
216
+ expect(subject.snake_to_camel(input)).to eq 'ATypeOfThing'
217
+ end
218
+ end
219
+
220
+ describe '#pluralize' do
221
+ let(:input) { 'Apple' }
222
+
223
+ it 'pluralizes the given string' do
224
+ expect(subject.pluralize(input)).to eq 'Apples'
225
+ end
226
+
227
+ context 'when string ends in y' do
228
+ let(:input) { 'Quantity' }
229
+
230
+ it 'pluralizes the given string' do
231
+ expect(subject.pluralize(input)).to eq 'Quantities'
232
+ end
233
+ end
234
+ end
235
+
236
+ describe '#singularize' do
237
+ let(:input) { 'Bananas' }
238
+
239
+ it 'singularizes the given string' do
240
+ expect(subject.singularize(input)).to eq 'Banana'
241
+ end
242
+
243
+ context 'when string ends in ies' do
244
+ let(:input) { 'Quantities' }
245
+
246
+ it 'singularizes the given string' do
247
+ expect(subject.singularize(input)).to eq 'Quantity'
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,245 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::Template::Manifest do
4
+ describe '.new_from_file' do
5
+ let(:file_path) { 'path/to/manifest' }
6
+ let(:exist) { true }
7
+ let(:manifest_name) { 'manifest name' }
8
+
9
+ let(:app_archetype_meta) do
10
+ {
11
+ 'app_archetype' => {
12
+ 'version' => AppArchetype::VERSION
13
+ }
14
+ }
15
+ end
16
+
17
+ let(:version) { '0.0.1' }
18
+ let(:vars) do
19
+ {
20
+ 'foo' => {
21
+ 'description' => 'a foo',
22
+ 'default' => 'bar'
23
+ },
24
+ 'bar' => {
25
+ 'description' => 'a bar',
26
+ 'default' => 'foo'
27
+ }
28
+ }
29
+ end
30
+ let(:content) do
31
+ {
32
+ 'name' => manifest_name,
33
+ 'version' => version,
34
+ 'metadata' => app_archetype_meta,
35
+ 'variables' => vars
36
+ }.to_json
37
+ end
38
+
39
+ before do
40
+ allow(File).to receive(:exist?).and_return(exist)
41
+ allow(File).to receive(:read).and_return(content)
42
+ allow(Jsonnet).to receive(:evaluate).and_call_original
43
+ end
44
+
45
+ context 'when file exists' do
46
+ before do
47
+ @parsed = described_class.new_from_file(file_path)
48
+ end
49
+
50
+ it 'reads file' do
51
+ expect(File).to have_received(:read).with(file_path)
52
+ end
53
+
54
+ it 'parses json' do
55
+ expect(Jsonnet).to have_received(:evaluate).with(content)
56
+ end
57
+
58
+ it 'returns manifest' do
59
+ expect(@parsed).to be_a AppArchetype::Template::Manifest
60
+ end
61
+
62
+ it 'has version' do
63
+ expect(@parsed.version).to eq version
64
+ end
65
+
66
+ it 'has variables' do
67
+ expect(@parsed.variables)
68
+ .to be_a AppArchetype::Template::VariableManager
69
+ end
70
+ end
71
+
72
+ context 'when manifest is from a later version of app archetype' do
73
+ let(:app_archetype_meta) do
74
+ {
75
+ 'app_archetype' => {
76
+ 'version' => '999.999.999'
77
+ }
78
+ }
79
+ end
80
+
81
+ it 'raises incompatibility error' do
82
+ expect do
83
+ described_class.new_from_file(file_path)
84
+ end.to raise_error 'provided manifest is invalid or incompatible with '\
85
+ 'this version of app archetype'
86
+ end
87
+ end
88
+
89
+ context 'when manifest is from an incompatible version of app archetype' do
90
+ let(:app_archetype_meta) do
91
+ {
92
+ 'app_archetype' => {
93
+ 'version' => '0.9.9'
94
+ }
95
+ }
96
+ end
97
+
98
+ it 'raises incompatibility error' do
99
+ expect do
100
+ described_class.new_from_file(file_path)
101
+ end.to raise_error 'provided manifest is invalid or incompatible with '\
102
+ 'this version of app archetype'
103
+ end
104
+ end
105
+
106
+ context 'when app_archetype metadata is missing' do
107
+ let(:app_archetype_meta) { {} }
108
+ it 'raises incompatibility error' do
109
+ expect do
110
+ described_class.new_from_file(file_path)
111
+ end.to raise_error 'provided manifest is invalid or incompatible with '\
112
+ 'this version of app archetype'
113
+ end
114
+ end
115
+ end
116
+
117
+ subject { described_class.new(path, data) }
118
+
119
+ describe '#name' do
120
+ let(:path) { 'path/to/manifest.json' }
121
+
122
+ let(:data) do
123
+ {
124
+ 'name' => 'test_manifest',
125
+ 'version' => '0.1.0',
126
+ 'variables' => {}
127
+ }
128
+ end
129
+
130
+ it 'returns name' do
131
+ expect(subject.name).to eq 'test_manifest'
132
+ end
133
+ end
134
+
135
+ describe '#version' do
136
+ let(:path) { 'path/to/manifest.json' }
137
+
138
+ let(:data) do
139
+ {
140
+ 'name' => 'test_manifest',
141
+ 'version' => '0.1.0',
142
+ 'variables' => {}
143
+ }
144
+ end
145
+
146
+ it 'returns version' do
147
+ expect(subject.version).to eq '0.1.0'
148
+ end
149
+ end
150
+
151
+ describe '#metadata' do
152
+ let(:path) { 'path/to/manifest.json' }
153
+ let(:meta) { { 'foo' => 'bar' } }
154
+
155
+ let(:data) do
156
+ {
157
+ 'name' => 'test_manifest',
158
+ 'version' => '0.1.0',
159
+ 'metadata' => meta,
160
+ 'variables' => {}
161
+ }
162
+ end
163
+
164
+ it 'returns metadata' do
165
+ expect(subject.metadata).to eq meta
166
+ end
167
+ end
168
+
169
+ describe '#template' do
170
+ let(:path) { 'path/to/manifest.json' }
171
+
172
+ let(:data) do
173
+ {
174
+ 'name' => 'test_manifest',
175
+ 'version' => '0.1.0',
176
+ 'variables' => {}
177
+ }
178
+ end
179
+
180
+ let(:exist) { true }
181
+ let(:template) { double(AppArchetype::Template::Source) }
182
+
183
+ before do
184
+ allow(File).to receive(:exist?).and_return(exist)
185
+ end
186
+
187
+ it 'loads template adjacent to manifest' do
188
+ expect(subject.template.path).to eq('path/to/template')
189
+ end
190
+
191
+ context 'when template files do not exist' do
192
+ let(:exist) { false }
193
+
194
+ it 'raises cannot find template error' do
195
+ expect { subject.template }.to raise_error(
196
+ RuntimeError,
197
+ 'cannot find template for manifest test_manifest'
198
+ )
199
+ end
200
+ end
201
+ end
202
+
203
+ describe 'validate' do
204
+ let(:path) { 'path/to/manifest.json' }
205
+ let(:data) { {} }
206
+ let(:result) { [] }
207
+
208
+ before do
209
+ allow(JSON::Validator).to receive(:fully_validate).and_return(result)
210
+ subject.validate
211
+ end
212
+
213
+ it 'runs json schema validation with manifest schema' do
214
+ expect(JSON::Validator).to have_received(:fully_validate)
215
+ .with(
216
+ AppArchetype::Template::Manifest::SCHEMA,
217
+ data.to_json,
218
+ strict: true
219
+ )
220
+ end
221
+ end
222
+
223
+ describe '#valid?' do
224
+ let(:path) { 'path/to/manifest.json' }
225
+ let(:data) { {} }
226
+ let(:validation_results) { [] }
227
+
228
+ before do
229
+ allow(subject).to receive(:validate).and_return(validation_results)
230
+ end
231
+
232
+ context 'when manifest is valid' do
233
+ it 'returns true' do
234
+ expect(subject.valid?).to be true
235
+ end
236
+ end
237
+
238
+ context 'when manifest is invalid' do
239
+ let(:validation_results) { ['there was a validation error'] }
240
+ it 'returns true' do
241
+ expect(subject.valid?).to be false
242
+ end
243
+ end
244
+ end
245
+ end