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,132 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::CLI do
4
+ subject { described_class.new }
5
+
6
+ describe '.manager' do
7
+ let(:manager) { double }
8
+ let(:template_dir) { 'path/to/templates' }
9
+
10
+ before do
11
+ allow(described_class).to receive(:template_dir).and_return(template_dir)
12
+ allow(AppArchetype::TemplateManager).to receive(:new).and_return(manager)
13
+
14
+ allow(manager).to receive(:load)
15
+
16
+ @manager = described_class.manager
17
+ end
18
+
19
+ it 'creates a manager' do
20
+ expect(AppArchetype::TemplateManager)
21
+ .to have_received(:new)
22
+ .with(template_dir)
23
+
24
+ expect(manager).to have_received(:load)
25
+ expect(@manager).to eq manager
26
+ end
27
+ end
28
+
29
+ describe '.template_dir' do
30
+ let(:env_template_dir) { 'path/to/templates' }
31
+ let(:exist) { true }
32
+
33
+ before do
34
+ allow(ENV).to receive(:[])
35
+ .with('ARCHETYPE_TEMPLATE_DIR')
36
+ .and_return(env_template_dir)
37
+
38
+ allow(File).to receive(:exist?).and_return(exist)
39
+ end
40
+
41
+ it 'returns template dir' do
42
+ expect(described_class.template_dir).to eq env_template_dir
43
+ end
44
+
45
+ context 'when ARCHETYPE_TEMPLATE_DIR environment variable not set' do
46
+ let(:env_template_dir) { nil }
47
+
48
+ it 'raises environment not set error' do
49
+ expect { described_class.template_dir }.to raise_error(
50
+ RuntimeError,
51
+ 'ARCHETYPE_TEMPLATE_DIR environment variable not set'
52
+ )
53
+ end
54
+ end
55
+
56
+ context 'when templates do not exist' do
57
+ let(:exist) { false }
58
+
59
+ it 'raises environment not set error' do
60
+ expect { described_class.template_dir }.to raise_error(
61
+ RuntimeError,
62
+ "ARCHETYPE_TEMPLATE_DIR #{env_template_dir} does not exist"
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '.editor' do
69
+ let(:env_editor) { 'ivm' }
70
+ let(:exit_status) { 0 }
71
+
72
+ before do
73
+ allow(ENV).to receive(:[])
74
+ .with('ARCHETYPE_EDITOR')
75
+ .and_return(env_editor)
76
+
77
+ allow(described_class).to receive(:`)
78
+ allow($?).to receive(:exitstatus).and_return(exit_status)
79
+ end
80
+
81
+ it 'returns editor' do
82
+ expect(described_class.editor).to eq env_editor
83
+ end
84
+
85
+ context 'when ARCHETYPE_EDITOR environment variable not set' do
86
+ let(:env_editor) { nil }
87
+
88
+ it 'raises environment not set error' do
89
+ expect { described_class.editor }.to raise_error(
90
+ RuntimeError,
91
+ 'ARCHETYPE_EDITOR environment variable not set'
92
+ )
93
+ end
94
+ end
95
+
96
+ context 'when editor check does not pass' do
97
+ let(:exit_status) { 1 }
98
+
99
+ before do
100
+ allow(AppArchetype::CLI).to receive(:print_warning)
101
+ described_class.editor
102
+ end
103
+
104
+ it 'logs a warning' do
105
+ expect(AppArchetype::CLI)
106
+ .to have_received(:print_warning)
107
+ .with(
108
+ "WARN: Configured editor #{env_editor} is not installed correctly "\
109
+ 'please check your configuration'
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '.exit_on_failure?' do
116
+ it 'returns true' do
117
+ expect(described_class.exit_on_failure?).to be true
118
+ end
119
+ end
120
+
121
+ describe '#version' do
122
+ before do
123
+ allow(subject).to receive(:print_message)
124
+ subject.version
125
+ end
126
+
127
+ it 'prints current version number' do
128
+ expect(subject).to have_received(:print_message)
129
+ .with(AppArchetype::VERSION)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::Generators do
4
+ describe '::TEMPLATE_MANIFEST' do
5
+ let(:name) { 'project' }
6
+
7
+ before do
8
+ @manifest = OpenStruct.new(
9
+ described_class::TEMPLATE_MANIFEST.call(name)
10
+ )
11
+ end
12
+
13
+ it 'has expected name' do
14
+ expect(@manifest.name).to eq name
15
+ end
16
+
17
+ it 'has expected version' do
18
+ expect(@manifest.version).to eq '1.0.0'
19
+ end
20
+
21
+ it 'has app archetype metadata' do
22
+ expect(@manifest.metadata['app_archetype']).not_to be nil
23
+ end
24
+
25
+ it 'renders app version into archetype metadata' do
26
+ expect(@manifest.metadata['app_archetype']['version'])
27
+ .to eq AppArchetype::VERSION
28
+ end
29
+
30
+ it 'has default variables' do
31
+ expect(@manifest.variables).to eq described_class::DEFAULT_VARS
32
+ end
33
+
34
+ it 'renders a manifest that is compliant with the manifest schema' do
35
+ expect(
36
+ JSON::Validator.fully_validate(
37
+ AppArchetype::Template::Manifest::SCHEMA,
38
+ @manifest.to_h.to_json,
39
+ strict: true
40
+ ).empty?
41
+ ).to be true
42
+ end
43
+ end
44
+
45
+ describe '::TEMPLATE_README' do
46
+ let(:name) { 'project' }
47
+
48
+ before do
49
+ @readme = described_class::TEMPLATE_README.call(name)
50
+ end
51
+
52
+ it 'renders name into heading' do
53
+ expect(@readme.include?("# #{name} Template")).to be true
54
+ end
55
+
56
+ it 'renders name into generation example' do
57
+ expect(@readme.include?("mkdir my_#{name}")).to be true
58
+ expect(@readme.include?("cd $HOME/Code/my_#{name}")).to be true
59
+ expect(@readme.include?("archetype render #{name}")).to be true
60
+ end
61
+ end
62
+
63
+ describe '.render_empty_template' do
64
+ let(:manifest_file) { double }
65
+ let(:manifest_path) { File.join(templates_path, 'manifest.json') }
66
+
67
+ let(:readme_file) { double }
68
+ let(:readme_path) { File.join(templates_path, 'README.md') }
69
+
70
+ let(:name) { 'new_template' }
71
+ let(:templates_path) { 'path/to/templates' }
72
+
73
+ let(:manifest) { 'manifest' }
74
+ let(:readme) { 'readme' }
75
+
76
+ before do
77
+ allow(File).to receive(:open)
78
+ .with(manifest_path, 'w')
79
+ .and_yield(manifest_file)
80
+ allow(manifest_file).to receive(:write)
81
+
82
+ allow(File).to receive(:open)
83
+ .with(readme_path, 'w')
84
+ .and_yield(readme_file)
85
+ allow(readme_file).to receive(:write)
86
+
87
+ allow(FileUtils).to receive(:mkdir_p)
88
+
89
+ allow(described_class::TEMPLATE_MANIFEST)
90
+ .to receive(:call).and_return(manifest)
91
+
92
+ allow(described_class::TEMPLATE_README)
93
+ .to receive(:call).and_return(readme)
94
+
95
+ described_class.render_empty_template(
96
+ name,
97
+ templates_path
98
+ )
99
+ end
100
+
101
+ it 'makes template dir' do
102
+ expect(FileUtils)
103
+ .to have_received(:mkdir_p)
104
+ .with(File.join(templates_path, name))
105
+ end
106
+
107
+ it 'renders blank manifest' do
108
+ expect(manifest_file)
109
+ .to have_received(:write)
110
+ .with("\"#{manifest}\"")
111
+ end
112
+
113
+ it 'renders readme' do
114
+ expect(readme_file)
115
+ .to have_received(:write)
116
+ .with(readme)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::Logger do
4
+ let(:logger) { double(Logger) }
5
+ let(:err_logger) { double(Logger) }
6
+ let(:message) { 'All work and no play makes Jack a dull boy' }
7
+
8
+ subject { Object.new.extend(described_class) }
9
+
10
+ before do
11
+ allow(subject).to receive(:exit).and_return(double)
12
+ end
13
+
14
+ describe '.logger' do
15
+ let(:out) { double(STDOUT) }
16
+ before do
17
+ allow(Logger).to receive(:new).and_return(logger)
18
+ allow(logger).to receive(:formatter=)
19
+
20
+ subject.logger(out)
21
+ end
22
+
23
+ it 'creates a new logger to STDOUT' do
24
+ expect(Logger).to have_received(:new).with(out)
25
+ end
26
+ end
27
+
28
+ describe '.print_message' do
29
+ let(:logger) { double(Logger) }
30
+
31
+ before do
32
+ allow(subject).to receive(:logger).and_return(logger)
33
+ allow(logger).to receive(:info)
34
+ subject.print_message(message)
35
+ end
36
+
37
+ it 'prints message to stdout' do
38
+ expect(logger).to have_received(:info).with(message)
39
+ end
40
+ end
41
+
42
+ describe '.print_warning' do
43
+ let(:logger) { double(Logger) }
44
+
45
+ before do
46
+ allow(subject).to receive(:logger).and_return(logger)
47
+ allow(logger).to receive(:warn)
48
+ subject.print_warning(message)
49
+ end
50
+
51
+ it 'prints message to stdout' do
52
+ expect(logger).to have_received(:warn).with(message)
53
+ end
54
+ end
55
+
56
+ describe '.print_error' do
57
+ let(:logger) { double(Logger) }
58
+
59
+ before do
60
+ allow(subject).to receive(:logger).and_return(logger)
61
+ allow(logger).to receive(:error)
62
+ subject.print_error(message)
63
+ end
64
+
65
+ it 'prints message to stderr' do
66
+ expect(logger).to have_received(:error).with(message)
67
+ end
68
+ end
69
+
70
+ describe '.print_message_and_exit' do
71
+ let(:exit_code) { 2 }
72
+
73
+ before do
74
+ allow(subject).to receive(:print_message)
75
+ subject.print_message_and_exit(message, exit_code)
76
+ end
77
+
78
+ it 'prints message' do
79
+ expect(subject).to have_received(:print_message).with(message)
80
+ end
81
+
82
+ it 'exits with set status' do
83
+ expect(subject).to have_received(:exit).with(exit_code)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,291 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AppArchetype::Renderer do
4
+ let(:logger) { double(Logger) }
5
+ let(:template) { AppArchetype::Template::Source.new('path/to/template') }
6
+ let(:destination) { 'path/to/destination' }
7
+ let(:variables) do
8
+ OpenStruct.new(
9
+ foo: 'bar'
10
+ )
11
+ end
12
+ let(:plan) do
13
+ AppArchetype::Template::Plan.new(
14
+ template,
15
+ variables,
16
+ destination_path: destination
17
+ )
18
+ end
19
+ let(:overwrite) { false }
20
+
21
+ before do
22
+ allow(subject).to receive(:logger).and_return(logger)
23
+ allow(logger).to receive(:info)
24
+ end
25
+
26
+ subject { described_class.new(plan, overwrite) }
27
+
28
+ describe '#render' do
29
+ let(:file) do
30
+ AppArchetype::Template::OutputFile.new(
31
+ 'path/to/source/file',
32
+ 'path/to/destination/file'
33
+ )
34
+ end
35
+
36
+ let(:erb_template) do
37
+ AppArchetype::Template::OutputFile.new(
38
+ 'path/to/template.erb',
39
+ 'path/to/destination/template'
40
+ )
41
+ end
42
+
43
+ let(:hbs_template) do
44
+ AppArchetype::Template::OutputFile.new(
45
+ 'path/to/template.hbs',
46
+ 'path/to/destination/template'
47
+ )
48
+ end
49
+
50
+ let(:directory) do
51
+ AppArchetype::Template::OutputFile.new(
52
+ 'path/to/source/dir',
53
+ 'path/to/destination/dir'
54
+ )
55
+ end
56
+
57
+ let(:file_double) { double(File) }
58
+
59
+ before do
60
+ allow(subject).to receive(:write_dir)
61
+ allow(subject).to receive(:render_erb_file)
62
+ allow(subject).to receive(:render_hbs_file)
63
+ allow(subject).to receive(:copy_file)
64
+
65
+ allow(File).to receive(:new).and_return(file_double)
66
+
67
+ allow(file).to receive(:source_file?).and_return(true)
68
+ allow(erb_template).to receive(:source_erb?).and_return(true)
69
+ allow(hbs_template).to receive(:source_hbs?).and_return(true)
70
+ allow(directory).to receive(:source_directory?).and_return(true)
71
+
72
+ plan.instance_variable_set(
73
+ :@files,
74
+ [file, erb_template, hbs_template, directory]
75
+ )
76
+
77
+ subject.render
78
+ end
79
+
80
+ it 'creates destination directory' do
81
+ expect(subject).to have_received(:write_dir).with(file_double)
82
+ end
83
+
84
+ it 'creates directory' do
85
+ expect(subject).to have_received(:write_dir).with(directory)
86
+ end
87
+
88
+ it 'renders erb template' do
89
+ expect(subject).to have_received(:render_erb_file).with(erb_template)
90
+ end
91
+
92
+ it 'renders hbs template' do
93
+ expect(subject).to have_received(:render_hbs_file).with(hbs_template)
94
+ end
95
+
96
+ it 'copies file' do
97
+ expect(subject).to have_received(:copy_file).with(file)
98
+ end
99
+
100
+ context 'when no method error is raised' do
101
+ before do
102
+ allow(subject).to receive(:render_erb_file).and_raise(NoMethodError.new)
103
+
104
+ plan.instance_variable_set(
105
+ :@files,
106
+ [erb_template]
107
+ )
108
+ end
109
+
110
+ it 'raises missing variable error' do
111
+ expect { subject.render }.to raise_error(
112
+ RuntimeError,
113
+ 'error rendering path/to/destination/template cannot '\
114
+ 'find variable `` in template'
115
+ )
116
+ end
117
+ end
118
+
119
+ context 'when no template is invalid' do
120
+ before do
121
+ allow(subject).to receive(:render_erb_file).and_raise(SyntaxError.new)
122
+
123
+ plan.instance_variable_set(
124
+ :@files,
125
+ [erb_template]
126
+ )
127
+ end
128
+
129
+ it 'raises missing variable error' do
130
+ expect { subject.render }.to raise_error(
131
+ RuntimeError,
132
+ 'error parsing path/to/destination/template template is invalid'
133
+ )
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '#write_dir' do
139
+ let(:source_path) { 'path/to/template/dir' }
140
+ let(:dest_path) { 'path/to/destination/dir' }
141
+ let(:dir) { AppArchetype::Template::OutputFile.new(source_path, dest_path) }
142
+ let(:exists) { false }
143
+
144
+ before do
145
+ allow(FileUtils).to receive(:mkdir_p)
146
+ end
147
+
148
+ it 'creates directory' do
149
+ subject.write_dir(dir)
150
+ expect(FileUtils).to have_received(:mkdir_p).with(dest_path)
151
+ end
152
+ end
153
+
154
+ describe '#render_erb_file' do
155
+ let(:source_path) { 'path/to/template/file' }
156
+ let(:dest_path) { 'path/to/destination/file' }
157
+
158
+ let(:file) do
159
+ AppArchetype::Template::OutputFile.new(source_path, dest_path)
160
+ end
161
+
162
+ let(:input) do
163
+ <<~INPUT
164
+ this is the content of the <%= foo %> file
165
+ INPUT
166
+ end
167
+
168
+ let(:expected_output) do
169
+ <<~OUTPUT
170
+ this is the content of the bar file
171
+ OUTPUT
172
+ end
173
+
174
+ let(:write_double) { double }
175
+
176
+ before do
177
+ allow(File).to receive(:read).and_return(input)
178
+ allow(File).to receive(:open).and_yield(write_double)
179
+ allow(write_double).to receive(:write)
180
+
181
+ subject.render_erb_file(file)
182
+ end
183
+
184
+ it 'reads source' do
185
+ expect(File).to have_received(:read).with(source_path)
186
+ end
187
+
188
+ it 'writes rendered template' do
189
+ expect(write_double).to have_received(:write).with(expected_output)
190
+ end
191
+
192
+ context 'when a variable is missing' do
193
+ let(:vars) { OpenStruct.new }
194
+ let(:var_value) { '' }
195
+
196
+ it 'renders template with blank' do
197
+ expect(write_double).to have_received(:write).with(expected_output)
198
+ end
199
+ end
200
+ end
201
+
202
+ describe '#render_hbs_file' do
203
+ let(:source_path) { 'path/to/template/file' }
204
+ let(:dest_path) { 'path/to/destination/file' }
205
+
206
+ let(:file) do
207
+ AppArchetype::Template::OutputFile.new(source_path, dest_path)
208
+ end
209
+
210
+ let(:input) do
211
+ <<~INPUT
212
+ this is the content of the {{ foo }} file
213
+ INPUT
214
+ end
215
+
216
+ let(:expected_output) do
217
+ <<~OUTPUT
218
+ this is the content of the bar file
219
+ OUTPUT
220
+ end
221
+
222
+ let(:write_double) { double }
223
+
224
+ before do
225
+ allow(File).to receive(:read).and_return(input)
226
+ allow(File).to receive(:open).and_yield(write_double)
227
+ allow(write_double).to receive(:write)
228
+
229
+ subject.render_hbs_file(file)
230
+ end
231
+
232
+ it 'reads source' do
233
+ expect(File).to have_received(:read).with(source_path)
234
+ end
235
+
236
+ it 'writes rendered template' do
237
+ expect(write_double).to have_received(:write).with(expected_output)
238
+ end
239
+
240
+ context 'when a variable is missing' do
241
+ let(:vars) { OpenStruct.new }
242
+ let(:var_value) { '' }
243
+
244
+ it 'renders template with blank' do
245
+ expect(write_double).to have_received(:write).with(expected_output)
246
+ end
247
+ end
248
+ end
249
+
250
+ describe '#copy_file' do
251
+ let(:source_path) { 'path/to/template/file' }
252
+ let(:dest_path) { 'path/to/destination/file' }
253
+
254
+ let(:file) do
255
+ AppArchetype::Template::OutputFile.new(source_path, dest_path)
256
+ end
257
+
258
+ let(:exists) { false }
259
+
260
+ before do
261
+ allow(FileUtils).to receive(:cp)
262
+ allow(file).to receive(:exist?).and_return(exists)
263
+ end
264
+
265
+ it 'copies file' do
266
+ subject.copy_file(file)
267
+ expect(FileUtils).to have_received(:cp).with(source_path, dest_path)
268
+ end
269
+
270
+ context 'when file already exists and overwrite not allowed' do
271
+ let(:exists) { true }
272
+
273
+ it 'raises error' do
274
+ expect do
275
+ subject.copy_file(file)
276
+ end.to raise_error('cannot overwrite file')
277
+ end
278
+ end
279
+
280
+ context 'when file already exists and overwrite is allowed' do
281
+ let(:exists) { true }
282
+ let(:overwrite) { true }
283
+
284
+ it 'raises error' do
285
+ expect do
286
+ subject.copy_file(file)
287
+ end.not_to raise_error
288
+ end
289
+ end
290
+ end
291
+ end