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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +51 -0
  3. data/exe/mkbrut +5 -0
  4. data/lib/mkbrut/app.rb +67 -0
  5. data/lib/mkbrut/app_id.rb +8 -0
  6. data/lib/mkbrut/app_name.rb +29 -0
  7. data/lib/mkbrut/app_options.rb +29 -0
  8. data/lib/mkbrut/base.rb +57 -0
  9. data/lib/mkbrut/cli.rb +91 -0
  10. data/lib/mkbrut/erb_binding_delegate.rb +20 -0
  11. data/lib/mkbrut/internet_identifier.rb +32 -0
  12. data/lib/mkbrut/invalid_identifier.rb +4 -0
  13. data/lib/mkbrut/ops/add_css_import.rb +42 -0
  14. data/lib/mkbrut/ops/add_i18n_message.rb +74 -0
  15. data/lib/mkbrut/ops/add_method.rb +48 -0
  16. data/lib/mkbrut/ops/append_to_file.rb +17 -0
  17. data/lib/mkbrut/ops/base_op.rb +21 -0
  18. data/lib/mkbrut/ops/copy_file.rb +12 -0
  19. data/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
  20. data/lib/mkbrut/ops/insert_route.rb +52 -0
  21. data/lib/mkbrut/ops/mkdir.rb +13 -0
  22. data/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
  23. data/lib/mkbrut/ops/render_template.rb +26 -0
  24. data/lib/mkbrut/ops/skip_file.rb +10 -0
  25. data/lib/mkbrut/ops.rb +16 -0
  26. data/lib/mkbrut/organization.rb +5 -0
  27. data/lib/mkbrut/prefix.rb +26 -0
  28. data/lib/mkbrut/prefixed_io.rb +16 -0
  29. data/lib/mkbrut/segments/bare_bones.rb +184 -0
  30. data/lib/mkbrut/segments/demo.rb +117 -0
  31. data/lib/mkbrut/segments/sidekiq.rb +3 -0
  32. data/lib/mkbrut/segments.rb +7 -0
  33. data/lib/mkbrut/version.rb +3 -0
  34. data/lib/mkbrut/versions.rb +16 -0
  35. data/lib/mkbrut.rb +17 -0
  36. data/templates/Base/Dockerfile.dx +205 -0
  37. data/templates/Base/Gemfile.erb +53 -0
  38. data/templates/Base/Procfile.development +4 -0
  39. data/templates/Base/Procfile.test +1 -0
  40. data/templates/Base/README.md +4 -0
  41. data/templates/Base/README.md.erb +40 -0
  42. data/templates/Base/app/bootstrap.rb +61 -0
  43. data/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
  44. data/templates/Base/app/config/i18n/en/2_app.rb +24 -0
  45. data/templates/Base/app/public/static/manifest.json.erb +33 -0
  46. data/templates/Base/app/src/app.rb.erb +37 -0
  47. data/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
  48. data/templates/Base/app/src/back_end/data_models/db.rb +19 -0
  49. data/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
  50. data/templates/Base/app/src/front_end/components/app_component.rb +8 -0
  51. data/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
  52. data/templates/Base/app/src/front_end/css/fonts.css +19 -0
  53. data/templates/Base/app/src/front_end/css/index.css +3 -0
  54. data/templates/Base/app/src/front_end/css/svgs.css +12 -0
  55. data/templates/Base/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
  56. data/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
  57. data/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
  58. data/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
  59. data/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
  60. data/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
  61. data/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
  62. data/templates/Base/app/src/front_end/images/favicon.ico +0 -0
  63. data/templates/Base/app/src/front_end/images/icon.png +0 -0
  64. data/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
  65. data/templates/Base/app/src/front_end/js/index.js +6 -0
  66. data/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +76 -0
  67. data/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
  68. data/templates/Base/app/src/front_end/pages/home_page.rb.erb +54 -0
  69. data/templates/Base/app/src/front_end/support/app_session.rb +6 -0
  70. data/templates/Base/app/src/front_end/svgs/README.md +5 -0
  71. data/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
  72. data/templates/Base/bin/README.md.erb +5 -0
  73. data/templates/Base/bin/build-assets +7 -0
  74. data/templates/Base/bin/ci +39 -0
  75. data/templates/Base/bin/console +31 -0
  76. data/templates/Base/bin/db +9 -0
  77. data/templates/Base/bin/dbconsole +51 -0
  78. data/templates/Base/bin/dev +25 -0
  79. data/templates/Base/bin/release +26 -0
  80. data/templates/Base/bin/run +86 -0
  81. data/templates/Base/bin/scaffold +9 -0
  82. data/templates/Base/bin/setup +256 -0
  83. data/templates/Base/bin/test +9 -0
  84. data/templates/Base/bin/test-server +29 -0
  85. data/templates/Base/bin/watch-and-build-assets +37 -0
  86. data/templates/Base/config.ru +16 -0
  87. data/templates/Base/docker-compose.dx.yml +85 -0
  88. data/templates/Base/dx/README.md +28 -0
  89. data/templates/Base/dx/bash_customizations +12 -0
  90. data/templates/Base/dx/bash_customizations.local +4 -0
  91. data/templates/Base/dx/build +101 -0
  92. data/templates/Base/dx/docker-compose.env.erb +25 -0
  93. data/templates/Base/dx/dx.sh.lib +137 -0
  94. data/templates/Base/dx/exec +56 -0
  95. data/templates/Base/dx/prune +19 -0
  96. data/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
  97. data/templates/Base/dx/start +30 -0
  98. data/templates/Base/dx/stop +23 -0
  99. data/templates/Base/package.json.erb +37 -0
  100. data/templates/Base/puma.config.rb +53 -0
  101. data/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
  102. data/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
  103. data/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
  104. data/templates/Base/specs/lint_factories.spec.rb +7 -0
  105. data/templates/Base/specs/spec_helper.rb +78 -0
  106. data/templates/Base/specs/support.rb +2 -0
  107. data/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
  108. data/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
  109. data/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
  110. data/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
  111. data/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
  112. data/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +15 -0
  113. data/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
  114. data/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
  115. data/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
  116. data/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
  117. data/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
  118. data/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
  119. data/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
  120. data/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
  121. data/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
  122. data/templates/segments/Demo/specs/factories/db/guestbook_message.rb +7 -0
  123. data/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
  124. data/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
  125. data/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
  126. data/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
  127. data/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
  128. 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,13 @@
