charming 0.1.2 → 0.1.4

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/charming/application.rb +22 -5
  3. data/lib/charming/cli.rb +3 -3
  4. data/lib/charming/controller/class_methods.rb +2 -2
  5. data/lib/charming/controller/command_palette.rb +2 -2
  6. data/lib/charming/controller/component_dispatching.rb +47 -3
  7. data/lib/charming/controller/focus.rb +123 -0
  8. data/lib/charming/controller/focus_management.rb +1 -1
  9. data/lib/charming/controller/rendering.rb +6 -17
  10. data/lib/charming/controller/session_state.rb +12 -1
  11. data/lib/charming/controller.rb +11 -2
  12. data/lib/charming/database/commands.rb +106 -0
  13. data/lib/charming/generators/component_generator.rb +1 -1
  14. data/lib/charming/generators/database_installer.rb +154 -0
  15. data/lib/charming/generators/model_generator.rb +2 -10
  16. data/lib/charming/generators/name.rb +1 -1
  17. data/lib/charming/generators/templates/app/application.template +1 -1
  18. data/lib/charming/generators/templates/app/layout.template +3 -6
  19. data/lib/charming/generators/templates/app/view.template +1 -1
  20. data/lib/charming/generators/templates/component/component.rb.template +1 -1
  21. data/lib/charming/generators/templates/screen/view.rb.template +1 -1
  22. data/lib/charming/generators/templates/view/view.rb.template +1 -1
  23. data/lib/charming/generators/view_generator.rb +1 -1
  24. data/lib/charming/internal/renderer/differential.rb +13 -5
  25. data/lib/charming/internal/terminal/tty_backend.rb +22 -2
  26. data/lib/charming/presentation/component.rb +3 -5
  27. data/lib/charming/presentation/components/activity_indicator.rb +173 -134
  28. data/lib/charming/presentation/components/command_palette.rb +94 -96
  29. data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
  30. data/lib/charming/presentation/components/empty_state.rb +47 -49
  31. data/lib/charming/presentation/components/form/builder.rb +52 -54
  32. data/lib/charming/presentation/components/form/confirm.rb +49 -51
  33. data/lib/charming/presentation/components/form/field.rb +94 -96
  34. data/lib/charming/presentation/components/form/input.rb +53 -55
  35. data/lib/charming/presentation/components/form/note.rb +27 -29
  36. data/lib/charming/presentation/components/form/select.rb +84 -86
  37. data/lib/charming/presentation/components/form/textarea.rb +67 -69
  38. data/lib/charming/presentation/components/form.rb +120 -122
  39. data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
  40. data/lib/charming/presentation/components/list.rb +123 -125
  41. data/lib/charming/presentation/components/markdown.rb +21 -23
  42. data/lib/charming/presentation/components/modal.rb +46 -48
  43. data/lib/charming/presentation/components/progressbar.rb +51 -53
  44. data/lib/charming/presentation/components/spinner.rb +40 -42
  45. data/lib/charming/presentation/components/table.rb +109 -111
  46. data/lib/charming/presentation/components/text_area.rb +219 -221
  47. data/lib/charming/presentation/components/text_input.rb +120 -122
  48. data/lib/charming/presentation/components/viewport.rb +218 -220
  49. data/lib/charming/presentation/layout/builder.rb +64 -66
  50. data/lib/charming/presentation/layout/overlay.rb +48 -50
  51. data/lib/charming/presentation/layout/pane.rb +129 -118
  52. data/lib/charming/presentation/layout/rect.rb +19 -16
  53. data/lib/charming/presentation/layout/screen_layout.rb +47 -42
  54. data/lib/charming/presentation/layout/split.rb +107 -102
  55. data/lib/charming/presentation/layout.rb +28 -30
  56. data/lib/charming/presentation/markdown/render_context.rb +30 -14
  57. data/lib/charming/presentation/markdown/renderer.rb +302 -79
  58. data/lib/charming/presentation/markdown/style_config.rb +215 -0
  59. data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -59
  60. data/lib/charming/presentation/markdown.rb +4 -6
  61. data/lib/charming/presentation/template_view.rb +22 -24
  62. data/lib/charming/presentation/templates/erb_handler.rb +4 -6
  63. data/lib/charming/presentation/templates.rb +47 -49
  64. data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
  65. data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
  66. data/lib/charming/presentation/ui/border.rb +24 -26
  67. data/lib/charming/presentation/ui/border_painter.rb +37 -39
  68. data/lib/charming/presentation/ui/canvas.rb +59 -61
  69. data/lib/charming/presentation/ui/style.rb +173 -175
  70. data/lib/charming/presentation/ui/theme.rb +133 -135
  71. data/lib/charming/presentation/ui/width.rb +12 -14
  72. data/lib/charming/presentation/ui.rb +69 -71
  73. data/lib/charming/presentation/view.rb +110 -105
  74. data/lib/charming/router.rb +3 -8
  75. data/lib/charming/runtime.rb +25 -10
  76. data/lib/charming/version.rb +1 -1
  77. data/lib/charming.rb +5 -4
  78. metadata +43 -9
  79. data/lib/charming/database_commands.rb +0 -103
  80. data/lib/charming/database_installer.rb +0 -152
  81. data/lib/charming/focus.rb +0 -121
  82. data/lib/charming/presentation/markdown/block_renderers.rb +0 -120
  83. data/lib/charming/presentation/markdown/inline_renderers.rb +0 -68
