mkbrut 0.1.0
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/README.md +51 -0
- data/exe/mkbrut +5 -0
- data/lib/mkbrut/app.rb +67 -0
- data/lib/mkbrut/app_id.rb +8 -0
- data/lib/mkbrut/app_name.rb +29 -0
- data/lib/mkbrut/app_options.rb +29 -0
- data/lib/mkbrut/base.rb +57 -0
- data/lib/mkbrut/cli.rb +91 -0
- data/lib/mkbrut/erb_binding_delegate.rb +20 -0
- data/lib/mkbrut/internet_identifier.rb +32 -0
- data/lib/mkbrut/invalid_identifier.rb +4 -0
- data/lib/mkbrut/ops/add_css_import.rb +42 -0
- data/lib/mkbrut/ops/add_i18n_message.rb +74 -0
- data/lib/mkbrut/ops/add_method.rb +48 -0
- data/lib/mkbrut/ops/append_to_file.rb +17 -0
- data/lib/mkbrut/ops/base_op.rb +21 -0
- data/lib/mkbrut/ops/copy_file.rb +12 -0
- data/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
- data/lib/mkbrut/ops/insert_route.rb +52 -0
- data/lib/mkbrut/ops/mkdir.rb +13 -0
- data/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
- data/lib/mkbrut/ops/render_template.rb +26 -0
- data/lib/mkbrut/ops/skip_file.rb +10 -0
- data/lib/mkbrut/ops.rb +16 -0
- data/lib/mkbrut/organization.rb +5 -0
- data/lib/mkbrut/prefix.rb +26 -0
- data/lib/mkbrut/prefixed_io.rb +16 -0
- data/lib/mkbrut/segments/bare_bones.rb +184 -0
- data/lib/mkbrut/segments/demo.rb +117 -0
- data/lib/mkbrut/segments/sidekiq.rb +3 -0
- data/lib/mkbrut/segments.rb +7 -0
- data/lib/mkbrut/version.rb +3 -0
- data/lib/mkbrut/versions.rb +16 -0
- data/lib/mkbrut.rb +17 -0
- data/templates/Base/Dockerfile.dx +205 -0
- data/templates/Base/Gemfile.erb +53 -0
- data/templates/Base/Procfile.development +4 -0
- data/templates/Base/Procfile.test +1 -0
- data/templates/Base/README.md +4 -0
- data/templates/Base/README.md.erb +40 -0
- data/templates/Base/app/bootstrap.rb +61 -0
- data/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
- data/templates/Base/app/config/i18n/en/2_app.rb +24 -0
- data/templates/Base/app/public/static/manifest.json.erb +33 -0
- data/templates/Base/app/src/app.rb.erb +37 -0
- data/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
- data/templates/Base/app/src/back_end/data_models/db.rb +19 -0
- data/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
- data/templates/Base/app/src/front_end/components/app_component.rb +8 -0
- data/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
- data/templates/Base/app/src/front_end/css/fonts.css +19 -0
- data/templates/Base/app/src/front_end/css/index.css +3 -0
- data/templates/Base/app/src/front_end/css/svgs.css +12 -0
- data/templates/Base/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
- data/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
- data/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
- data/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
- data/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
- data/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
- data/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
- data/templates/Base/app/src/front_end/images/favicon.ico +0 -0
- data/templates/Base/app/src/front_end/images/icon.png +0 -0
- data/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
- data/templates/Base/app/src/front_end/js/index.js +6 -0
- data/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +76 -0
- data/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
- data/templates/Base/app/src/front_end/pages/home_page.rb.erb +54 -0
- data/templates/Base/app/src/front_end/support/app_session.rb +6 -0
- data/templates/Base/app/src/front_end/svgs/README.md +5 -0
- data/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
- data/templates/Base/bin/README.md.erb +5 -0
- data/templates/Base/bin/build-assets +7 -0
- data/templates/Base/bin/ci +39 -0
- data/templates/Base/bin/console +31 -0
- data/templates/Base/bin/db +9 -0
- data/templates/Base/bin/dbconsole +51 -0
- data/templates/Base/bin/dev +25 -0
- data/templates/Base/bin/release +26 -0
- data/templates/Base/bin/run +86 -0
- data/templates/Base/bin/scaffold +9 -0
- data/templates/Base/bin/setup +256 -0
- data/templates/Base/bin/test +9 -0
- data/templates/Base/bin/test-server +29 -0
- data/templates/Base/bin/watch-and-build-assets +37 -0
- data/templates/Base/config.ru +16 -0
- data/templates/Base/docker-compose.dx.yml +85 -0
- data/templates/Base/dx/README.md +28 -0
- data/templates/Base/dx/bash_customizations +12 -0
- data/templates/Base/dx/bash_customizations.local +4 -0
- data/templates/Base/dx/build +101 -0
- data/templates/Base/dx/docker-compose.env.erb +25 -0
- data/templates/Base/dx/dx.sh.lib +137 -0
- data/templates/Base/dx/exec +56 -0
- data/templates/Base/dx/prune +19 -0
- data/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/templates/Base/dx/start +30 -0
- data/templates/Base/dx/stop +23 -0
- data/templates/Base/package.json.erb +37 -0
- data/templates/Base/puma.config.rb +53 -0
- data/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
- data/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
- data/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
- data/templates/Base/specs/lint_factories.spec.rb +7 -0
- data/templates/Base/specs/spec_helper.rb +78 -0
- data/templates/Base/specs/support.rb +2 -0
- data/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
- data/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
- data/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
- data/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
- data/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
- data/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +15 -0
- data/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
- data/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
- data/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
- data/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
- data/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
- data/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
- data/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
- data/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
- data/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
- data/templates/segments/Demo/specs/factories/db/guestbook_message.rb +7 -0
- data/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
- data/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
- data/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
- data/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
- data/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
- metadata +195 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
class MKBrut::Ops::InsertCodeInMethod < MKBrut::Ops::PrismParsingOp
|
2
|
+
def initialize(file:, class_name:, method_name:, code:, where: :end)
|
3
|
+
@file = file
|
4
|
+
@class_name = class_name
|
5
|
+
@method_name = method_name.to_sym
|
6
|
+
@code = code
|
7
|
+
@where = where
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
method_node = find_method(class_name: @class_name, method_name: @method_name)
|
12
|
+
|
13
|
+
insertion_point = if @where == :start
|
14
|
+
insertion_point_for_code_at_start_of_method(method_node: method_node)
|
15
|
+
else
|
16
|
+
insertion_point_for_code_at_end_of_method(method_node: method_node)
|
17
|
+
end
|
18
|
+
indented_code = indent_code_for_method(method_node: method_node)
|
19
|
+
|
20
|
+
new_source = @source.dup.insert(insertion_point, indented_code)
|
21
|
+
File.write(@file, new_source)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def indent_code_for_method(method_node:)
|
27
|
+
|
28
|
+
method_start_line = method_node.location.start_line
|
29
|
+
spaces_before_def = @source.lines[method_start_line - 1][/^\s*/] || ""
|
30
|
+
spaces_for_code_in_method = spaces_before_def + " "
|
31
|
+
|
32
|
+
post_indent = if @where == :start
|
33
|
+
"\n#{spaces_before_def}"
|
34
|
+
else
|
35
|
+
""
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
"\n" +
|
40
|
+
@code.split(/\n/).map { |line|
|
41
|
+
spaces_for_code_in_method + line
|
42
|
+
}.join("\n") + post_indent
|
43
|
+
end
|
44
|
+
|
45
|
+
def insertion_point_for_code_at_end_of_method(method_node:)
|
46
|
+
line_number_of_method_end = method_node.location.end_line - 1
|
47
|
+
length_of_method_end = @source.lines[line_number_of_method_end].length
|
48
|
+
|
49
|
+
method_node.location.end_offset - length_of_method_end
|
50
|
+
end
|
51
|
+
|
52
|
+
def insertion_point_for_code_at_start_of_method(method_node:)
|
53
|
+
line_number_of_method_start = method_node.location.start_line - 1
|
54
|
+
length_of_method_start = @source.lines[line_number_of_method_start].length
|
55
|
+
|
56
|
+
method_node.location.start_offset + length_of_method_start
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class MKBrut::Ops::InsertRoute < MKBrut::Ops::PrismParsingOp
|
2
|
+
def initialize(project_root:, code:)
|
3
|
+
@file = project_root / "app" / "src" / "app.rb"
|
4
|
+
@code = code
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
if dry_run?
|
9
|
+
puts "Would insert route:\n#{@code}\ninto #{@file}"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
app_class_node = find_class(class_name: "App")
|
13
|
+
|
14
|
+
routes_block = find_routes_block(app_class_node)
|
15
|
+
|
16
|
+
if !routes_block
|
17
|
+
raise "'App' in '#{@file}' did not have a routes block, so we cannot insert a new route"
|
18
|
+
end
|
19
|
+
|
20
|
+
end_offset = routes_block.block.location.end_offset
|
21
|
+
indented_line = " #{@code}\n "
|
22
|
+
new_source = @source.dup.insert(end_offset - 3, indented_line)
|
23
|
+
|
24
|
+
File.open(@file, "w") do |file|
|
25
|
+
file.puts new_source
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_routes_block(class_node)
|
30
|
+
statements = case class_node.body
|
31
|
+
when Prism::StatementsNode
|
32
|
+
class_node.body.body
|
33
|
+
when nil
|
34
|
+
[]
|
35
|
+
else
|
36
|
+
[class_node.body]
|
37
|
+
end
|
38
|
+
|
39
|
+
statements.detect do |statement|
|
40
|
+
if statement.is_a?(Prism::CallNode)
|
41
|
+
if statement.name == :routes
|
42
|
+
statement.block
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "prism"
|
2
|
+
class MKBrut::Ops::PrismParsingOp < MKBrut::Ops::BaseOp
|
3
|
+
def initialize(file:)
|
4
|
+
@file = file
|
5
|
+
end
|
6
|
+
|
7
|
+
class ClassNotInSource < StandardError
|
8
|
+
def initialize(file:, class_name:)
|
9
|
+
super("Could not find the class '#{class_name}' inside '#{file}'")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MethodNotInClass < StandardError
|
14
|
+
def initialize(file:, class_name:, method_name:)
|
15
|
+
super("Could not find the method '#{method_name}' in class '#{class_name}' inside '#{file}'")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class SourceNotParseable < StandardError
|
20
|
+
def initialize(tree_errors:, file:)
|
21
|
+
error_message = tree_errors.map { |error|
|
22
|
+
"#{error.message} (line #{error.location.start_line}, column #{error.location.start_column})"
|
23
|
+
}.join(", ")
|
24
|
+
super("Failed to parse file '#{file}': #{error_message}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_file!
|
31
|
+
source = File.read(@file)
|
32
|
+
tree = Prism.parse(source)
|
33
|
+
|
34
|
+
if !tree.success?
|
35
|
+
raise SourceNotParseable.new(tree_errors: tree.errors, file: @file)
|
36
|
+
end
|
37
|
+
@tree = tree
|
38
|
+
@source = source
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_class(class_name:, assumed_body: true)
|
42
|
+
if !@tree
|
43
|
+
parse_file!
|
44
|
+
end
|
45
|
+
class_node = @tree.value.statements.body.detect { |node|
|
46
|
+
node.is_a?(Prism::ClassNode) && node.constant_path.slice == class_name
|
47
|
+
}
|
48
|
+
|
49
|
+
if !class_node
|
50
|
+
raise ClassNotInSource.new(file: @file, class_name: class_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
if !class_node.body.respond_to?(:body) && assumed_body
|
54
|
+
raise "The class '#{class_name}' in '#{file}' does not have any methods"
|
55
|
+
end
|
56
|
+
class_node
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_method(class_name:, method_name:)
|
60
|
+
class_node = find_class(class_name:)
|
61
|
+
method_node = class_node.body.body.detect { |node|
|
62
|
+
node.is_a?(Prism::DefNode) && node.name == @method_name
|
63
|
+
}
|
64
|
+
|
65
|
+
if !method_node
|
66
|
+
raise MethodNotInClass.new(file: @file, class_name: class_name, method_name: @method_name)
|
67
|
+
end
|
68
|
+
method_node
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "erb"
|
2
|
+
class MKBrut::Ops::RenderTemplate < MKBrut::Ops::BaseOp
|
3
|
+
|
4
|
+
def initialize(source, destination_root:, erb_binding:)
|
5
|
+
@erb = source
|
6
|
+
@destination_file = destination_root / @erb.basename.sub_ext("")
|
7
|
+
@erb_binding = erb_binding
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
if dry_run?
|
12
|
+
puts "Render '#{@destination_file}'"
|
13
|
+
return
|
14
|
+
end
|
15
|
+
template = File.read(@erb)
|
16
|
+
File.open(@destination_file, "w") do |file|
|
17
|
+
file.puts ERB.new(
|
18
|
+
template,
|
19
|
+
trim_mode: "-"
|
20
|
+
).result(
|
21
|
+
@erb_binding.instance_eval { binding }
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def to_s = "ERB '#{@erb}' to '#{@destination_file}'"
|
26
|
+
end
|
data/lib/mkbrut/ops.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module MKBrut
|
2
|
+
module Ops
|
3
|
+
autoload :BaseOp, "mkbrut/ops/base_op"
|
4
|
+
autoload :Mkdir, "mkbrut/ops/mkdir"
|
5
|
+
autoload :CopyFile, "mkbrut/ops/copy_file"
|
6
|
+
autoload :RenderTemplate, "mkbrut/ops/render_template"
|
7
|
+
autoload :SkipFile, "mkbrut/ops/skip_file"
|
8
|
+
autoload :InsertRoute, "mkbrut/ops/insert_route"
|
9
|
+
autoload :InsertCodeInMethod, "mkbrut/ops/insert_code_in_method"
|
10
|
+
autoload :AppendToFile, "mkbrut/ops/append_to_file"
|
11
|
+
autoload :PrismParsingOp, "mkbrut/ops/prism_parsing_op"
|
12
|
+
autoload :AddI18nMessage, "mkbrut/ops/add_i18n_message"
|
13
|
+
autoload :AddCSSImport, "mkbrut/ops/add_css_import"
|
14
|
+
autoload :AddMethod, "mkbrut/ops/add_method"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MKBrut
|
2
|
+
class Prefix
|
3
|
+
def self.from_app_id(app_id)
|
4
|
+
app_id = app_id.to_s
|
5
|
+
prefix = if app_id =~ /^[^-]+[a-z]-[a-z]/
|
6
|
+
app_id.split("-")[0..1].map { it[0] }.join("")
|
7
|
+
else
|
8
|
+
app_id[0..1]
|
9
|
+
end
|
10
|
+
self.new(prefix)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(identifier)
|
14
|
+
@identifier = identifier.to_s
|
15
|
+
if @identifier.length != 2
|
16
|
+
raise InvalidIdentifier, "prefix '#{@identifier}' must be 2 characters"
|
17
|
+
end
|
18
|
+
if @identifier !~ /^[a-z]+$/
|
19
|
+
raise InvalidIdentifier, "prefix must be only lower case ASCII letters"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s = @identifier
|
24
|
+
alias to_str to_s
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# The bare bones configuration on top of a blank Brut app.
|
2
|
+
class MKBrut::Segments::BareBones < MKBrut::Base
|
3
|
+
|
4
|
+
def self.friendly_name = "Bare bones framing"
|
5
|
+
|
6
|
+
def initialize(app_options:, current_dir:, templates_dir:)
|
7
|
+
@project_root = current_dir / app_options.app_name
|
8
|
+
@templates_dir = templates_dir / "segments" / "BareBones"
|
9
|
+
@erb_binding = ErbBindingDelegate.new(app_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add!
|
13
|
+
|
14
|
+
operations = copy_files(@templates_dir, @project_root) +
|
15
|
+
other_operations(@project_root)
|
16
|
+
|
17
|
+
operations.each do |operation|
|
18
|
+
operation.call
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def other_operations(project_root)
|
24
|
+
[
|
25
|
+
MKBrut::Ops::InsertRoute.new(
|
26
|
+
project_root: @project_root,
|
27
|
+
code: %{path "/trigger_exception", method: :get}
|
28
|
+
),
|
29
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
30
|
+
file: @project_root / "app" / "src" / "app.rb",
|
31
|
+
class_name: "App",
|
32
|
+
method_name: "initialize",
|
33
|
+
code: %{
|
34
|
+
Brut.container.store(
|
35
|
+
"trigger_exception_key",
|
36
|
+
String,
|
37
|
+
"String used to prevent anyone from triggering exceptions in TriggerExceptionHandler"
|
38
|
+
) do
|
39
|
+
ENV.fetch("TRIGGER_EXCEPTION_KEY")
|
40
|
+
end},
|
41
|
+
),
|
42
|
+
InsertCustomElement.new(
|
43
|
+
project_root: @project_root,
|
44
|
+
element_class_name: "Example",
|
45
|
+
),
|
46
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
47
|
+
file: @project_root / "app" / "src" / "front_end" / "pages" / "home_page.rb",
|
48
|
+
class_name: "HomePage",
|
49
|
+
method_name: "page_template",
|
50
|
+
code: %{
|
51
|
+
#{ @erb_binding.prefix }_example(
|
52
|
+
transform: "upper",
|
53
|
+
class: [ "ff-sans",
|
54
|
+
"lh-title",
|
55
|
+
"tracked",
|
56
|
+
"f-5",
|
57
|
+
"f-6-ns",
|
58
|
+
"bg-green-100",
|
59
|
+
"red-900",
|
60
|
+
"tc",
|
61
|
+
"pa-3",
|
62
|
+
"mt-3",
|
63
|
+
"db",
|
64
|
+
]
|
65
|
+
) do
|
66
|
+
"We Like the Web"
|
67
|
+
end
|
68
|
+
}
|
69
|
+
),
|
70
|
+
InsertEndToEndTestCode.new(
|
71
|
+
file: @project_root / "specs" / "e2e" / "home_page.spec.rb",
|
72
|
+
code: %{
|
73
|
+
example = page.locator("#{ @erb_binding.prefix }-example")
|
74
|
+
# The #{ @erb_binding.prefix }-example custom element will transform
|
75
|
+
# the text it contains. Since this is an end-to-end test
|
76
|
+
# the element should've done its thing and given us
|
77
|
+
# upper-case text.
|
78
|
+
expect(example).to have_text("WE LIKE THE WEB") }
|
79
|
+
),
|
80
|
+
MKBrut::Ops::AppendToFile.new(
|
81
|
+
file: @project_root / ".env.development",
|
82
|
+
content: %{
|
83
|
+
# Key used to allow triggering an exception. This is required to prevent
|
84
|
+
# just anyone from triggering one.
|
85
|
+
TRIGGER_EXCEPTION_KEY=dev-trigger-exception
|
86
|
+
}
|
87
|
+
),
|
88
|
+
MKBrut::Ops::AppendToFile.new(
|
89
|
+
file: @project_root / ".env.test",
|
90
|
+
content: "TRIGGER_EXCEPTION_KEY=test-trigger-exception"
|
91
|
+
),
|
92
|
+
|
93
|
+
]
|
94
|
+
end
|
95
|
+
class InsertCustomElement < MKBrut::Ops::BaseOp
|
96
|
+
def initialize(project_root:, element_class_name:)
|
97
|
+
@file = project_root / "app" / "src" / "front_end" / "js" / "index.js"
|
98
|
+
@element_class_name = element_class_name
|
99
|
+
end
|
100
|
+
def call
|
101
|
+
if dry_run?
|
102
|
+
puts "Would insert custom element '#{@element_class_name}' into #{@file}"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
inserted = false
|
106
|
+
new_source = []
|
107
|
+
File.read(@file).split("\n").each do |line|
|
108
|
+
regexp = /^document\.addEventListener\(\"DOMContentLoaded\"/
|
109
|
+
if line.match?(regexp)
|
110
|
+
new_source << %{import #{@element_class_name} from "./#{@element_class_name}"}
|
111
|
+
new_source << line
|
112
|
+
new_source << %{ #{@element_class_name}.define()}
|
113
|
+
inserted = true
|
114
|
+
else
|
115
|
+
new_source << line
|
116
|
+
end
|
117
|
+
end
|
118
|
+
if !inserted
|
119
|
+
raise "Could not find a place to insert code in '#{@file}'. Trying to find a line that matches this regular expression:\n\n#{regexp.inspect}"
|
120
|
+
end
|
121
|
+
File.open(@file, "w") do |file|
|
122
|
+
file.puts new_source.join("\n")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class InsertEndToEndTestCode < MKBrut::Ops::PrismParsingOp
|
128
|
+
def initialize(file:, code:)
|
129
|
+
@file = file
|
130
|
+
@code = code
|
131
|
+
end
|
132
|
+
def call
|
133
|
+
if dry_run?
|
134
|
+
puts "Would insert end-to-end test code into #{@file}:\n\n#{@code}\n"
|
135
|
+
return
|
136
|
+
end
|
137
|
+
parse_file!
|
138
|
+
|
139
|
+
found_describe = false
|
140
|
+
first_it_block = nil
|
141
|
+
|
142
|
+
@tree.value.statements.body.each do |top|
|
143
|
+
if top.is_a?(Prism::CallNode) &&
|
144
|
+
top.name == :describe &&
|
145
|
+
top.block
|
146
|
+
found_describe = true
|
147
|
+
|
148
|
+
statements = top.block.body
|
149
|
+
if statements.respond_to?(:body)
|
150
|
+
statements.body.each do |statement|
|
151
|
+
if statement.is_a?(Prism::CallNode) &&
|
152
|
+
statement.name == :it &&
|
153
|
+
statement.block
|
154
|
+
|
155
|
+
first_it_block = statement
|
156
|
+
break
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
if !first_it_block
|
164
|
+
if found_describe
|
165
|
+
raise "Could not find an 'it' block inside the first 'describe' in '#{@file}'"
|
166
|
+
else
|
167
|
+
raise "Could not find a 'describe' block in '#{@file}'"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
insertion_point = first_it_block.block.location.end_offset - 3
|
172
|
+
|
173
|
+
block_line = @source.lines[first_it_block.location.start_line - 1]
|
174
|
+
describe_indent = block_line[/^\s*/]
|
175
|
+
it_indent = describe_indent + " "
|
176
|
+
|
177
|
+
new_source = @source.dup.insert(insertion_point, "\n#{it_indent}#{@code}\n#{describe_indent}")
|
178
|
+
File.open(@file, "w") do |file|
|
179
|
+
file.puts new_source
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# Additional classes and configuration for a new Brut
|
2
|
+
# app that provides a more demonstration of the features.
|
3
|
+
class MKBrut::Segments::Demo < MKBrut::Base
|
4
|
+
|
5
|
+
def self.friendly_name = "Demo features and files"
|
6
|
+
|
7
|
+
def initialize(app_options:, current_dir:, templates_dir:)
|
8
|
+
@project_root = current_dir / app_options.app_name
|
9
|
+
@templates_dir = templates_dir / "segments" / "Demo"
|
10
|
+
@erb_binding = ErbBindingDelegate.new(app_options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add!
|
14
|
+
operations = copy_files(@templates_dir, @project_root) +
|
15
|
+
other_operations(@project_root)
|
16
|
+
|
17
|
+
operations.each do |operation|
|
18
|
+
operation.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def other_operations(project_root)
|
25
|
+
[
|
26
|
+
MKBrut::Ops::AddI18nMessage.new(
|
27
|
+
project_root: project_root,
|
28
|
+
hash: {
|
29
|
+
en: {
|
30
|
+
cv: {
|
31
|
+
be: {
|
32
|
+
not_enough_words: "%{field} does not have enough words",
|
33
|
+
already_posted: "You've already posted a message. Thanks for that!",
|
34
|
+
},
|
35
|
+
},
|
36
|
+
pages: {
|
37
|
+
"NewGuestbookMessagePage": {
|
38
|
+
title: "New guestbook message page",
|
39
|
+
},
|
40
|
+
"GuestbookPage": {
|
41
|
+
title: "Guestbook page",
|
42
|
+
},
|
43
|
+
},
|
44
|
+
guestbook_not_saved: "Your guestbook message was not saved",
|
45
|
+
guestbook_saved: "Thanks for writing!",
|
46
|
+
},
|
47
|
+
}
|
48
|
+
),
|
49
|
+
MKBrut::Ops::InsertRoute.new(
|
50
|
+
project_root: project_root,
|
51
|
+
code: %{page "/guestbook"},
|
52
|
+
),
|
53
|
+
MKBrut::Ops::InsertRoute.new(
|
54
|
+
project_root: project_root,
|
55
|
+
code: %{form "/guestbook_message"},
|
56
|
+
),
|
57
|
+
MKBrut::Ops::InsertRoute.new(
|
58
|
+
project_root: project_root,
|
59
|
+
code: %{page "/new_guestbook_message"},
|
60
|
+
),
|
61
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
62
|
+
file: project_root / "app" / "src" / "back_end" / "data_models" / "seed" / "seed_data.rb",
|
63
|
+
class_name: "SeedData",
|
64
|
+
method_name: "seed!",
|
65
|
+
code: %{
|
66
|
+
10.times do
|
67
|
+
create(:guestbook_message, created_at: Date.today - rand(1..100))
|
68
|
+
end
|
69
|
+
}
|
70
|
+
),
|
71
|
+
MKBrut::Ops::AddCSSImport.new(
|
72
|
+
project_root: project_root,
|
73
|
+
import: "constraint-violations.css"
|
74
|
+
),
|
75
|
+
MKBrut::Ops::InsertCodeInMethod.new(
|
76
|
+
file: project_root / "app" / "src" / "front_end" / "pages"/ "home_page.rb",
|
77
|
+
class_name: "HomePage",
|
78
|
+
method_name: "page_template",
|
79
|
+
where: :start,
|
80
|
+
code: %{
|
81
|
+
div(class: "w-50 mh-auto mt-4") do
|
82
|
+
a(href: NewGuestbookMessagePage.routing,
|
83
|
+
class: "db f-3 ff-sans blue-400 fw-bold tc"
|
84
|
+
) do
|
85
|
+
"Sign The Guestbook!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
89
|
+
),
|
90
|
+
MKBrut::Ops::AddMethod.new(
|
91
|
+
file: project_root / "app" / "src" / "front_end" / "support" / "app_session.rb",
|
92
|
+
class_name: "AppSession",
|
93
|
+
code: %{def signed_guestbook? = !!self.guestbook_message},
|
94
|
+
),
|
95
|
+
MKBrut::Ops::AddMethod.new(
|
96
|
+
file: project_root / "app" / "src" / "front_end" / "support" / "app_session.rb",
|
97
|
+
class_name: "AppSession",
|
98
|
+
code: %{
|
99
|
+
def guestbook_message
|
100
|
+
DB::GuestbookMessage.find(
|
101
|
+
external_id: self[:guestbook_message_external_id]
|
102
|
+
)
|
103
|
+
end
|
104
|
+
}
|
105
|
+
),
|
106
|
+
MKBrut::Ops::AddMethod.new(
|
107
|
+
file: project_root / "app" / "src" / "front_end" / "support" / "app_session.rb",
|
108
|
+
class_name: "AppSession",
|
109
|
+
code: %{
|
110
|
+
def signed_guestbook(guestbook_message)
|
111
|
+
self[:guestbook_message_external_id] = guestbook_message.external_id
|
112
|
+
end
|
113
|
+
}
|
114
|
+
),
|
115
|
+
]
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
class MKBrut::Versions
|
5
|
+
def initialize
|
6
|
+
@brut_version = Gem.loaded_specs["brut"].version.to_s
|
7
|
+
package_json_file = Pathname(Gem::Specification.find_by_name("mkbrut").gem_dir) / "package.json"
|
8
|
+
package_json = JSON.parse(File.read(package_json_file))
|
9
|
+
@brut_js_version = package_json["devDependencies"]["brut-js"]
|
10
|
+
@brut_css_version = package_json["devDependencies"]["brut-css"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def brut_version_specifier = "~> #{@brut_version}"
|
14
|
+
def brut_css_version_specifier = @brut_css_version
|
15
|
+
def brut_js_version_specifier = @brut_js_version
|
16
|
+
end
|
data/lib/mkbrut.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module MKBrut
|
2
|
+
autoload :App, "mkbrut/app"
|
3
|
+
autoload :AppId, "mkbrut/app_id"
|
4
|
+
autoload :AppName, "mkbrut/app_name"
|
5
|
+
autoload :AppOptions, "mkbrut/app_options"
|
6
|
+
autoload :Base, "mkbrut/base"
|
7
|
+
autoload :CLI, "mkbrut/cli"
|
8
|
+
autoload :ErbBindingDelegate, "mkbrut/erb_binding_delegate"
|
9
|
+
autoload :InternetIdentifier, "mkbrut/internet_identifier"
|
10
|
+
autoload :InvalidIdentifier, "mkbrut/invalid_identifier"
|
11
|
+
autoload :Ops, "mkbrut/ops"
|
12
|
+
autoload :Organization, "mkbrut/organization"
|
13
|
+
autoload :Prefix, "mkbrut/prefix"
|
14
|
+
autoload :Segments, "mkbrut/segments"
|
15
|
+
autoload :Versions, "mkbrut/versions"
|
16
|
+
autoload :PrefixedIO, "mkbrut/prefixed_io"
|
17
|
+
end
|