app_archetype 1.2.3

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