@@ -1,135 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- # View is the base class for all screen view implementations. It provides assign injection (via `initialize`),
6
- # rendering hooks, layout composition helpers (`row`, `column`, `render_component`, `yield_content`),
7
- # and access to controller theme, style, and focus state from within views.
8
- class View
9
- # Initializes the view with named assigns injected as instance-local accessor methods via
10
- # `define_singleton_method`. Called when a controller instantiates a view for rendering.
11
- def initialize(**assigns)
12
- @assigns = assigns
13
- define_assign_readers
14
- end
4
+ # View is the base class for all screen view implementations. It provides assign injection (via `initialize`),
5
+ # rendering hooks, layout composition helpers (`row`, `column`, `render_component`, `yield_content`),
6
+ # and access to controller theme, style, and focus state from within views.
7
+ class View
8
+ # Initializes the view with named assigns injected as instance-local accessor methods via
9
+ # `define_singleton_method`. Called when a controller instantiates a view for rendering.
10
+ def initialize(**assigns)
11
+ @assigns = assigns
12
+ define_assign_readers
13
+ end
15
14
 
16
- # Returns all view assigns as a hash, used by layouts to compose the full template (content + screen + controller).
17
- def layout_assigns
18
- assigns
19
- end
15
+ # Returns all view assigns as a hash, used by layouts to compose the full template (content + screen + controller).
16
+ def layout_assigns
17
+ assigns
18
+ end
20
19
 
21
- # Renders the view's body. Default is empty — subclasses override to return visible text.
22
- def render
23
- ""
24
- end
20
+ # Renders the view's body. Default is empty — subclasses override to return visible text.
21
+ def render
22
+ ""
23
+ end
25
24
 
26
- # Delegates focus checking to the controller in assigns, allowing views to determine which slot (sidebar, content) has focus.
27
- def focused?(slot)
28
- ctrl = assigns[:focus_controller] || assigns[:controller]
29
- ctrl ? ctrl.focused?(slot) : false
30
- end
25
+ # Delegates focus checking to the controller in assigns, allowing views to determine which slot (sidebar, content) has focus.
26
+ def focused?(slot)
27
+ ctrl = assigns[:focus_controller] || assigns[:controller]
28
+ ctrl ? ctrl.focused?(slot) : false
29
+ end
31
30
 
32
- private
31
+ private
33
32
 
34
- attr_reader :assigns
33
+ attr_reader :assigns
35
34
 
36
- # Returns the shared UI style configuration used by components and views for visual rendering (colors, borders).
37
- def style
38
- UI.style
39
- end
35
+ # Returns the shared UI style configuration used by components and views for visual rendering (colors, borders).
36
+ def style
37
+ UI.style
38
+ end
40
39
 