1
+ require "fileutils"
2
+
3
+ class MKBrut::Ops::Mkdir < MKBrut::Ops::BaseOp
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def call
9
+ FileUtils.mkdir_p(@path, **fileutils_args)
10
+ end
11
+
12
+ def to_s = "Make Dir '#{@path}'"
13
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ class MKBrut::Ops::SkipFile
2
+ def initialize(source)
3
+ @source = source
4
+ end
5
+
6
+ def call
7
+ end
8
+
9
+ def to_s = "Skip #{@source}"
10
+ 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,5 @@
1
+ class MKBrut::Organization < MKBrut::InternetIdentifier
2
+ def initialize(value)
3
+ super(:organization, value)
4
+ end
5
+ 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,16 @@
1
+ require "delegate"
2
+ class PrefixedIO < Delegator
3
+ def initialize(io, prefix)
4
+ @io = io
5
+ @prefix = "[ #{prefix} ] "
6
+ end
7
+
8
+ def __getobj__ = @io
9
+
10
+ def puts(*args)
11
+ args.each do |arg|
12
+ @io.puts(@prefix + arg.to_s)
13
+ end
14
+ end
15
+
16
+ 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,3 @@
1
+ # Adds Sidekiq to a Brut app
2
+ class MKBrut::Segments::Sidekiq
3
+ end
@@ -0,0 +1,7 @@
1
+ module MKBrut
2
+ module Segments
3
+ autoload :BareBones, "mkbrut/segments/bare_bones"
4
+ autoload :Demo, "mkbrut/segments/demo"
5
+ autoload :Sidekiq, "mkbrut/segments/demo"
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module MKBrut
2
+ VERSION = "0.1.0"
3
+ 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