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,95 @@
|
|
1
|
+
module AppArchetype
|
2
|
+
# Generators create empty projects for the app_archetype gem
|
3
|
+
module Generators
|
4
|
+
# Default variables provided to new projects
|
5
|
+
DEFAULT_VARS = {
|
6
|
+
'example_string' => {
|
7
|
+
'type' => 'string',
|
8
|
+
'description' => 'This is an example string variable',
|
9
|
+
'default' => 'default value'
|
10
|
+
},
|
11
|
+
'example_random_string' => {
|
12
|
+
'type' => 'string',
|
13
|
+
'description' => 'Example call to helper to generate 25 char string',
|
14
|
+
'value' => '#random_string,25'
|
15
|
+
}
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
# Function that creates a named, empty manifest for new templates
|
19
|
+
TEMPLATE_MANIFEST = lambda do |name|
|
20
|
+
{
|
21
|
+
'name' => name,
|
22
|
+
'version' => '1.0.0',
|
23
|
+
'metadata' => {
|
24
|
+
'app_archetype' => {
|
25
|
+
'version' => AppArchetype::VERSION
|
26
|
+
}
|
27
|
+
},
|
28
|
+
'variables' => DEFAULT_VARS
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Function that creates a readme for a new blank template
|
33
|
+
TEMPLATE_README = lambda do |name|
|
34
|
+
<<~MD
|
35
|
+
# #{name} Template
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
To generate:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
cd $HOME/Code
|
43
|
+
mkdir my_#{name}
|
44
|
+
cd $HOME/Code/my_#{name}
|
45
|
+
|
46
|
+
archetype render #{name}
|
47
|
+
```
|
48
|
+
MD
|
49
|
+
end
|
50
|
+
|
51
|
+
class <<self
|
52
|
+
##
|
53
|
+
# Render empty template renders a manifest and template folder at
|
54
|
+
# the given path.
|
55
|
+
#
|
56
|
+
# The name param will be rendered into the template manifest at
|
57
|
+
# runtime
|
58
|
+
#
|
59
|
+
# @param [String] name
|
60
|
+
# @param [String] path
|
61
|
+
#
|
62
|
+
def render_empty_template(name, path)
|
63
|
+
template_path = File.join(path, name)
|
64
|
+
manifest_path = File.join(path, 'manifest.json')
|
65
|
+
readme_path = File.join(path, 'README.md')
|
66
|
+
|
67
|
+
make_template_dir(template_path)
|
68
|
+
render_manifest(manifest_path, name)
|
69
|
+
render_readme(readme_path, name)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def make_template_dir(path)
|
75
|
+
FileUtils.mkdir_p(path)
|
76
|
+
end
|
77
|
+
|
78
|
+
def render_manifest(path, name)
|
79
|
+
File.open(path, 'w') do |f|
|
80
|
+
f.write(
|
81
|
+
TEMPLATE_MANIFEST.call(name).to_json
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_readme(path, name)
|
87
|
+
File.open(path, 'w') do |f|
|
88
|
+
f.write(
|
89
|
+
TEMPLATE_README.call(name)
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AppArchetype
|
2
|
+
# CLI Logging methods
|
3
|
+
module Logger
|
4
|
+
##
|
5
|
+
# Creates logger for printing messages
|
6
|
+
#
|
7
|
+
# Sets the formatter to output only the provided message to the
|
8
|
+
# specified IO
|
9
|
+
#
|
10
|
+
# @param [IO] out - default: STDOUT
|
11
|
+
#
|
12
|
+
# @return [::Logger]
|
13
|
+
#
|
14
|
+
def logger(out = STDOUT)
|
15
|
+
@logger ||= ::Logger.new(out)
|
16
|
+
@logger.formatter = proc do |_sev, _time, _prog, msg|
|
17
|
+
"#{msg}\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
@logger
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Prints command line message to STDOUT
|
25
|
+
#
|
26
|
+
# For use when printing info messages for a user to STDOUT
|
27
|
+
#
|
28
|
+
# @param [String] message - message to be printed
|
29
|
+
#
|
30
|
+
def print_message(message)
|
31
|
+
logger.info(message)
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Prints warning to STDOUT
|
36
|
+
#
|
37
|
+
# For use when printing warn messages to STDOUT
|
38
|
+
#
|
39
|
+
# @param [String] message - message to be printed
|
40
|
+
#
|
41
|
+
def print_warning(message)
|
42
|
+
logger.warn(message)
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Prints error to STDERR
|
47
|
+
#
|
48
|
+
# For indicating fatal message to user
|
49
|
+
#
|
50
|
+
# @param [String] message - message to be printed
|
51
|
+
#
|
52
|
+
def print_error(message)
|
53
|
+
logger(STDERR).error(message)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Prints a message and then exits with given status code
|
58
|
+
#
|
59
|
+
# This will terminate the program with the given status code
|
60
|
+
#
|
61
|
+
# @param [String] message - message to be printed
|
62
|
+
# @param [Integer] exit_code - exit status (default: 1)
|
63
|
+
#
|
64
|
+
def print_message_and_exit(message, exit_code = 1)
|
65
|
+
print_message(message)
|
66
|
+
exit(exit_code)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
require 'ruby-handlebars'
|
4
|
+
|
5
|
+
module AppArchetype
|
6
|
+
# Renderer renders a plan
|
7
|
+
class Renderer
|
8
|
+
include AppArchetype::Logger
|
9
|
+
|
10
|
+
##
|
11
|
+
# Creates a renderer instance
|
12
|
+
#
|
13
|
+
# @param [AppArchetype::Template::Plan] plan
|
14
|
+
# @param [Boolean] overwrite
|
15
|
+
#
|
16
|
+
def initialize(plan, overwrite = false)
|
17
|
+
@plan = plan
|
18
|
+
@overwrite = overwrite
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Renders plan to disk. The renderer is capable of:
|
23
|
+
# - creating directories
|
24
|
+
# - Rendering ERB templates with plan variables
|
25
|
+
# - Rendering Handlebars templates with plan variables
|
26
|
+
# - Copying static files
|
27
|
+
#
|
28
|
+
# When a template requests a varaible that does not exist within
|
29
|
+
# the plan - then the rendering process stops and a RuntimeError
|
30
|
+
# is raised
|
31
|
+
#
|
32
|
+
# Similarly when a template cannot be parsed a Runtime Error will
|
33
|
+
# be raised.
|
34
|
+
#
|
35
|
+
def render
|
36
|
+
write_dir(File.new(@plan.destination_path))
|
37
|
+
|
38
|
+
@last_file = ''
|
39
|
+
@plan.files.each do |file|
|
40
|
+
@last_file = file
|
41
|
+
if file.source_directory?
|
42
|
+
write_dir(file)
|
43
|
+
elsif file.source_erb?
|
44
|
+
render_erb_file(file)
|
45
|
+
elsif file.source_hbs?
|
46
|
+
render_hbs_file(file)
|
47
|
+
elsif file.source_file?
|
48
|
+
copy_file(file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue NoMethodError => e
|
52
|
+
raise "error rendering #{@last_file.path} "\
|
53
|
+
"cannot find variable `#{e.name}` in template"
|
54
|
+
rescue SyntaxError
|
55
|
+
raise "error parsing #{@last_file.path} template is invalid"
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Creates a directory at the specified location
|
60
|
+
#
|
61
|
+
# @param [AppArchetype::Template::OutputFile] file
|
62
|
+
#
|
63
|
+
def write_dir(file)
|
64
|
+
print_message("CREATE dir -> #{file.path}")
|
65
|
+
|
66
|
+
FileUtils.mkdir_p(file.path)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Renders erb template to output location
|
71
|
+
#
|
72
|
+
# @param [AppArchetype::Template::OutputFile] file
|
73
|
+
#
|
74
|
+
def render_erb_file(file)
|
75
|
+
raise 'cannot overwrite file' if file.exist? && !@overwrite
|
76
|
+
|
77
|
+
print_message("RENDER erb ->: #{file.path}")
|
78
|
+
input = File.read(file.source_file_path)
|
79
|
+
out = ERB.new(input).result(@plan.variables.instance_eval { binding })
|
80
|
+
|
81
|
+
File.open(file.path.gsub('.erb', ''), 'w+') { |f| f.write(out) }
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Renders handlebars template to output location
|
86
|
+
#
|
87
|
+
# @param [AppArchetype::Template::OutputFile] file
|
88
|
+
#
|
89
|
+
def render_hbs_file(file)
|
90
|
+
raise 'cannot overwrite file' if file.exist? && !@overwrite
|
91
|
+
|
92
|
+
print_message("RENDER hbs ->: #{file.path}")
|
93
|
+
|
94
|
+
input = File.read(file.source_file_path)
|
95
|
+
|
96
|
+
hbs = Handlebars::Handlebars.new
|
97
|
+
out = hbs.compile(input).call(@plan.variables.to_h)
|
98
|
+
|
99
|
+
File.open(file.path.gsub('.hbs', ''), 'w+') { |f| f.write(out) }
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Copies source file to planned path only ovewriting if permitted by the
|
104
|
+
# renderer.
|
105
|
+
#
|
106
|
+
# @param [AppArchetype::Template::OutputFile] file
|
107
|
+
#
|
108
|
+
def copy_file(file)
|
109
|
+
raise 'cannot overwrite file' if file.exist? && !@overwrite
|
110
|
+
|
111
|
+
print_message("COPY file ->: #{file.path}")
|
112
|
+
|
113
|
+
FileUtils.cp(file.source_file_path, file.path)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'app_archetype/template/helpers'
|
2
|
+
require 'app_archetype/template/source'
|
3
|
+
require 'app_archetype/template/variable_manager'
|
4
|
+
require 'app_archetype/template/variable'
|
5
|
+
require 'app_archetype/template/manifest'
|
6
|
+
require 'app_archetype/template/plan'
|
7
|
+
|
8
|
+
module AppArchetype
|
9
|
+
# Template is a namespace for template components
|
10
|
+
module Template
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module AppArchetype
|
4
|
+
module Template
|
5
|
+
# Template rendering helpers
|
6
|
+
module Helpers
|
7
|
+
# dot provides a convenient way for a noop render at the
|
8
|
+
# beginning of dotfiles
|
9
|
+
def dot
|
10
|
+
''
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Returns this year as YYYY
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
#
|
18
|
+
def this_year
|
19
|
+
Time.now.strftime('%Y')
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns timestamp at current time
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
def timestamp_now
|
28
|
+
Time.now.strftime('%Y%m%d%H%M%S%L')
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns timestamp at utc current time
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
#
|
36
|
+
def timestamp_utc_now
|
37
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S%L')
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Generates a random string at specified length
|
42
|
+
#
|
43
|
+
# @param [String] length
|
44
|
+
def random_string(length = '256')
|
45
|
+
length = length.to_i
|
46
|
+
key_set = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a
|
47
|
+
(0...length).map { key_set[Random.rand(0..key_set.length)] }.join
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Randomizes a given string by addding a slice of a hex
|
52
|
+
# to the end of it at the specified size.
|
53
|
+
#
|
54
|
+
# The template will pass through a string as arguments for this
|
55
|
+
# function, thus it must accept a string as an argument.
|
56
|
+
#
|
57
|
+
# @param [String] string
|
58
|
+
# @param [String] size
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def randomize(string, size = '5')
|
62
|
+
size = size.to_i
|
63
|
+
raise 'size must be an integer' unless size.is_a?(Integer) && size != 0
|
64
|
+
raise 'randomize supports up to 32 characters' if size > 32
|
65
|
+
|
66
|
+
hex = SecureRandom.hex
|
67
|
+
suffix = hex[hex.length - size..hex.length]
|
68
|
+
|
69
|
+
"#{string}_#{suffix}"
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Converts a string to upper case
|
74
|
+
#
|
75
|
+
# @param [String] string
|
76
|
+
#
|
77
|
+
# @return [String]
|
78
|
+
#
|
79
|
+
def upcase(string)
|
80
|
+
string.upcase
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Converts a string to lower case
|
85
|
+
#
|
86
|
+
# @param [String] string
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
def downcase(string)
|
90
|
+
string.downcase
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Joins a string with specified delimiter
|
95
|
+
#
|
96
|
+
# @param [String] delim
|
97
|
+
# @param [Array] strings
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
def join(delim, *strings)
|
101
|
+
strings.join(delim)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Changes a string into snake case. Useful for
|
106
|
+
# converting class names to function or file names.
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# str = 'AGreatExample'
|
110
|
+
# puts snake_case(str) # => outputs 'a_great_example'
|
111
|
+
#
|
112
|
+
# @param [String] string
|
113
|
+
#
|
114
|
+
# @return [String]
|
115
|
+
#
|
116
|
+
def snake_case(string)
|
117
|
+
return string.downcase if string =~ /\A[A-Z]+\z/
|
118
|
+
|
119
|
+
string
|
120
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
121
|
+
.gsub(/([a-z])([A-Z])/, '\1_\2')
|
122
|
+
.gsub(/\s/, '_')
|
123
|
+
.tr('-', '_')
|
124
|
+
.downcase
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Downcase and converts a string into dashcase string
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# str = 'AGreatExample'
|
132
|
+
# puts = dash_case(str) # => outputs 'a-great-example'
|
133
|
+
|
134
|
+
def dash_case(string)
|
135
|
+
return string.downcase if string =~ /\A[A-Z]+\z/
|
136
|
+
|
137
|
+
string
|
138
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1-\2')
|
139
|
+
.gsub(/([a-z])([A-Z])/, '\1-\2')
|
140
|
+
.tr(' ', '-')
|
141
|
+
.tr('_', '-')
|
142
|
+
.downcase
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Camelcases a given string
|
147
|
+
#
|
148
|
+
# Usage:
|
149
|
+
# in_string = "an example"
|
150
|
+
# out_string = camel_case(in_string) => AnExample
|
151
|
+
#
|
152
|
+
# @param [String] string
|
153
|
+
# @return [String]
|
154
|
+
def camel_case(string)
|
155
|
+
str = snake_case(string)
|
156
|
+
snake_to_camel(str)
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Converts snake case string to camelcase
|
161
|
+
#
|
162
|
+
# Usage:
|
163
|
+
# in_string = "an_example"
|
164
|
+
# out_string = snake_to_camel(in_string) => AnExample
|
165
|
+
#
|
166
|
+
# @param [String] string
|
167
|
+
# @return [String]
|
168
|
+
def snake_to_camel(string)
|
169
|
+
str = snake_case(string)
|
170
|
+
str.to_s.split('_').map(&:capitalize).join('')
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Attempts to pluralize a word
|
175
|
+
#
|
176
|
+
# Usage:
|
177
|
+
# in_string = "Thing"
|
178
|
+
# out_string = pluralize(in_string) => "Things"
|
179
|
+
#
|
180
|
+
# @param [String] string
|
181
|
+
# @return [String]
|
182
|
+
#
|
183
|
+
def pluralize(string)
|
184
|
+
str = string.to_s
|
185
|
+
|
186
|
+
if str.match(/([^aeiouy]|qu)y$/i)
|
187
|
+
str = str.gsub(/y\Z/, 'ies')
|
188
|
+
else
|
189
|
+
str << 's'
|
190
|
+
end
|
191
|
+
|
192
|
+
str
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Singularizes plural words
|
197
|
+
#
|
198
|
+
# Usage:
|
199
|
+
# in_string = "Things"
|
200
|
+
# out_string = singularize(in_string) => "Thing"
|
201
|
+
#
|
202
|
+
# @param [String] string
|
203
|
+
# @return [String]
|
204
|
+
#
|
205
|
+
def singularize(string)
|
206
|
+
str = string.to_s
|
207
|
+
|
208
|
+
if str.end_with?('ies')
|
209
|
+
str.gsub(/ies\Z/, 'y')
|
210
|
+
else
|
211
|
+
str.gsub(/s\Z/, '')
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|