41
- # Returns the active theme: uses `theme` from assigns or controller, falling back to `UI::Theme.default`.
42
- def theme
43
- assigns[:theme] || assigns[:controller]&.theme || UI::Theme.default
44
- end
40
+ # Returns the active theme: uses `theme` from assigns or controller, falling back to `UI::Theme.default`.
41
+ def theme
42
+ assigns[:theme] || assigns[:controller]&.theme || UI::Theme.default
43
+ end
45
44
 
46
- # Outputs styled text through the view's rendering pipeline. Accepts a named `style:` for inline formatting.
47
- # Appends the rendered value to the output buffer and returns it.
48
- def text(value, style: nil)
49
- rendered = apply_style(value.to_s, style)
50
- append_to_buffer(rendered)
51
- rendered
52
- end
45
+ # Outputs styled text through the view's rendering pipeline. Accepts a named `style:` for inline formatting.
46
+ # Appends the rendered value to the output buffer and returns it.
47
+ def text(value, style: nil)
48
+ rendered = apply_style(value.to_s, style)
49
+ append_to_buffer(rendered)
50
+ rendered
51
+ end
53
52
 
54
- # Renders a box with optional styling. Accepts an inline block for complex content or a plain value.
55
- # Used for bordered containers and field groups in views.
56
- def box(value = nil, style: nil, &)
57
- content = block_given? ? capture(&) : value.to_s
58
- apply_style(content, style)
59
- end
53
+ # Renders a box with optional styling. Accepts an inline block for complex content or a plain value.
54
+ # Used for bordered containers and field groups in views.
55
+ def box(value = nil, style: nil, &)
56
+ content = block_given? ? capture(&) : value.to_s
57
+ apply_style(content, style)
58
+ end
60
59
 
61
- # Joins items horizontally (side-by-side) using the UI rendering engine. Supports a `gap:` parameter.
62
- def row(*items, gap: 0)
63
- UI.join_horizontal(*items, gap: gap)
64
- end
60
+ # Joins items horizontally (side-by-side) using the UI rendering engine. Supports a `gap:` parameter.
61
+ def row(*items, gap: 0)
62
+ UI.join_horizontal(*items, gap: gap)
63
+ end
65
64
 
66
- # Stacks items vertically using the UI rendering engine. Supports a `gap:` parameter for spacing.
67
- def column(*items, gap: 0)
68
- UI.join_vertical(*items, gap: gap)
69
- end
65
+ # Stacks items vertically using the UI rendering engine. Supports a `gap:` parameter for spacing.
66
+ def column(*items, gap: 0)
67
+ UI.join_vertical(*items, gap: gap)
68
+ end
70
69
 
71
- # Renders a component (e.g., a ProgressBar, Spinner, Modal) and returns its string output.
72
- def render_component(component)
73
- component.render.to_s
74
- end
70
+ # Renders a component (e.g., a ProgressBar, Spinner, Modal) and returns its string output.
71
+ def render_component(component)
72
+ component.render.to_s
73
+ end
75
74
 
76
- # Renders a partial view component. An alias for `render_component` used in layout templates.
77
- def render_partial(partial)
78
- render_component(partial)
79
- end
75
+ # Renders a partial view component. An alias for `render_component` used in layout templates.
76
+ def render_partial(partial)
77
+ render_component(partial)
78
+ end
80
79
 
81
- # Builds a declarative layout tree for the current terminal screen and renders it.
82
- def screen_layout(background: nil, &)
83
- layout = Layout::Builder.build(screen: layout_screen, view: self, background: background, &)
84
- register_layout_focus(layout)
85
- layout.render
86
- end
80
+ # Builds a declarative layout tree for the current terminal screen and renders it.
81
+ def screen_layout(background: nil, &)
82
+ layout = Layout::Builder.build(screen: layout_screen, view: self, background: background, &)
83
+ register_layout_focus(layout)
84
+ register_layout_mouse_targets(layout)
85
+ layout.render
86
+ end
87
87
 
88
- # Yields the layout's `content` slot — used by view templates to inject their body into a layout wrapper (e.g., sidebar).
89
- def yield_content
90
- assigns.fetch(:content, "")
91
- end
88
+ # Yields the layout's `content` slot — used by view templates to inject their body into a layout wrapper (e.g., sidebar).
89
+ def yield_content
90
+ assigns.fetch(:content, "")
91
+ end
92
92
 
