charming 0.1.1 → 0.1.2
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 +4 -4
- data/README.md +2 -2
- data/lib/charming/application.rb +11 -0
- data/lib/charming/cli.rb +23 -0
- data/lib/charming/controller/class_methods.rb +115 -0
- data/lib/charming/controller/command_palette.rb +135 -0
- data/lib/charming/controller/component_dispatching.rb +81 -0
- data/lib/charming/controller/dispatching.rb +60 -0
- data/lib/charming/controller/focus_management.rb +30 -0
- data/lib/charming/controller/rendering.rb +127 -0
- data/lib/charming/controller/session_state.rb +41 -0
- data/lib/charming/controller/sidebar_navigation.rb +111 -0
- data/lib/charming/controller.rb +35 -559
- data/lib/charming/database_commands.rb +16 -0
- data/lib/charming/database_installer.rb +27 -0
- data/lib/charming/focus.rb +58 -2
- data/lib/charming/generators/app_file_generator.rb +13 -0
- data/lib/charming/generators/app_generator.rb +123 -47
- data/lib/charming/generators/base.rb +26 -0
- data/lib/charming/generators/component_generator.rb +10 -10
- data/lib/charming/generators/controller_generator.rb +22 -11
- data/lib/charming/generators/model_generator.rb +38 -29
- data/lib/charming/generators/name.rb +10 -0
- data/lib/charming/generators/screen_generator.rb +78 -32
- data/lib/charming/generators/templates/app/Gemfile.template +5 -0
- data/lib/charming/generators/templates/app/README.md.template +9 -0
- data/lib/charming/generators/templates/app/Rakefile.template +3 -0
- data/lib/charming/generators/templates/app/application.template +13 -0
- data/lib/charming/generators/templates/app/application_controller.template +19 -0
- data/lib/charming/generators/templates/app/application_record.template +7 -0
- data/lib/charming/generators/templates/app/application_state.template +6 -0
- data/lib/charming/generators/templates/app/database_config.template +12 -0
- data/lib/charming/generators/templates/app/executable.template +7 -0
- data/lib/charming/generators/templates/app/gemspec.template +6 -0
- data/lib/charming/generators/templates/app/home_controller.template +6 -0
- data/lib/charming/generators/templates/app/home_state.template +7 -0
- data/lib/charming/generators/templates/app/keep.template +0 -0
- data/lib/charming/generators/templates/app/layout.template +113 -0
- data/lib/charming/generators/templates/app/root_file.template +20 -0
- data/lib/charming/generators/templates/app/routes.template +5 -0
- data/lib/charming/generators/templates/app/seeds.template +1 -0
- data/lib/charming/generators/templates/app/spec_controller.template +17 -0
- data/lib/charming/generators/templates/app/spec_helper.template +3 -0
- data/lib/charming/generators/templates/app/spec_state.template +17 -0
- data/lib/charming/generators/templates/app/spec_view.template +16 -0
- data/lib/charming/generators/templates/app/version.template +5 -0
- data/lib/charming/generators/templates/app/view.template +21 -0
- data/lib/charming/generators/templates/component/component.rb.template +9 -0
- data/lib/charming/generators/templates/controller/controller.rb.template +6 -0
- data/lib/charming/generators/templates/model/migration.rb.template +9 -0
- data/lib/charming/generators/templates/model/model.rb.template +6 -0
- data/lib/charming/generators/templates/model/spec.rb.template +9 -0
- data/lib/charming/generators/templates/screen/controller.rb.template +7 -0
- data/lib/charming/generators/templates/screen/spec_controller.rb.template +17 -0
- data/lib/charming/generators/templates/screen/spec_state.rb.template +17 -0
- data/lib/charming/generators/templates/screen/spec_view.rb.template +13 -0
- data/lib/charming/generators/templates/screen/state.rb.template +7 -0
- data/lib/charming/generators/templates/screen/view.rb.template +11 -0
- data/lib/charming/generators/templates/view/view.rb.template +11 -0
- data/lib/charming/generators/view_generator.rb +19 -3
- data/lib/charming/internal/renderer/differential.rb +15 -0
- data/lib/charming/internal/renderer/full_repaint.rb +6 -0
- data/lib/charming/internal/terminal/adapter.rb +29 -3
- data/lib/charming/internal/terminal/key_normalizer.rb +84 -0
- data/lib/charming/internal/terminal/memory_backend.rb +28 -1
- data/lib/charming/internal/terminal/mouse_parser.rb +81 -0
- data/lib/charming/internal/terminal/tty_backend.rb +43 -113
- data/lib/charming/presentation/components/empty_state.rb +13 -0
- data/lib/charming/presentation/components/form/builder.rb +14 -0
- data/lib/charming/presentation/components/form/confirm.rb +13 -0
- data/lib/charming/presentation/components/form/field.rb +25 -0
- data/lib/charming/presentation/components/form/input.rb +14 -0
- data/lib/charming/presentation/components/form/note.rb +9 -0
- data/lib/charming/presentation/components/form/select.rb +23 -0
- data/lib/charming/presentation/components/form/textarea.rb +16 -0
- data/lib/charming/presentation/components/form.rb +29 -0
- data/lib/charming/presentation/components/list.rb +28 -0
- data/lib/charming/presentation/components/markdown.rb +6 -0
- data/lib/charming/presentation/components/modal.rb +14 -0
- data/lib/charming/presentation/components/progressbar.rb +13 -0
- data/lib/charming/presentation/components/spinner.rb +10 -0
- data/lib/charming/presentation/components/table.rb +25 -0
- data/lib/charming/presentation/components/text_area.rb +48 -0
- data/lib/charming/presentation/components/text_input.rb +24 -0
- data/lib/charming/presentation/components/viewport.rb +52 -0
- data/lib/charming/presentation/layout/builder.rb +86 -0
- data/lib/charming/presentation/layout/overlay.rb +57 -0
- data/lib/charming/presentation/layout/pane.rb +145 -0
- data/lib/charming/presentation/layout/rect.rb +23 -0
- data/lib/charming/presentation/layout/screen_layout.rb +60 -0
- data/lib/charming/presentation/layout/split.rb +134 -0
- data/lib/charming/presentation/markdown/block_renderers.rb +120 -0
- data/lib/charming/presentation/markdown/inline_renderers.rb +68 -0
- data/lib/charming/presentation/markdown/render_context.rb +22 -0
- data/lib/charming/presentation/markdown/renderer.rb +45 -135
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +16 -0
- data/lib/charming/presentation/markdown.rb +3 -0
- data/lib/charming/presentation/template_view.rb +7 -0
- data/lib/charming/presentation/templates.rb +17 -0
- data/lib/charming/presentation/ui/ansi_codes.rb +89 -0
- data/lib/charming/presentation/ui/ansi_slicer.rb +94 -0
- data/lib/charming/presentation/ui/border_painter.rb +58 -0
- data/lib/charming/presentation/ui/canvas.rb +82 -0
- data/lib/charming/presentation/ui/style.rb +62 -95
- data/lib/charming/presentation/ui.rb +15 -156
- data/lib/charming/presentation/view.rb +17 -0
- data/lib/charming/runtime.rb +2 -0
- data/lib/charming/tasks/inline_executor.rb +9 -0
- data/lib/charming/tasks/task.rb +3 -0
- data/lib/charming/tasks/threaded_executor.rb +12 -0
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +13 -0
- metadata +59 -10
- data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -90
- data/lib/charming/generators/app_generator/basic_templates.rb +0 -81
- data/lib/charming/generators/app_generator/component_templates.rb +0 -36
- data/lib/charming/generators/app_generator/controller_template.rb +0 -60
- data/lib/charming/generators/app_generator/database_templates.rb +0 -45
- data/lib/charming/generators/app_generator/layout_template.rb +0 -66
- data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -69
- data/lib/charming/generators/app_generator/state_templates.rb +0 -30
- data/lib/charming/generators/app_generator/view_template.rb +0 -84
|
@@ -2,45 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Generators
|
|
5
|
+
# ControllerGenerator implements `charming generate controller NAME [ACTION ...]`.
|
|
6
|
+
# Writes `app/controllers/<name>_controller.rb` containing a class that inherits
|
|
7
|
+
# from the app's `ApplicationController` and a `show` (or named) action that renders
|
|
8
|
+
# the conventional view with the command palette passed as an assign.
|
|
5
9
|
class ControllerGenerator < AppFileGenerator
|
|
10
|
+
# *name* is the resource name. *args* is the list of action names (defaults to `show`).
|
|
11
|
+
# *out*, *destination*, and *force* are forwarded to the parent.
|
|
6
12
|
def initialize(name, args, out:, destination:, force: false)
|
|
7
13
|
super
|
|
8
14
|
@actions = args
|
|
9
15
|
end
|
|
10
16
|
|
|
17
|
+
# Writes the controller file to the standard app/controllers path.
|
|
11
18
|
def generate
|
|
12
19
|
create_file(app_path("app", "controllers"), controller)
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
private
|
|
16
23
|
|
|
24
|
+
# The list of action names supplied on the command line.
|
|
17
25
|
attr_reader :actions
|
|
18
26
|
|
|
27
|
+
# The file-name suffix used by `app_path` (sets "controller" so the file is
|
|
28
|
+
# `<name>_controller.rb`).
|
|
19
29
|
def suffix
|
|
20
30
|
"controller"
|
|
21
31
|
end
|
|
22
32
|
|
|
33
|
+
# The full source of the generated controller file.
|
|
23
34
|
def controller
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#{action_methods} end
|
|
29
|
-
end
|
|
30
|
-
)
|
|
35
|
+
render_template("controller/controller.rb.template",
|
|
36
|
+
app_class: app_name.class_name,
|
|
37
|
+
controller_class: name.controller_class_name,
|
|
38
|
+
action_methods: action_methods)
|
|
31
39
|
end
|
|
32
40
|
|
|
41
|
+
# Renders one action method per action name; falls back to a single `show` action
|
|
42
|
+
# when no actions were specified.
|
|
33
43
|
def action_methods
|
|
34
44
|
return action_method("show") if actions.empty?
|
|
35
45
|
|
|
36
46
|
actions.map { |action| action_method(action) }.join("\n")
|
|
37
47
|
end
|
|
38
48
|
|
|
49
|
+
# Source for a single action method that renders the matching conventional view and
|
|
50
|
+
# passes the command palette as an assign.
|
|
39
51
|
def action_method(action)
|
|
40
|
-
|
|
41
|
-
render :#{action}, palette: command_palette
|
|
42
|
-
end
|
|
43
|
-
)
|
|
52
|
+
" def #{action}\n" \
|
|
53
|
+
" render :#{action}, palette: command_palette\n" \
|
|
54
|
+
" end\n"
|
|
44
55
|
end
|
|
45
56
|
end
|
|
46
57
|
end
|
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Generators
|
|
5
|
+
# ModelGenerator implements `charming generate model NAME [name:type ...]`. Writes an
|
|
6
|
+
# ActiveRecord model class, a `Create<Table>` migration (with one column per supplied
|
|
7
|
+
# field), and a baseline spec. Requires the app to have been generated with
|
|
8
|
+
# `--database sqlite3`.
|
|
5
9
|
class ModelGenerator < AppFileGenerator
|
|
10
|
+
# A single model field: column *name* and ActiveRecord *type* (e.g., "string").
|
|
6
11
|
Field = Data.define(:name, :type)
|
|
12
|
+
|
|
13
|
+
# The set of ActiveRecord column types accepted on the command line.
|
|
7
14
|
VALID_TYPES = %w[string text integer float decimal boolean date datetime time].freeze
|
|
8
15
|
|
|
16
|
+
# *name* is the resource name. *args* is the list of `name:type` field specifications.
|
|
9
17
|
def initialize(name, args, out:, destination:, force: false)
|
|
10
18
|
super
|
|
11
19
|
@fields = args.map { |arg| parse_field(arg) }
|
|
12
20
|
end
|
|
13
21
|
|
|
22
|
+
# Validates that the app is database-configured, then writes the model, migration,
|
|
23
|
+
# and spec files.
|
|
14
24
|
def generate
|
|
15
25
|
raise Error, "Database support is not configured. Generate the app with --database sqlite3 first." unless database_configured?
|
|
16
26
|
|
|
@@ -21,67 +31,61 @@ module Charming
|
|
|
21
31
|
|
|
22
32
|
private
|
|
23
33
|
|
|
34
|
+
# The list of parsed Field entries supplied on the command line.
|
|
24
35
|
attr_reader :fields
|
|
25
36
|
|
|
37
|
+
# No file-name suffix; ModelGenerator writes files to explicit paths.
|
|
26
38
|
def suffix
|
|
27
39
|
nil
|
|
28
40
|
end
|
|
29
41
|
|
|
42
|
+
# Path to the generated `app/models/<name>.rb` file.
|
|
30
43
|
def model_path
|
|
31
44
|
File.join("app", "models", "#{name.snake_name}.rb")
|
|
32
45
|
end
|
|
33
46
|
|
|
47
|
+
# Path to the generated `db/migrate/<timestamp>_create_<table>.rb` file.
|
|
34
48
|
def migration_path
|
|
35
49
|
File.join("db", "migrate", "#{timestamp}_create_#{table_name}.rb")
|
|
36
50
|
end
|
|
37
51
|
|
|
52
|
+
# Path to the generated `spec/models/<name>_spec.rb` file.
|
|
38
53
|
def spec_path
|
|
39
54
|
File.join("spec", "models", "#{name.snake_name}_spec.rb")
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# The full source of the generated ActiveRecord model class.
|
|
42
58
|
def model
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class #{name.class_name} < ApplicationRecord
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
)
|
|
59
|
+
render_template("model/model.rb.template",
|
|
60
|
+
app_class: app_name.class_name,
|
|
61
|
+
model_class: name.class_name)
|
|
50
62
|
end
|
|
51
63
|
|
|
64
|
+
# The full source of the generated migration, with one `t.<type> :<name>` line per field.
|
|
52
65
|
def migration
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
create_table :#{table_name} do |t|
|
|
58
|
-
#{field_lines} t.timestamps
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
)
|
|
66
|
+
render_template("model/migration.rb.template",
|
|
67
|
+
table_class: table_class_name,
|
|
68
|
+
table_name: table_name,
|
|
69
|
+
field_lines: field_lines)
|
|
63
70
|
end
|
|
64
71
|
|
|
72
|
+
# The full source of the generated model spec (asserts the model inherits from
|
|
73
|
+
# `ApplicationRecord`).
|
|
65
74
|
def spec
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
RSpec.describe #{app_name.class_name}::#{name.class_name} do
|
|
71
|
-
it "inherits from ApplicationRecord" do
|
|
72
|
-
expect(described_class.superclass).to eq(#{app_name.class_name}::ApplicationRecord)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
)
|
|
75
|
+
render_template("model/spec.rb.template",
|
|
76
|
+
app_snake: app_name.snake_name,
|
|
77
|
+
app_class: app_name.class_name,
|
|
78
|
+
model_class: name.class_name)
|
|
76
79
|
end
|
|
77
80
|
|
|
81
|
+
# Renders one `t.<type> :<name>` line per field, joined together.
|
|
78
82
|
def field_lines
|
|
79
83
|
fields.map { |field|
|
|
80
|
-
|
|
81
|
-
)
|
|
84
|
+
" t.#{field.type} :#{field.name}\n"
|
|
82
85
|
}.join
|
|
83
86
|
end
|
|
84
87
|
|
|
88
|
+
# Parses a single `name:type` argument. Raises Error on invalid names or unsupported types.
|
|
85
89
|
def parse_field(value)
|
|
86
90
|
field_name, type = value.split(":", 2)
|
|
87
91
|
raise Error, "Invalid field: #{value.inspect}" unless field_name && type
|
|
@@ -91,19 +95,23 @@ end
|
|
|
91
95
|
Field.new(name: field_name, type: type)
|
|
92
96
|
end
|
|
93
97
|
|
|
98
|
+
# True when `config/database.rb` and `app/models/application_record.rb` both exist.
|
|
94
99
|
def database_configured?
|
|
95
100
|
File.exist?(File.join(destination, "config", "database.rb")) &&
|
|
96
101
|
File.exist?(File.join(destination, "app", "models", "application_record.rb"))
|
|
97
102
|
end
|
|
98
103
|
|
|
104
|
+
# The pluralized table name (e.g., "user" → "users", "category" → "categories").
|
|
99
105
|
def table_name
|
|
100
106
|
pluralize(name.snake_name)
|
|
101
107
|
end
|
|
102
108
|
|
|
109
|
+
# The CamelCase migration class name (e.g., "users" → "Users").
|
|
103
110
|
def table_class_name
|
|
104
111
|
table_name.split("_").map(&:capitalize).join
|
|
105
112
|
end
|
|
106
113
|
|
|
114
|
+
# Minimal English pluralization for the model name (covers the common -y, -s/x/z/ch/sh cases).
|
|
107
115
|
def pluralize(value)
|
|
108
116
|
return value.sub(/y\z/, "ies") if value.end_with?("y")
|
|
109
117
|
return "#{value}es" if value.match?(/(?:s|x|z|ch|sh)\z/)
|
|
@@ -111,6 +119,7 @@ end
|
|
|
111
119
|
"#{value}s"
|
|
112
120
|
end
|
|
113
121
|
|
|
122
|
+
# The current UTC timestamp in the format ActiveRecord uses for migration filenames.
|
|
114
123
|
def timestamp
|
|
115
124
|
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
116
125
|
end
|
|
@@ -2,24 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Generators
|
|
5
|
+
# Name validates a generator resource name and exposes the conventional Ruby class-name
|
|
6
|
+
# variants (singular class, controller, component) derived from it. The original input
|
|
7
|
+
# must match `VALID_NAME` (lowercase, snake_case, must start with a letter).
|
|
5
8
|
class Name
|
|
9
|
+
# Regex matching a valid snake_case resource name: lowercase letter, then any
|
|
10
|
+
# combination of lowercase letters, digits, and underscores.
|
|
6
11
|
VALID_NAME = /\A[a-z][a-z0-9_]*\z/
|
|
7
12
|
|
|
13
|
+
# The original snake_case name as supplied.
|
|
8
14
|
attr_reader :snake_name
|
|
9
15
|
|
|
16
|
+
# Raises Error when *value* doesn't match `VALID_NAME`.
|
|
10
17
|
def initialize(value)
|
|
11
18
|
@snake_name = value.to_s
|
|
12
19
|
raise Error, "Invalid name: #{value}" unless VALID_NAME.match?(@snake_name)
|
|
13
20
|
end
|
|
14
21
|
|
|
22
|
+
# The CamelCase class name (e.g., "user" → "User").
|
|
15
23
|
def class_name
|
|
16
24
|
snake_name.split("_").map(&:capitalize).join
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
# The controller class name (e.g., "user" → "UserController").
|
|
19
28
|
def controller_class_name
|
|
20
29
|
"#{class_name}Controller"
|
|
21
30
|
end
|
|
22
31
|
|
|
32
|
+
# The component class name (e.g., "user" → "UserComponent").
|
|
23
33
|
def component_class_name
|
|
24
34
|
"#{class_name}Component"
|
|
25
35
|
end
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Generators
|
|
5
|
+
# ScreenGenerator implements `charming generate screen NAME`. Writes a complete vertical
|
|
6
|
+
# slice for a new screen: a state class, a controller with a `show` action, a view,
|
|
7
|
+
# matching spec files, and inserts a route into `config/routes.rb` and a command entry
|
|
8
|
+
# into `ApplicationController` for the command palette.
|
|
5
9
|
class ScreenGenerator < AppFileGenerator
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
# *name* is the resource name. *args* is unused (raises Error when non-empty).
|
|
8
11
|
def initialize(name, args, out:, destination:, force: false)
|
|
9
12
|
super
|
|
10
13
|
raise Error, "Usage: charming generate screen NAME" if args.any?
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Writes the state, controller, view, and three spec files, then inserts a route
|
|
17
|
+
# and a command-palette entry.
|
|
13
18
|
def generate
|
|
14
19
|
create_file(state_path, state)
|
|
15
20
|
create_file(controller_path, controller)
|
|
@@ -23,88 +28,124 @@ module Charming
|
|
|
23
28
|
|
|
24
29
|
private
|
|
25
30
|
|
|
31
|
+
# The file-name suffix used by `app_path` ("screen" — only used by the parent class).
|
|
26
32
|
def suffix
|
|
27
33
|
"screen"
|
|
28
34
|
end
|
|
29
35
|
|
|
36
|
+
# Path to the generated state class.
|
|
30
37
|
def state_path
|
|
31
38
|
File.join("app", "state", "#{name.snake_name}_state.rb")
|
|
32
39
|
end
|
|
33
40
|
|
|
41
|
+
# Path to the generated controller class.
|
|
34
42
|
def controller_path
|
|
35
43
|
File.join("app", "controllers", "#{name.snake_name}_controller.rb")
|
|
36
44
|
end
|
|
37
45
|
|
|
46
|
+
# Path to the generated `show` view.
|
|
38
47
|
def view_path
|
|
39
|
-
File.join("app", "views", name.snake_name, "
|
|
48
|
+
File.join("app", "views", name.snake_name, "show_view.rb")
|
|
40
49
|
end
|
|
41
50
|
|
|
51
|
+
# Path to the generated state spec.
|
|
42
52
|
def spec_state_path
|
|
43
53
|
File.join("spec", "state", "#{name.snake_name}_state_spec.rb")
|
|
44
54
|
end
|
|
45
55
|
|
|
56
|
+
# Path to the generated controller spec.
|
|
46
57
|
def spec_controller_path
|
|
47
58
|
File.join("spec", "controllers", "#{name.snake_name}_controller_spec.rb")
|
|
48
59
|
end
|
|
49
60
|
|
|
61
|
+
# Path to the generated view spec.
|
|
50
62
|
def spec_view_path
|
|
51
|
-
File.join("spec", "views", name.snake_name, "
|
|
63
|
+
File.join("spec", "views", name.snake_name, "show_view_spec.rb")
|
|
52
64
|
end
|
|
53
65
|
|
|
66
|
+
# Absolute path to the app's `config/routes.rb`.
|
|
54
67
|
def route_path
|
|
55
68
|
File.join(destination, "config", "routes.rb")
|
|
56
69
|
end
|
|
57
70
|
|
|
71
|
+
# Absolute path to the app's `ApplicationController`.
|
|
58
72
|
def application_controller_path
|
|
59
73
|
File.join(destination, "app", "controllers", "application_controller.rb")
|
|
60
74
|
end
|
|
61
75
|
|
|
76
|
+
# The source of the generated state class.
|
|
62
77
|
def state
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
attribute :title, :string, default: "#{name.class_name}"
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
)
|
|
78
|
+
render_template("screen/state.rb.template",
|
|
79
|
+
app_class: app_name.class_name,
|
|
80
|
+
state_class: "#{name.class_name}State",
|
|
81
|
+
title: name.class_name)
|
|
71
82
|
end
|
|
72
83
|
|
|
84
|
+
# The source of the generated controller class.
|
|
73
85
|
def controller
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#{controller_body}
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
)
|
|
86
|
+
render_template("screen/controller.rb.template",
|
|
87
|
+
app_class: app_name.class_name,
|
|
88
|
+
controller_class: name.controller_class_name,
|
|
89
|
+
controller_body: controller_body)
|
|
82
90
|
end
|
|
83
91
|
|
|
92
|
+
# The body of the controller: a `show` action and a private accessor for the state.
|
|
84
93
|
def controller_body
|
|
85
|
-
|
|
86
|
-
render :show
|
|
87
|
-
#{name.snake_name}: #{name.snake_name}
|
|
88
|
-
palette: command_palette
|
|
89
|
-
end
|
|
94
|
+
" def show\n" \
|
|
95
|
+
" render :show,\n" \
|
|
96
|
+
" #{name.snake_name}: #{name.snake_name},\n" \
|
|
97
|
+
" palette: command_palette\n" \
|
|
98
|
+
" end\n" \
|
|
99
|
+
"\n" \
|
|
100
|
+
" private\n" \
|
|
101
|
+
"\n" \
|
|
102
|
+
" def #{name.snake_name}\n" \
|
|
103
|
+
" state(:#{name.snake_name}, #{name.class_name}State)\n" \
|
|
104
|
+
" end"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The source of the generated view class.
|
|
108
|
+
def view
|
|
109
|
+
render_template("screen/view.rb.template",
|
|
110
|
+
app_class: app_name.class_name,
|
|
111
|
+
resource_module: name.class_name,
|
|
112
|
+
screen_name: name.snake_name)
|
|
113
|
+
end
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
# The source of the generated state spec.
|
|
116
|
+
def spec_state
|
|
117
|
+
render_template("screen/spec_state.rb.template",
|
|
118
|
+
app_snake: app_name.snake_name,
|
|
119
|
+
app_class: app_name.class_name,
|
|
120
|
+
state_class: "#{name.class_name}State",
|
|
121
|
+
title: name.class_name)
|
|
122
|
+
end
|
|
92
123
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
# The source of the generated controller spec.
|
|
125
|
+
def spec_controller
|
|
126
|
+
render_template("screen/spec_controller.rb.template",
|
|
127
|
+
app_snake: app_name.snake_name,
|
|
128
|
+
app_class: app_name.class_name,
|
|
129
|
+
controller_class: name.controller_class_name)
|
|
96
130
|
end
|
|
97
131
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
132
|
+
# The source of the generated view spec.
|
|
133
|
+
def spec_view
|
|
134
|
+
render_template("screen/spec_view.rb.template",
|
|
135
|
+
app_snake: app_name.snake_name,
|
|
136
|
+
app_class: app_name.class_name,
|
|
137
|
+
resource_module: name.class_name,
|
|
138
|
+
screen_name: name.snake_name,
|
|
139
|
+
title: name.class_name)
|
|
101
140
|
end
|
|
102
141
|
|
|
142
|
+
# Inserts a `screen` route into `config/routes.rb`, idempotently.
|
|
103
143
|
def insert_route
|
|
104
144
|
route = %( screen "/#{name.snake_name}", to: "#{name.snake_name}#show", title: "#{name.class_name}")
|
|
105
145
|
insert_before_end(route_path, route, "route", "end")
|
|
106
146
|
end
|
|
107
147
|
|
|
148
|
+
# Inserts a `command` block into `ApplicationController`, idempotently.
|
|
108
149
|
def insert_command
|
|
109
150
|
command = %( command "#{name.class_name}" do
|
|
110
151
|
navigate_to "/#{name.snake_name}"
|
|
@@ -112,6 +153,8 @@ end
|
|
|
112
153
|
insert_before_end(application_controller_path, command, "command", " end")
|
|
113
154
|
end
|
|
114
155
|
|
|
156
|
+
# Inserts *content* into *path* just before the line matching *end_line*. No-ops when
|
|
157
|
+
# the content is already present. Raises Error when the file or end-line is missing.
|
|
115
158
|
def insert_before_end(path, content, label, end_line)
|
|
116
159
|
raise Error, "Missing file: #{relative_path(path)}" unless File.exist?(path)
|
|
117
160
|
|
|
@@ -125,6 +168,8 @@ end
|
|
|
125
168
|
out.puts "insert #{label} #{relative_path(path)}"
|
|
126
169
|
end
|
|
127
170
|
|
|
171
|
+
# Returns the index of the last line in *lines* that matches *end_line* (the line
|
|
172
|
+
# just before which new content will be inserted). Raises Error when not found.
|
|
128
173
|
def insertion_index(lines, path, end_line)
|
|
129
174
|
index = lines.rindex { |line| line.chomp == end_line }
|
|
130
175
|
raise Error, "Could not update #{relative_path(path)}" unless index
|
|
@@ -132,6 +177,7 @@ end
|
|
|
132
177
|
index
|
|
133
178
|
end
|
|
134
179
|
|
|
180
|
+
# Strips the destination prefix from *path* for human-friendly status output.
|
|
135
181
|
def relative_path(path)
|
|
136
182
|
path.delete_prefix("#{destination}/")
|
|
137
183
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module __APP_CLASS__
|
|
4
|
+
class Application < Charming::Application
|
|
5
|
+
root File.expand_path("../..", __dir__)
|
|
6
|
+
|
|
7
|
+
Charming::Presentation::UI::Theme.built_in_names.each do |theme_name|
|
|
8
|
+
theme theme_name.to_sym, built_in: theme_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
default_theme :phosphor
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module __APP_CLASS__
|
|
4
|
+
class ApplicationController < Charming::Controller
|
|
5
|
+
layout Layouts::ApplicationLayout
|
|
6
|
+
focus_ring :sidebar, :content
|
|
7
|
+
|
|
8
|
+
key "p", :open_command_palette, scope: :global
|
|
9
|
+
key "q", :quit, scope: :global
|
|
10
|
+
|
|
11
|
+
command "Home" do
|
|
12
|
+
navigate_to "/"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
command "Theme", :open_theme_palette
|
|
16
|
+
command "Close palette", :close_command_palette
|
|
17
|
+
command "Quit app", :quit
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
database_path = File.expand_path("../db/development.sqlite3", __dir__)
|
|
7
|
+
FileUtils.mkdir_p(File.dirname(database_path))
|
|
8
|
+
|
|
9
|
+
ActiveRecord::Base.establish_connection(
|
|
10
|
+
adapter: "sqlite3",
|
|
11
|
+
database: database_path
|
|
12
|
+
)
|
|
File without changes
|