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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d93e08e9f662468dd47a79c11c2afa36144905bfdf52e8fe54d209f99940557
|
4
|
+
data.tar.gz: 5911070cfefab0d42f46d19c5676bdce3f6bc0c63d386adab09d1995778f274c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: efae7e39b7ebe46371308cd83098d26a0ac12c637e76988b37ccc43c4271b58d74a1575f52ee451e821a3b410b2dad268807bafac5d7797aa99d6a4c63705e88
|
7
|
+
data.tar.gz: 8ec79d1512d8552e72e86534f72cd9750c3fc236a3224a98b195b062f1fffa90ad56eaafd5f2d0b3a6aa877296dfe6674a212fb76bbd125a43ab1d475e81df1d
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# mkbrut - Create a new Brut App
|
2
|
+
|
3
|
+
`mkbrut` is how you go from zero to having a Brut app where you can start working.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
TBD
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```
|
12
|
+
mkbrut my-new-app
|
13
|
+
```
|
14
|
+
|
15
|
+
This will create a new Brut app, including a development environment. The app will
|
16
|
+
have some demo features to show you around the framework.
|
17
|
+
|
18
|
+
To get a bare bones new app, use `--no-demo`:
|
19
|
+
|
20
|
+
```
|
21
|
+
mkbrut my-new-app --no-demo
|
22
|
+
```
|
23
|
+
|
24
|
+
You can also customize some aspects of your app once start to have an opinion about
|
25
|
+
it:
|
26
|
+
|
27
|
+
|
28
|
+
```
|
29
|
+
mkbrut --app-id=new-app \
|
30
|
+
--organization=cyberdyne \
|
31
|
+
--prefix=ap \
|
32
|
+
my-new-app
|
33
|
+
```
|
34
|
+
|
35
|
+
* `--app-id` The identifier for your app, suitable for use as a hostname or other internet-safe identifier.
|
36
|
+
* `--organization` This is your organization name you might use on GitHub, DockerHub, or WhateverHub.
|
37
|
+
* `--prefix` The two-character prefix for all external IDs of your database tables that opt into external IDs as well as any autonomous custom elements you might make
|
38
|
+
|
39
|
+
## Developing
|
40
|
+
|
41
|
+
`mkbrut` has a Docker-based dev environment:
|
42
|
+
|
43
|
+
1. Install Docker
|
44
|
+
2. `dx/build`
|
45
|
+
3. `dx/start`
|
46
|
+
4. Open a new terminal:
|
47
|
+
1. `dx/exec bash`
|
48
|
+
2. You are now inside a running Docker container:
|
49
|
+
1. `bin/setup`
|
50
|
+
2. `bundle exec exe/mkbrut -h`
|
51
|
+
|
data/exe/mkbrut
ADDED
data/lib/mkbrut/app.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "erb"
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module MKBrut
|
6
|
+
class App
|
7
|
+
def initialize(current_dir:, app_options:, out:, err:)
|
8
|
+
@out = out
|
9
|
+
@app_options = app_options
|
10
|
+
|
11
|
+
@out.puts "Creating app with these options:\n"
|
12
|
+
@out.puts "App name: #{app_options.app_name}"
|
13
|
+
@out.puts "App ID: #{app_options.app_id}"
|
14
|
+
@out.puts "Prefix: #{app_options.prefix}"
|
15
|
+
@out.puts "Organization: #{app_options.organization}"
|
16
|
+
@out.puts "Include demo? #{app_options.demo}\n"
|
17
|
+
|
18
|
+
if app_options.dry_run?
|
19
|
+
@out.puts "Dry Run"
|
20
|
+
MKBrut::Ops::BaseOp.dry_run = true
|
21
|
+
end
|
22
|
+
|
23
|
+
templates_dir = Pathname(
|
24
|
+
Gem::Specification.find_by_name("mkbrut").gem_dir
|
25
|
+
) / "templates"
|
26
|
+
|
27
|
+
@base = MKBrut::Base.new(
|
28
|
+
app_options:,
|
29
|
+
current_dir:,
|
30
|
+
templates_dir:
|
31
|
+
)
|
32
|
+
@segments = [
|
33
|
+
MKBrut::Segments::BareBones.new(
|
34
|
+
app_options:,
|
35
|
+
current_dir:,
|
36
|
+
templates_dir:,
|
37
|
+
)
|
38
|
+
]
|
39
|
+
if app_options.demo?
|
40
|
+
@segments << MKBrut::Segments::Demo.new(
|
41
|
+
app_options:,
|
42
|
+
current_dir:,
|
43
|
+
templates_dir:
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def create!
|
49
|
+
@out.puts "Creating Base app"
|
50
|
+
@base.create!
|
51
|
+
@segments.each do |segment|
|
52
|
+
@out.puts "Creating segment: #{segment.class.friendly_name}"
|
53
|
+
segment.add!
|
54
|
+
end
|
55
|
+
@out.puts "#{@app_options.app_name} was created\n\n"
|
56
|
+
@out.puts "Time to get building:"
|
57
|
+
@out.puts "1. cd #{@app_options.app_name}"
|
58
|
+
@out.puts "2. dx/build"
|
59
|
+
@out.puts "3. dx/start"
|
60
|
+
@out.puts "4. [ in another terminal ] dx/exec bash"
|
61
|
+
@out.puts "5. [ inside the Docker container ] bin/setup"
|
62
|
+
@out.puts "6. [ inside the Docker container ] bin/dev"
|
63
|
+
@out.puts "7. Visit http://localhost:6502 in your browser"
|
64
|
+
@out.puts "8. [ inside the Docker container ] bin/setup help # to see more commands"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MKBrut
|
2
|
+
class AppName
|
3
|
+
def initialize(value)
|
4
|
+
identifier = value.to_s
|
5
|
+
if identifier.empty?
|
6
|
+
raise MKBrut::InvalidIdentifier, "app-name is required"
|
7
|
+
end
|
8
|
+
|
9
|
+
if identifier.length > 63
|
10
|
+
raise MKBrut::InvalidIdentifier, "app-name cannot be longer than 63 characters"
|
11
|
+
end
|
12
|
+
|
13
|
+
if identifier.start_with?("-") || identifier.end_with?("-")
|
14
|
+
raise MKBrut::InvalidIdentifier, "app-name cannot start or end with a hyphen"
|
15
|
+
end
|
16
|
+
|
17
|
+
if identifier.match?(/[^a-zA-Z\-_]/)
|
18
|
+
raise MKBrut::InvalidIdentifier, "app-name can only contain letters, hyphens, and underscores"
|
19
|
+
end
|
20
|
+
if identifier.match?(/__/) || identifier.match?(/--/)
|
21
|
+
raise MKBrut::InvalidIdentifier, "app-name can not have repeating underscores or hyphens"
|
22
|
+
end
|
23
|
+
@identifier = identifier.to_s.gsub(/_/,"-")
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s = @identifier
|
27
|
+
alias to_str to_s
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class MKBrut::AppOptions
|
2
|
+
attr_reader :app_name, :app_id, :prefix, :organization, :demo, :versions
|
3
|
+
|
4
|
+
def initialize(
|
5
|
+
app_name:,
|
6
|
+
app_id: nil,
|
7
|
+
prefix: nil,
|
8
|
+
dry_run: nil,
|
9
|
+
organization: nil,
|
10
|
+
demo: false,
|
11
|
+
versions: nil,
|
12
|
+
**ignore
|
13
|
+
)
|
14
|
+
if app_name.nil?
|
15
|
+
raise ArgumentError, "app_name is required"
|
16
|
+
end
|
17
|
+
|
18
|
+
@app_name = app_name
|
19
|
+
@app_id = app_id || MKBrut::AppId.from_app_name(@app_name)
|
20
|
+
@prefix = prefix || MKBrut::Prefix.from_app_id(@app_id)
|
21
|
+
@organization = organization || @app_id
|
22
|
+
@dry_run = !!dry_run
|
23
|
+
@demo = !!demo
|
24
|
+
@versions = versions
|
25
|
+
end
|
26
|
+
|
27
|
+
def dry_run? = @dry_run
|
28
|
+
def demo? = @demo
|
29
|
+
end
|
data/lib/mkbrut/base.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "securerandom"
|
3
|
+
# Constructs the base of any Brut app.
|
4
|
+
class MKBrut::Base
|
5
|
+
include MKBrut
|
6
|
+
|
7
|
+
class ErbBinding < MKBrut::ErbBindingDelegate
|
8
|
+
def session_secret = SecureRandom.hex(64)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(app_options:, current_dir:, templates_dir:)
|
12
|
+
@project_root = current_dir / app_options.app_name
|
13
|
+
@templates_dir = templates_dir / "Base"
|
14
|
+
@erb_binding = ErbBinding.new(app_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create!
|
18
|
+
if @project_root.exist?
|
19
|
+
raise "Project root #{@project_root} already exists"
|
20
|
+
end
|
21
|
+
operations = [ Ops::Mkdir.new(@project_root) ] +
|
22
|
+
copy_files(@templates_dir, @project_root)
|
23
|
+
|
24
|
+
operations.each do |operation|
|
25
|
+
operation.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def filenames_to_always_skip = [ "README.md", "mkicons.sh" ]
|
32
|
+
|
33
|
+
def copy_files(source_dir, destination_root)
|
34
|
+
operations = []
|
35
|
+
Dir.glob("#{source_dir}/*", flags: File::FNM_DOTMATCH).each do |template_file|
|
36
|
+
template_file = Pathname(template_file)
|
37
|
+
if [ ".", ".." ].include?(template_file.basename.to_s)
|
38
|
+
next
|
39
|
+
end
|
40
|
+
if template_file.directory?
|
41
|
+
operations << Ops::Mkdir.new(destination_root / template_file.basename)
|
42
|
+
operations += copy_files(template_file, destination_root / template_file.basename)
|
43
|
+
elsif template_file.extname == ".erb"
|
44
|
+
operations << Ops::RenderTemplate.new(
|
45
|
+
template_file,
|
46
|
+
destination_root:,
|
47
|
+
erb_binding: @erb_binding
|
48
|
+
)
|
49
|
+
elsif filenames_to_always_skip.include?(template_file.basename.to_s)
|
50
|
+
operations << Ops::SkipFile.new(template_file)
|
51
|
+
else
|
52
|
+
operations << Ops::CopyFile.new(template_file, destination_root:)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
operations
|
56
|
+
end
|
57
|
+
end
|
data/lib/mkbrut/cli.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module MKBrut
|
5
|
+
class CLI
|
6
|
+
def initialize(args:, out: $stdout, err: $stderr)
|
7
|
+
@args = args
|
8
|
+
@out = out
|
9
|
+
@err = err
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
|
14
|
+
app_options = parse_options(@args, MKBrut::Versions.new)
|
15
|
+
new_app = MKBrut::App.new(
|
16
|
+
current_dir: Pathname.pwd.expand_path,
|
17
|
+
app_options:,
|
18
|
+
out: PrefixedIO.new(@out, "mkbrut"),
|
19
|
+
err: @err
|
20
|
+
)
|
21
|
+
new_app.create!
|
22
|
+
0
|
23
|
+
rescue => e
|
24
|
+
@err.puts "Error: #{e.message}"
|
25
|
+
if ENV["BRUT_CLI_RAISE_ON_ERROR"] == "true"
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
1
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_help
|
32
|
+
@out.puts @option_parser
|
33
|
+
@out.puts
|
34
|
+
@out.puts "ARGUMENTS"
|
35
|
+
@out.puts
|
36
|
+
@out.puts " app-name - name for your app, which will be the folder where your app's files are created"
|
37
|
+
@out.puts
|
38
|
+
@out.puts "ENVIRONMENT VARIABLES"
|
39
|
+
@out.puts
|
40
|
+
@out.puts " BRUT_CLI_RAISE_ON_ERROR - if set to 'true', any error will raise an exception instead of printing to stderr"
|
41
|
+
@out.puts
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
|
48
|
+
def parse_options(args, versions)
|
49
|
+
options = {}
|
50
|
+
@option_parser = OptionParser.new do |opts|
|
51
|
+
opts.accept(MKBrut::Prefix) do |prefix|
|
52
|
+
MKBrut::Prefix.new(prefix)
|
53
|
+
end
|
54
|
+
opts.accept(MKBrut::AppId) do |prefix|
|
55
|
+
MKBrut::AppId.new(prefix)
|
56
|
+
end
|
57
|
+
opts.accept(MKBrut::Organization) do |prefix|
|
58
|
+
MKBrut::Organization.new(prefix)
|
59
|
+
end
|
60
|
+
opts.banner = "Usage: mkbrut [options] app-name\n\n Creates a new Brut-powered app\n\nOPTIONS\n\n"
|
61
|
+
|
62
|
+
opts.on("-a", "--app-id=ID", MKBrut::AppId,
|
63
|
+
"App identifier, which must be able to be used as a hostname or other Internet identifier. Derived from your app name, if omitted")
|
64
|
+
|
65
|
+
opts.on("-o", "--organization=ORG",MKBrut::Organization,
|
66
|
+
"Organization name, e.g. what you'd use for GitHub. Defaults to the app-id value")
|
67
|
+
|
68
|
+
opts.on("-e", "--prefix=PREFIX", MKBrut::Prefix,
|
69
|
+
"Two-character prefix for external IDs and autonomous custom elements. Derived from your app-id, if omitted.")
|
70
|
+
|
71
|
+
opts.on("--dry-run", "Only show what would happen, don't actually do anything")
|
72
|
+
opts.on("--[no-]demo", "Include, or not, additional files that demonstrate Brut's features (default is true for now")
|
73
|
+
opts.on("-h", "--help", "Show this help message") do
|
74
|
+
show_help
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@option_parser.parse!(args, into: options)
|
80
|
+
if !options.key?(:demo)
|
81
|
+
options[:demo] = true
|
82
|
+
end
|
83
|
+
|
84
|
+
options[:app_name] = MKBrut::AppName.new(args.first)
|
85
|
+
options[:app_id] = options[:'app-id']
|
86
|
+
options[:dry_run] = !!options[:'dry-run']
|
87
|
+
MKBrut::AppOptions.new(**options.merge(versions:))
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This exists because ERB can't working with a SimpleDelegator or
|
2
|
+
# Delegate.
|
3
|
+
class MKBrut::ErbBindingDelegate
|
4
|
+
def initialize(app_options)
|
5
|
+
@app_options = app_options
|
6
|
+
end
|
7
|
+
|
8
|
+
# Not using Delegate because it won't work with ERB binding
|
9
|
+
def method_missing(syn,*args,&block)
|
10
|
+
if args.empty? && @app_options.respond_to?(syn)
|
11
|
+
@app_options.send(syn)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(syn,include_all)
|
18
|
+
@app_options.respond_to?(syn)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MKBrut
|
2
|
+
class InternetIdentifier
|
3
|
+
def initialize(name, value)
|
4
|
+
@name = name
|
5
|
+
@identifier = value.to_s
|
6
|
+
validate_identifier
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s = @identifier
|
10
|
+
alias to_str to_s
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def validate_identifier
|
15
|
+
if @identifier.empty?
|
16
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot be empty"
|
17
|
+
end
|
18
|
+
|
19
|
+
if @identifier.length > 63
|
20
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot be longer than 63 characters"
|
21
|
+
end
|
22
|
+
|
23
|
+
if @identifier.start_with?("-") || @identifier.end_with?("-")
|
24
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot start or end with a hyphen"
|
25
|
+
end
|
26
|
+
|
27
|
+
if @identifier.match?(/[^a-zA-Z0-9-]/)
|
28
|
+
raise MKBrut::InvalidIdentifier, "#{@name} can only contain letters, numbers, and hyphens"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class MKBrut::Ops::AddCSSImport < MKBrut::Ops::BaseOp
|
2
|
+
def initialize(project_root:, import:)
|
3
|
+
@file = project_root / "app" / "src" / "front_end" / "css" / "index.css"
|
4
|
+
@import = import
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
if dry_run?
|
9
|
+
puts "Would add import '#{@import}'; to '#{@file}'"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
contents = File.read(@file).split(/\n/)
|
14
|
+
|
15
|
+
inserted_import = false
|
16
|
+
previous_line_was_import = false
|
17
|
+
new_contents = []
|
18
|
+
contents.each do |line|
|
19
|
+
if line =~ /^\s*@import\s+["']/
|
20
|
+
previous_line_was_import = true
|
21
|
+
new_contents << line
|
22
|
+
else
|
23
|
+
if previous_line_was_import && !inserted_import
|
24
|
+
new_contents << "@import '#{@import}';"
|
25
|
+
inserted_import = true
|
26
|
+
end
|
27
|
+
previous_line_was_import = false
|
28
|
+
new_contents << line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if !inserted_import && previous_line_was_import
|
32
|
+
new_contents << "@import \"#{@import}\";"
|
33
|
+
inserted_import = true
|
34
|
+
end
|
35
|
+
if !inserted_import
|
36
|
+
raise "Did not find any other @imports in '#{@file}' - was expecting at least one to exist"
|
37
|
+
end
|
38
|
+
File.open(@file, "w") do |file|
|
39
|
+
file.puts new_contents.join("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class MKBrut::Ops::AddI18nMessage < MKBrut::Ops::PrismParsingOp
|
2
|
+
def initialize(project_root:, hash:)
|
3
|
+
@file = project_root / "app" / "config" / "i18n" / "en" / "2_app.rb"
|
4
|
+
@hash = hash
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
if dry_run?
|
9
|
+
puts "Would merge:\n#{@hash}\ninto #{@file}"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
parse_file!
|
13
|
+
|
14
|
+
hash_node = @tree.value.statements.body.detect { it.is_a?(Prism::HashNode) }
|
15
|
+
if !hash_node
|
16
|
+
raise "'#{@file}' did not have a hash node, so we cannot insert a new i18n message"
|
17
|
+
end
|
18
|
+
|
19
|
+
# eval the source to get a real hash of the contents
|
20
|
+
start_offset = hash_node.location.start_offset
|
21
|
+
end_offset = hash_node.location.end_offset
|
22
|
+
original_code = @source[start_offset...end_offset]
|
23
|
+
original_hash = eval(original_code, binding, @file.to_s)
|
24
|
+
|
25
|
+
new_hash = deep_merge(original_hash,@hash)
|
26
|
+
|
27
|
+
formatted_hash = format_hash(new_hash)
|
28
|
+
|
29
|
+
new_source = @source.dup
|
30
|
+
new_source[start_offset...end_offset] = formatted_hash
|
31
|
+
|
32
|
+
File.open(@file, "w") do |file|
|
33
|
+
file.puts new_source
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def deep_merge(a, b)
|
40
|
+
a.merge(b) do |_key, old_val, new_val|
|
41
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
42
|
+
deep_merge(old_val, new_val)
|
43
|
+
else
|
44
|
+
new_val
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# NASTY, but not currently sure a better what do it.
|
50
|
+
def format_hash(hash, trailing_comma = "", indent = "")
|
51
|
+
string = "{\n"
|
52
|
+
hash.each do |key, value|
|
53
|
+
key_code = if key.kind_of?(Symbol)
|
54
|
+
if key =~ /^[A-Za-z_][A-Za-z0-9_]*$/
|
55
|
+
"#{key}:"
|
56
|
+
else
|
57
|
+
"'#{key}':"
|
58
|
+
end
|
59
|
+
else
|
60
|
+
"#{key} =>"
|
61
|
+
end
|
62
|
+
value_code = case value
|
63
|
+
when String
|
64
|
+
then "\"#{value}\",\n"
|
65
|
+
when Hash
|
66
|
+
format_hash(value, ",", indent + " ")
|
67
|
+
end
|
68
|
+
string << "#{indent} #{key_code} #{value_code}"
|
69
|
+
end
|
70
|
+
string << "#{indent}}#{trailing_comma}\n"
|
71
|
+
string
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class MKBrut::Ops::AddMethod < MKBrut::Ops::PrismParsingOp
|
2
|
+
def initialize(file:, class_name:, code:)
|
3
|
+
@file = file
|
4
|
+
@class_name = class_name
|
5
|
+
@code = code.gsub(/^\n\s*$/,"").gsub(/\n$/,"")
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
if dry_run?
|
10
|
+
puts "Would add method:\n#{@code}\nto #{@class_name} in '#{@file}'"
|
11
|
+
return
|
12
|
+
end
|
13
|
+
class_node = find_class(class_name: @class_name, assumed_body: false)
|
14
|
+
|
15
|
+
insert_offset = nil
|
16
|
+
class_body_nodes = case class_node.body
|
17
|
+
when Prism::StatementsNode
|
18
|
+
class_node.body.body
|
19
|
+
when nil
|
20
|
+
[]
|
21
|
+
else
|
22
|
+
[class_node.body]
|
23
|
+
end
|
24
|
+
|
25
|
+
class_body_nodes.each do |node|
|
26
|
+
if node.is_a?(Prism::CallNode) && node.name == "private"
|
27
|
+
insert_offset = node.location.start_offset
|
28
|
+
break
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if insert_offset.nil?
|
33
|
+
# Use the final end of the class
|
34
|
+
insert_offset = class_node.location.end_offset - 3
|
35
|
+
end
|
36
|
+
|
37
|
+
class_start_line = class_node.location.start_line
|
38
|
+
class_indent = @source.lines[class_start_line - 1][/^\s*/] || ""
|
39
|
+
method_indent = class_indent + " "
|
40
|
+
|
41
|
+
indented_method_code = @code.lines.map { |line| method_indent + line }.join
|
42
|
+
insert_text = "\n" + indented_method_code + "\n"
|
43
|
+
|
44
|
+
updated_source = @source.dup.insert(insert_offset, insert_text)
|
45
|
+
File.write(@file, updated_source)
|
46
|
+
updated_source
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class MKBrut::Ops::AppendToFile < MKBrut::Ops::BaseOp
|
2
|
+
def initialize(file:, content:)
|
3
|
+
@file = file
|
4
|
+
@content = content
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
if dry_run?
|
9
|
+
puts "Would append to #{@file}:\n#{@content}\n"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
File.open(@file, "a") do |file|
|
14
|
+
file.puts @content
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class MKBrut::Ops::BaseOp
|
2
|
+
@dry_run = false
|
3
|
+
|
4
|
+
def self.dry_run=(value)
|
5
|
+
MKBrut::Ops::BaseOp.instance_variable_set(:@dry_run, value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.dry_run? = !!MKBrut::Ops::BaseOp.instance_variable_get(:@dry_run)
|
9
|
+
def dry_run? = self.class.dry_run?
|
10
|
+
|
11
|
+
def call = raise "Subclass must implement"
|
12
|
+
|
13
|
+
def self.fileutils_args
|
14
|
+
if self.dry_run?
|
15
|
+
{ noop: true, verbose: true }
|
16
|
+
else
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def fileutils_args = self.class.fileutils_args
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
class MKBrut::Ops::CopyFile < MKBrut::Ops::BaseOp
|
4
|
+
def initialize(source, destination_root:)
|
5
|
+
@source = source
|
6
|
+
@destination_root = destination_root
|
7
|
+
end
|
8
|
+
def call
|
9
|
+
FileUtils.cp(@source, @destination_root / @source.basename, **fileutils_args)
|
10
|
+
end
|
11
|
+
def to_s = "Copy '#{@source}' to '#{@destination_root}'"
|
12
|
+
end
|