93
- # Evaluates a block in the view's context with a clean output buffer. Captures text written via `text`/`box`
94
- # and returns joined content. Resets buffer afterward for parent rendering.
95
- def capture(&)
96
- previous_buffer = @output_buffer
97
- @output_buffer = []
98
- result = instance_eval(&)
99
- @output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
100
- ensure
101
- @output_buffer = previous_buffer
102
- end
93
+ # Evaluates a block in the view's context with a clean output buffer. Captures text written via `text`/`box`
94
+ # and returns joined content. Resets buffer afterward for parent rendering.
95
+ def capture(&)
96
+ previous_buffer = @output_buffer
97
+ @output_buffer = []
98
+ result = instance_eval(&)
99
+ @output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
100
+ ensure
101
+ @output_buffer = previous_buffer
102
+ end
103
103
 
104
- # Appends a value to the current output buffer (if one is active). Used by rendering helpers.
105
- def append_to_buffer(value)
106
- @output_buffer << value if @output_buffer
107
- end
104
+ # Appends a value to the current output buffer (if one is active). Used by rendering helpers.
105
+ def append_to_buffer(value)
106
+ @output_buffer << value if @output_buffer
107
+ end
108
108
 
109
- # Applies a style object's `render` method to a string, returning styled output or raw text when style is nil.
110
- def apply_style(value, style_object)
111
- style_object ? style_object.render(value) : value
112
- end
109
+ # Applies a style object's `render` method to a string, returning styled output or raw text when style is nil.
110
+ def apply_style(value, style_object)
111
+ style_object ? style_object.render(value) : value
112
+ end
113
113
 
114
- # Dynamically defines read-only accessor methods for each assign key as singleton methods on self.
115
- # Skips keys where the view already responds (controller methods take precedence).
116
- def define_assign_readers
117
- assigns.each_key do |name|
118
- next if respond_to?(name, true)
114
+ # Dynamically defines read-only accessor methods for each assign key as singleton methods on self.
115
+ # Skips keys where the view already responds (controller methods take precedence).
116
+ def define_assign_readers
117
+ assigns.each_key do |name|
118
+ next if respond_to?(name, true)
119
119
 
120
- define_singleton_method(name) { assigns.fetch(name) }
121
- end
120
+ define_singleton_method(name) { assigns.fetch(name) }
122
121
  end
122
+ end
123
123
 
124
- def layout_screen
125
- assigns[:screen] || assigns[:controller]&.screen || Charming::Screen.new(width: 80, height: 24)
126
- end
124
+ def layout_screen
125
+ assigns[:screen] || assigns[:controller]&.screen || Charming::Screen.new(width: 80, height: 24)
126
+ end
127
127
 
128
- def register_layout_focus(layout)
129
- return unless assigns[:controller]
128
+ def register_layout_focus(layout)
129
+ return unless assigns[:controller]
130
130
 
131
- assigns[:controller].focus.define_layout(layout.focusable_names)
132
- end
131
+ assigns[:controller].focus.define_layout(layout.focusable_names)
132
+ end
133
+
134
+ def register_layout_mouse_targets(layout)
135
+ return unless assigns[:controller]
136
+
137
+ assigns[:controller].register_mouse_targets(layout.mouse_targets)
133
138
  end
134
139
  end
135
140
  end
@@ -108,20 +108,15 @@ module Charming
108
108
  end
109
109
  end
110
110
 
111
- # Splits a camel-case string into words for title derivation (e.g., "my_route" → ["my", "route"]).
112
- def camelize(value)
113
- value.split("_").map(&:capitalize).join
114
- end
115
-
116
111
  # Looks up a constant by name in Object. Used to resolve controller strings from route definitions.
117
112
  def constantize(name)
118
- Object.const_get(name)
113
+ ActiveSupport::Inflector.constantize(name)
119
114
  end
120
115
 
121
116
  # Builds the full controller constant name, prepending the namespace if present.
