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.
- checksums.yaml +7 -0
- data/.doxie.json +25 -0
- data/.github/workflows/build.yml +25 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +35 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +51 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +172 -0
- data/LICENSE +21 -0
- data/README.md +138 -0
- data/Rakefile +19 -0
- data/app_archetype.gemspec +39 -0
- data/bin/archetype +20 -0
- data/lib/app_archetype.rb +14 -0
- data/lib/app_archetype/cli.rb +204 -0
- data/lib/app_archetype/cli/presenters.rb +106 -0
- data/lib/app_archetype/cli/prompts.rb +152 -0
- data/lib/app_archetype/generators.rb +95 -0
- data/lib/app_archetype/logger.rb +69 -0
- data/lib/app_archetype/renderer.rb +116 -0
- data/lib/app_archetype/template.rb +12 -0
- data/lib/app_archetype/template/helpers.rb +216 -0
- data/lib/app_archetype/template/manifest.rb +193 -0
- data/lib/app_archetype/template/plan.rb +172 -0
- data/lib/app_archetype/template/source.rb +39 -0
- data/lib/app_archetype/template/variable.rb +237 -0
- data/lib/app_archetype/template/variable_manager.rb +75 -0
- data/lib/app_archetype/template_manager.rb +113 -0
- data/lib/app_archetype/version.rb +6 -0
- data/lib/core_ext/string.rb +67 -0
- data/spec/app_archetype/cli/presenters_spec.rb +99 -0
- data/spec/app_archetype/cli/prompts_spec.rb +292 -0
- data/spec/app_archetype/cli_spec.rb +132 -0
- data/spec/app_archetype/generators_spec.rb +119 -0
- data/spec/app_archetype/logger_spec.rb +86 -0
- data/spec/app_archetype/renderer_spec.rb +291 -0
- data/spec/app_archetype/template/helpers_spec.rb +251 -0
- data/spec/app_archetype/template/manifest_spec.rb +245 -0
- data/spec/app_archetype/template/plan_spec.rb +191 -0
- data/spec/app_archetype/template/source_spec.rb +60 -0
- data/spec/app_archetype/template/variable_manager_spec.rb +103 -0
- data/spec/app_archetype/template/variable_spec.rb +245 -0
- data/spec/app_archetype/template_manager_spec.rb +221 -0
- data/spec/core_ext/string_spec.rb +143 -0
- data/spec/spec_helper.rb +29 -0
- 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
|