122
- # For example: "HomeController" with namespace "Admin" → "Admin::HomeController".
117
+ # For example: "home" with namespace "Admin" → "Admin::HomeController".
123
118
  def controller_constant_name(controller_name)
124
- name = "#{camelize(controller_name)}Controller"
119
+ name = "#{ActiveSupport::Inflector.camelize(controller_name)}Controller"
125
120
  @namespace.to_s.empty? ? name : "#{@namespace}::#{name}"
126
121
  end
127
122
 
@@ -26,19 +26,21 @@ module Charming
26
26
  # restores terminal state on exit.
27
27
  def run
28
28
  setup_terminal
29
- render(resolve_response(dispatch(@route.action)))
30
- loop do
31
- event = next_task_event || next_timer_event || @backend.read_event(timeout: read_timeout)
32
- next unless event
29
+ with_raw_input do
30
+ render(resolve_response(dispatch(@route.action)))
31
+ loop do
32
+ event = next_task_event || next_timer_event || @backend.read_event(timeout: read_timeout)
33
+ next unless event
33
34
 
34
- response = dispatch_event(event)
35
- next unless response
36
- break if response.quit?
35
+ response = dispatch_event(event)
36
+ next unless response
37
+ break if response.quit?
37
38
 
38
- response = resolve_response(response)
39
- break if response.quit?
39
+ response = resolve_response(response)
40
+ break if response.quit?
40
41
 
41
- render(response)
42
+ render(response)
43
+ end
42
44
  end
43
45
  ensure
44
46
  @task_executor&.shutdown(timeout: 0.0)
@@ -93,8 +95,12 @@ module Charming
93
95
  end
94
96
 
95
97
  # Dispatches a resize event: updates screen dimensions and re-renders the current action.
98
+ # The renderer's cached previous frame is invalidated and the backend is cleared so the
99
+ # new-dimension frame paints onto a clean alt-screen instead of overlaying stale rows.
96
100
  def dispatch_resize(event)
97
101
  @screen = Screen.new(width: event.width, height: event.height)
102
+ @renderer.invalidate if @renderer.respond_to?(:invalidate)
103
+ @backend.clear if @backend.respond_to?(:clear)
98
104
  dispatch(@route.action, event: event)
99
105
  end
100
106
 
@@ -180,13 +186,22 @@ module Charming
180
186
  def setup_terminal
181
187
  @backend.enter_alt_screen
182
188
  @backend.hide_cursor
189
+ @backend.enable_mouse_tracking if @backend.respond_to?(:enable_mouse_tracking)
183
190
  @backend.install_resize_handler if @backend.respond_to?(:install_resize_handler)
184
191
  end
185
192
 
193
+ # Keeps input raw/no-echo across rendering and dispatch, not just during reads.
194
+ def with_raw_input(&block)
195
+ return yield unless @backend.respond_to?(:with_raw_input)
196
+
197
+ @backend.with_raw_input(&block)
198
+ end
199
+
186
200
  # Restores terminal state: reinstalls any previous resize handler, shows
187
201
  # the cursor, and leaves the alternative screen buffer.
188
202
  def restore_terminal
189
203
  @backend.restore_resize_handler if @backend.respond_to?(:restore_resize_handler)
204
+ @backend.disable_mouse_tracking if @backend.respond_to?(:disable_mouse_tracking)
190
205
  @backend.show_cursor
191
206
  @backend.leave_alt_screen
192
207
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/charming.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/inflector"
4
+ require "logger"
3
5
  require "zeitwerk"
4
6
 
5
7
  loader = Zeitwerk::Loader.for_gem
@@ -9,14 +11,13 @@ loader.inflector.inflect(
9
11
  "ansi_codes" => "ANSICodes",
10
12
  "ansi_slicer" => "ANSISlicer",
11
13
  "border_painter" => "BorderPainter",
12
- "block_renderers" => "BlockRenderer",
13
- "inline_renderers" => "InlineRenderer",
14
14
  "render_context" => "RenderContext",
15
15
  "erb_handler" => "ErbHandler",
16
16
  "key_normalizer" => "KeyNormalizer",
17
17
  "mouse_parser" => "MouseParser",
18
18
  "tty_backend" => "TTYBackend"
19
19
  )
20
+ loader.collapse("#{__dir__}/charming/presentation")
20
21
  loader.setup
21
22
 
22
23
  module Charming
@@ -37,5 +38,5 @@ module Charming
37
38
  end
38
39
  end
39
40
 
40
- Charming::Presentation::Templates.register ".tui.erb", Charming::Presentation::Templates::ErbHandler
41
- Charming::Presentation::Templates.register ".txt.erb", Charming::Presentation::Templates::ErbHandler
41
+ Charming::Templates.register ".tui.erb", Charming::Templates::ErbHandler
42
+ Charming::Templates.register ".txt.erb", Charming::Templates::ErbHandler
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: charming
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - pando
@@ -50,19 +50,53 @@ dependencies:
50
50
  - !ruby/object:Gem::Version
51
51
  version: 8.1.2
52
52
  - !ruby/object:Gem::Dependency
53
- name: kramdown
53
+ name: activesupport
54
54
  requirement: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - "~>"
57
57
  - !ruby/object:Gem::Version
58
- version: '2.5'
58
+ version: '8.1'
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 8.1.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '8.1'
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 8.1.2
72
+ - !ruby/object:Gem::Dependency
73
+ name: commonmarker
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '2.0'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '2.0'
86
+ - !ruby/object:Gem::Dependency
87
+ name: logger
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - "~>"
91
+ - !ruby/object:Gem::Version
92
+ version: '1.7'
59
93
  type: :runtime
60
94
  prerelease: false
61
95
  version_requirements: !ruby/object:Gem::Requirement
62
96
  requirements:
63
97
  - - "~>"
64
98
  - !ruby/object:Gem::Version
65
- version: '2.5'
99
+ version: '1.7'
66
100
  - !ruby/object:Gem::Dependency
67
101
  name: rouge
68
102
  requirement: !ruby/object:Gem::Requirement
@@ -210,23 +244,23 @@ files:
210
244
  - lib/charming/controller/command_palette.rb
211
245
  - lib/charming/controller/component_dispatching.rb
212
246
  - lib/charming/controller/dispatching.rb
247
+ - lib/charming/controller/focus.rb
213
248
  - lib/charming/controller/focus_management.rb
214
249
  - lib/charming/controller/rendering.rb
215
250
  - lib/charming/controller/session_state.rb
216
251
  - lib/charming/controller/sidebar_navigation.rb
217
- - lib/charming/database_commands.rb
218
- - lib/charming/database_installer.rb
252
+ - lib/charming/database/commands.rb
219
253
  - lib/charming/events/key_event.rb
220
254
  - lib/charming/events/mouse_event.rb
221
255
  - lib/charming/events/resize_event.rb
222
256
  - lib/charming/events/task_event.rb
223
257
  - lib/charming/events/timer_event.rb
224
- - lib/charming/focus.rb
225
258
  - lib/charming/generators/app_file_generator.rb
226
259
  - lib/charming/generators/app_generator.rb
227
260
  - lib/charming/generators/base.rb
228
261
  - lib/charming/generators/component_generator.rb
229
262
  - lib/charming/generators/controller_generator.rb
263
+ - lib/charming/generators/database_installer.rb
230
264
  - lib/charming/generators/error.rb
231
265
  - lib/charming/generators/model_generator.rb
232
266
  - lib/charming/generators/name.rb
@@ -277,6 +311,7 @@ files:
277
311
  - lib/charming/presentation/component.rb
278
312
  - lib/charming/presentation/components/activity_indicator.rb
279
313
  - lib/charming/presentation/components/command_palette.rb
314
+ - lib/charming/presentation/components/command_palette_modal.rb
280
315
  - lib/charming/presentation/components/empty_state.rb
281
316
  - lib/charming/presentation/components/form.rb
282
317
  - lib/charming/presentation/components/form/builder.rb
@@ -304,10 +339,9 @@ files:
304
339
  - lib/charming/presentation/layout/screen_layout.rb
305
340
  - lib/charming/presentation/layout/split.rb
306
341
  - lib/charming/presentation/markdown.rb
307
- - lib/charming/presentation/markdown/block_renderers.rb
308
- - lib/charming/presentation/markdown/inline_renderers.rb
309
342
  - lib/charming/presentation/markdown/render_context.rb
310
343
  - lib/charming/presentation/markdown/renderer.rb
344
+ - lib/charming/presentation/markdown/style_config.rb
311
345
  - lib/charming/presentation/markdown/syntax_highlighter.rb
312
346
  - lib/charming/presentation/template_view.rb
313
347
  - lib/charming/presentation/templates.rb
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
-
5
- module Charming
6
- # DatabaseCommands implements the runtime side of `charming db:COMMAND` (other than
7
- # `db:install`, which lives in DatabaseInstaller). It loads the app's `config/database.rb`,
8
- # delegates the actual work to ActiveRecord, and prints a short status line on success.
9
- class DatabaseCommands
10
- # *command* is the subcommand string (e.g., "db:create"). *out* is the status-output
11
- # stream. *destination* is the app root for resolving `config/database.rb` and `db/`.
12
- def initialize(command, out:, destination:)
13
- @command = command
14
- @out = out
15
- @destination = destination
16
- end
17
-
18
- # Dispatches the configured command. Raises Generators::Error for unknown commands.
19
- def run
20
- case command
21
- when "db:create" then create
22
- when "db:migrate" then migrate
23
- when "db:rollback" then rollback
24
- when "db:drop" then drop
25
- when "db:seed" then seed
26
- else raise Generators::Error, "Unknown database command: #{command}"
27
- end
28
- end
29
-
30
- private
31
-
32
- # The subcommand, output stream, and app destination.
33
- attr_reader :command, :out, :destination
34
-
35
- # Creates the SQLite database file (touch) and establishes the connection.
36
- def create
37
- load_database
38
- FileUtils.mkdir_p(File.dirname(database_path)) if database_path
39
- FileUtils.touch(database_path) if database_path
40
- ActiveRecord::Base.connection
41
- out.puts "create #{relative_database_path}"
42
- end
43
-
44
- # Runs all pending migrations from `db/migrate`.
45
- def migrate
46
- load_database
47
- migration_context.migrate
48
- out.puts "migrate db/migrate"
49
- end
50
-
51
- # Rolls back the most recent migration.
52
- def rollback
53
- load_database
54
- migration_context.rollback(1)
55
- out.puts "rollback db/migrate"
56
- end
57
-
58
- # Disconnects ActiveRecord, then deletes the database file.
59
- def drop
60
- load_database
61
- ActiveRecord::Base.connection.disconnect!
62
- File.delete(database_path) if database_path && File.exist?(database_path)
63
- out.puts "drop #{relative_database_path}"
64
- end
65
-
66
- # Loads `db/seeds.rb` (raises if missing).
67
- def seed
68
- load_database
69
- seed_path = File.join(destination, "db", "seeds.rb")
70
- raise Generators::Error, "Missing file: db/seeds.rb" unless File.exist?(seed_path)
71
-
72
- load seed_path
73
- out.puts "seed db/seeds.rb"
74
- end
75
-
76
- # Loads the app's `config/database.rb` (raises if missing) which establishes the connection.
77
- def load_database
78
- database_config = File.join(destination, "config", "database.rb")
79
- raise Generators::Error, "Database support is not configured. Missing config/database.rb." unless File.exist?(database_config)
80
-
81
- require database_config
82
- end
83
-
84
- # The ActiveRecord migration context rooted at `db/migrate` inside the app.
85
- def migration_context
86
- ActiveRecord::MigrationContext.new(File.join(destination, "db", "migrate"))
87
- end
88
-
89
- # The configured database file path (nil when ActiveRecord isn't connected to a file).
90
- def database_path
91
- ActiveRecord::Base.connection_db_config.database
92
- end
93
-
94
- # The database path relative to the app root, used for human-friendly status output.
95
- def relative_database_path
96
- return "database" unless database_path
97
-
98
- base = File.realpath(destination)
99
- path = File.expand_path(database_path)
100
- path.start_with?("#{base}/") ? path.delete_prefix("#{base}/") : path
101
- end
102
- end
103
- end