charming 0.1.0 → 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 +38 -378
- data/lib/charming/application.rb +14 -3
- data/lib/charming/{application_model.rb → application_state.rb} +3 -3
- data/lib/charming/cli.rb +62 -3
- 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 +46 -448
- data/lib/charming/database_commands.rb +103 -0
- data/lib/charming/database_installer.rb +152 -0
- data/lib/charming/events/key_event.rb +15 -0
- data/lib/charming/events/mouse_event.rb +42 -0
- data/lib/charming/events/resize_event.rb +9 -0
- data/lib/charming/events/task_event.rb +19 -0
- data/lib/charming/events/timer_event.rb +9 -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 +147 -45
- 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 -14
- data/lib/charming/generators/model_generator.rb +128 -0
- data/lib/charming/generators/name.rb +10 -4
- data/lib/charming/generators/screen_generator.rb +84 -52
- 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 +26 -13
- data/lib/charming/internal/renderer/differential.rb +17 -3
- 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 +62 -115
- data/lib/charming/presentation/component.rb +10 -0
- data/lib/charming/presentation/components/activity_indicator.rb +160 -0
- data/lib/charming/presentation/components/command_palette.rb +120 -0
- data/lib/charming/presentation/components/empty_state.rb +56 -0
- data/lib/charming/presentation/components/form/builder.rb +62 -0
- data/lib/charming/presentation/components/form/confirm.rb +69 -0
- data/lib/charming/presentation/components/form/field.rb +121 -0
- data/lib/charming/presentation/components/form/input.rb +71 -0
- data/lib/charming/presentation/components/form/note.rb +41 -0
- data/lib/charming/presentation/components/form/select.rb +112 -0
- data/lib/charming/presentation/components/form/textarea.rb +86 -0
- data/lib/charming/presentation/components/form.rb +156 -0
- data/lib/charming/presentation/components/keyboard_handler.rb +58 -0
- data/lib/charming/presentation/components/list.rb +132 -0
- data/lib/charming/presentation/components/markdown.rb +31 -0
- data/lib/charming/presentation/components/modal.rb +64 -0
- data/lib/charming/presentation/components/progressbar.rb +70 -0
- data/lib/charming/presentation/components/spinner.rb +49 -0
- data/lib/charming/presentation/components/table.rb +143 -0
- data/lib/charming/presentation/components/text_area.rb +267 -0
- data/lib/charming/presentation/components/text_input.rb +129 -0
- data/lib/charming/presentation/components/viewport.rb +272 -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/layout.rb +43 -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 +113 -0
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +79 -0
- data/lib/charming/presentation/markdown.rb +11 -0
- data/lib/charming/presentation/template_view.rb +34 -0
- data/lib/charming/presentation/templates/erb_handler.rb +15 -0
- data/lib/charming/presentation/templates.rb +68 -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.rb +35 -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 +213 -0
- data/lib/charming/presentation/ui/theme.rb +180 -0
- data/lib/charming/{ui → presentation/ui}/themes/phosphor.json +2 -2
- data/lib/charming/presentation/ui/width.rb +26 -0
- data/lib/charming/presentation/ui.rb +91 -0
- data/lib/charming/presentation/view.rb +135 -0
- data/lib/charming/runtime.rb +9 -7
- data/lib/charming/screen.rb +5 -1
- data/lib/charming/tasks/inline_executor.rb +37 -0
- data/lib/charming/tasks/task.rb +12 -0
- data/lib/charming/tasks/threaded_executor.rb +51 -0
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +17 -0
- metadata +170 -36
- data/lib/charming/component.rb +0 -8
- data/lib/charming/components/activity_indicator.rb +0 -158
- data/lib/charming/components/command_palette.rb +0 -118
- data/lib/charming/components/keyboard_handler.rb +0 -22
- data/lib/charming/components/list.rb +0 -105
- data/lib/charming/components/modal.rb +0 -48
- data/lib/charming/components/progressbar.rb +0 -55
- data/lib/charming/components/spinner.rb +0 -37
- data/lib/charming/components/table.rb +0 -115
- data/lib/charming/components/text_input.rb +0 -103
- data/lib/charming/components/viewport.rb +0 -191
- data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -86
- data/lib/charming/generators/app_generator/basic_templates.rb +0 -69
- data/lib/charming/generators/app_generator/component_templates.rb +0 -36
- data/lib/charming/generators/app_generator/controller_template.rb +0 -69
- data/lib/charming/generators/app_generator/layout_template.rb +0 -160
- data/lib/charming/generators/app_generator/model_templates.rb +0 -30
- data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -70
- data/lib/charming/generators/app_generator/view_template.rb +0 -90
- data/lib/charming/key_event.rb +0 -13
- data/lib/charming/mouse_event.rb +0 -40
- data/lib/charming/resize_event.rb +0 -7
- data/lib/charming/task.rb +0 -7
- data/lib/charming/task_event.rb +0 -17
- data/lib/charming/task_executor.rb +0 -62
- data/lib/charming/timer_event.rb +0 -7
- data/lib/charming/ui/border.rb +0 -33
- data/lib/charming/ui/style.rb +0 -244
- data/lib/charming/ui/theme.rb +0 -178
- data/lib/charming/ui/width.rb +0 -24
- data/lib/charming/ui.rb +0 -230
- data/lib/charming/view.rb +0 -116
- /data/lib/charming/{generators.rb → generators/error.rb} +0 -0
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module ComponentTemplates
|
|
7
|
-
def component
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
module #{name.class_name}
|
|
11
|
-
class AppFrameComponent < Charming::Component
|
|
12
|
-
def render
|
|
13
|
-
column(title_line, help_line, gap: 1)
|
|
14
|
-
end
|
|
15
|
-
#{component_helpers}
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def component_helpers
|
|
22
|
-
%(
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def title_line
|
|
26
|
-
text title, style: theme.title
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def help_line
|
|
30
|
-
text "Press p for commands, q to quit.", style: theme.muted
|
|
31
|
-
end)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module ControllerTemplate
|
|
7
|
-
def application_controller
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
module #{name.class_name}
|
|
11
|
-
class ApplicationController < Charming::Controller
|
|
12
|
-
layout Layouts::Application
|
|
13
|
-
focus_ring :sidebar, :content
|
|
14
|
-
|
|
15
|
-
key "p", :open_command_palette, scope: :global
|
|
16
|
-
key "q", :quit, scope: :global
|
|
17
|
-
|
|
18
|
-
command "Home" do
|
|
19
|
-
navigate_to "/"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
command "Theme", :open_theme_palette
|
|
23
|
-
command "Close palette", :close_command_palette
|
|
24
|
-
command "Quit app", :quit
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def controller
|
|
31
|
-
%(# frozen_string_literal: true
|
|
32
|
-
|
|
33
|
-
module #{name.class_name}
|
|
34
|
-
class HomeController < ApplicationController
|
|
35
|
-
#{controller_actions}
|
|
36
|
-
#{controller_helpers}
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def controller_actions
|
|
43
|
-
%(
|
|
44
|
-
def show
|
|
45
|
-
render_home
|
|
46
|
-
end)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def controller_helpers
|
|
50
|
-
%(
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
#{render_helpers})
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def render_helpers
|
|
57
|
-
%(
|
|
58
|
-
def render_home
|
|
59
|
-
render HomeView.new(home: home, palette: command_palette, screen: screen, theme: theme)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def home
|
|
63
|
-
model(:home, HomeModel)
|
|
64
|
-
end)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module LayoutTemplate
|
|
7
|
-
def layout
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
module #{name.class_name}
|
|
11
|
-
module Layouts
|
|
12
|
-
class Application < Charming::View
|
|
13
|
-
def render
|
|
14
|
-
body = Charming::UI.place(app_frame, width: screen.width, height: screen.height)
|
|
15
|
-
return body unless palette
|
|
16
|
-
|
|
17
|
-
Charming::UI.overlay(body, command_palette_modal)
|
|
18
|
-
end
|
|
19
|
-
#{layout_helpers}
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def layout_helpers
|
|
27
|
-
%(
|
|
28
|
-
private
|
|
29
|
-
#{layout_frame_helpers}
|
|
30
|
-
#{layout_navigation_helpers}
|
|
31
|
-
#{layout_modal_helpers}
|
|
32
|
-
#{layout_style_helpers})
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def layout_frame_helpers
|
|
36
|
-
%(
|
|
37
|
-
def app_frame
|
|
38
|
-
narrow? ? column(sidebar, main_content, gap: 1) : row(sidebar, main_content, gap: 1)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def sidebar
|
|
42
|
-
box(column(app_title, navigation, shortcuts, gap: 1), style: sidebar_style)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def main_content
|
|
46
|
-
box(yield_content, style: main_content_style)
|
|
47
|
-
end)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def layout_navigation_helpers
|
|
51
|
-
%(
|
|
52
|
-
|
|
53
|
-
def app_title
|
|
54
|
-
text "#{name.class_name}", style: theme.header_accent.align(:center).width(sidebar_width)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def navigation
|
|
58
|
-
column(*nav_items)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def nav_items
|
|
62
|
-
controller.application.routes.all.each_with_index.map do |route, index|
|
|
63
|
-
text nav_label(route, index), style: nav_style(route, index)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def nav_label(route, index)
|
|
68
|
-
cursor = sidebar_focused? && index == sidebar_index ? ">" : " "
|
|
69
|
-
active = current_route?(route) ? "●" : " "
|
|
70
|
-
"\#{cursor} \#{active} \#{route.title}"
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def nav_style(route, index)
|
|
74
|
-
return theme.selected if sidebar_focused? && index == sidebar_index
|
|
75
|
-
return theme.title if current_route?(route)
|
|
76
|
-
|
|
77
|
-
theme.muted
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def shortcuts
|
|
81
|
-
text "tab focus\\np commands\\nq quit", style: theme.muted
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def sidebar_focused?
|
|
85
|
-
controller.sidebar_focused?
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def content_focused?
|
|
89
|
-
controller.content_focused?
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def sidebar_index
|
|
93
|
-
controller.sidebar_index
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def current_route?(route)
|
|
97
|
-
route.controller_class == controller.class && route.action == :show
|
|
98
|
-
end)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def layout_modal_helpers
|
|
102
|
-
%(
|
|
103
|
-
|
|
104
|
-
def command_palette_modal
|
|
105
|
-
render_component Charming::Components::Modal.new(
|
|
106
|
-
content: palette,
|
|
107
|
-
title: "Command palette",
|
|
108
|
-
help: "Type to filter. Enter selects. Escape closes.",
|
|
109
|
-
width: 52,
|
|
110
|
-
theme: theme
|
|
111
|
-
)
|
|
112
|
-
end)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def layout_style_helpers
|
|
116
|
-
%(
|
|
117
|
-
#{layout_frame_style_helpers}
|
|
118
|
-
#{layout_dimension_helpers})
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def layout_frame_style_helpers
|
|
122
|
-
%(
|
|
123
|
-
def sidebar_style
|
|
124
|
-
base = sidebar_focused? ? theme.title : theme.border
|
|
125
|
-
base = base.border(:rounded).padding(1, 2).width(sidebar_width).height(panel_height)
|
|
126
|
-
palette ? base.faint : base
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def main_content_style
|
|
130
|
-
base = content_focused? ? theme.title : theme.border
|
|
131
|
-
base = base.border(:rounded).padding(1, 2).width(main_content_width).height(panel_height)
|
|
132
|
-
palette ? base.faint : base
|
|
133
|
-
end)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def layout_dimension_helpers
|
|
137
|
-
%(
|
|
138
|
-
|
|
139
|
-
def narrow?
|
|
140
|
-
screen.width < 72 && screen.height >= 20
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def sidebar_width
|
|
144
|
-
narrow? ? [screen.width - 6, 20].max : 22
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def main_content_width
|
|
148
|
-
narrow? ? [screen.width - 6, 20].max : [screen.width - sidebar_width - 13, 20].max
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def panel_height
|
|
152
|
-
return nil if narrow?
|
|
153
|
-
|
|
154
|
-
[screen.height - 4, 5].max
|
|
155
|
-
end)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module ModelTemplates
|
|
7
|
-
def application_model
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
module #{name.class_name}
|
|
11
|
-
class ApplicationModel < Charming::ApplicationModel
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def home_model
|
|
18
|
-
%(# frozen_string_literal: true
|
|
19
|
-
|
|
20
|
-
module #{name.class_name}
|
|
21
|
-
class HomeModel < ApplicationModel
|
|
22
|
-
attribute :title, :string, default: "#{name.class_name}"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module ScreenSpecTemplates
|
|
7
|
-
def spec_model
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
require "#{app_name.snake_name}"
|
|
11
|
-
|
|
12
|
-
RSpec.describe #{app_name.class_name}::#{name.class_name}Model do
|
|
13
|
-
describe "#title" do
|
|
14
|
-
it "has the correct default string value" do
|
|
15
|
-
instance = described_class.new
|
|
16
|
-
expect(instance.title).to eq("#{name.class_name}")
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "accepts overridden title values" do
|
|
20
|
-
instance = described_class.new(title: "Alternative")
|
|
21
|
-
expect(instance.title).to eq("Alternative")
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def spec_controller
|
|
29
|
-
%(# frozen_string_literal: true
|
|
30
|
-
|
|
31
|
-
require "#{app_name.snake_name}"
|
|
32
|
-
|
|
33
|
-
RSpec.describe #{app_name.class_name}::#{name.controller_class_name} do
|
|
34
|
-
let(:application) { #{app_name.class_name}::Application.new }
|
|
35
|
-
|
|
36
|
-
subject(:controller) { described_class.new(application: application) }
|
|
37
|
-
|
|
38
|
-
describe "#show" do
|
|
39
|
-
it "renders the view with the model" do
|
|
40
|
-
response = controller.dispatch(:show)
|
|
41
|
-
|
|
42
|
-
expect(response).to respond_to(:body)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def spec_view
|
|
50
|
-
%(# frozen_string_literal: true
|
|
51
|
-
|
|
52
|
-
require "#{app_name.snake_name}"
|
|
53
|
-
|
|
54
|
-
RSpec.describe #{app_name.class_name}::#{name.view_class_name} do
|
|
55
|
-
describe "#render" do
|
|
56
|
-
it "renders the model title" do
|
|
57
|
-
view = described_class.new(
|
|
58
|
-
#{name.snake_name}: double(title: "#{name.class_name}")
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
expect(view.render).to eq("#{name.class_name}")
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module ViewTemplate
|
|
7
|
-
def executable
|
|
8
|
-
%(#!/usr/bin/env ruby
|
|
9
|
-
# frozen_string_literal: true
|
|
10
|
-
|
|
11
|
-
require "bundler/setup"
|
|
12
|
-
require "#{name.snake_name}"
|
|
13
|
-
|
|
14
|
-
Charming.run(#{name.class_name}::Application.new)
|
|
15
|
-
)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def root_file
|
|
19
|
-
%(# frozen_string_literal: true
|
|
20
|
-
|
|
21
|
-
require "charming"
|
|
22
|
-
require "zeitwerk"
|
|
23
|
-
|
|
24
|
-
module #{name.class_name}
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
loader = Zeitwerk::Loader.new
|
|
28
|
-
loader.tag = "#{name.snake_name}"
|
|
29
|
-
loader.inflector.inflect("version" => "VERSION")
|
|
30
|
-
loader.push_dir(File.expand_path("#{name.snake_name}", __dir__), namespace: #{name.class_name})
|
|
31
|
-
loader.push_dir(File.expand_path("../app/models", __dir__), namespace: #{name.class_name})
|
|
32
|
-
loader.push_dir(File.expand_path("../app/components", __dir__), namespace: #{name.class_name})
|
|
33
|
-
loader.push_dir(File.expand_path("../app/views", __dir__), namespace: #{name.class_name})
|
|
34
|
-
loader.push_dir(File.expand_path("../app/controllers", __dir__), namespace: #{name.class_name})
|
|
35
|
-
loader.setup
|
|
36
|
-
|
|
37
|
-
require_relative "../config/routes"
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def application
|
|
42
|
-
%(# frozen_string_literal: true
|
|
43
|
-
|
|
44
|
-
module #{name.class_name}
|
|
45
|
-
class Application < Charming::Application
|
|
46
|
-
Charming::UI::Theme.built_in_names.each do |theme_name|
|
|
47
|
-
theme theme_name.to_sym, built_in: theme_name
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
default_theme :phosphor
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def version
|
|
57
|
-
%(# frozen_string_literal: true
|
|
58
|
-
|
|
59
|
-
module #{name.class_name}
|
|
60
|
-
VERSION = "0.1.0"
|
|
61
|
-
end
|
|
62
|
-
)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def view
|
|
66
|
-
%(# frozen_string_literal: true
|
|
67
|
-
|
|
68
|
-
module #{name.class_name}
|
|
69
|
-
class HomeView < Charming::View
|
|
70
|
-
def render
|
|
71
|
-
app_frame
|
|
72
|
-
end
|
|
73
|
-
#{view_helpers}
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def view_helpers
|
|
80
|
-
%(
|
|
81
|
-
private
|
|
82
|
-
|
|
83
|
-
def app_frame
|
|
84
|
-
render_component AppFrameComponent.new(title: home.title, theme: theme)
|
|
85
|
-
end)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
data/lib/charming/key_event.rb
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# KeyEvent represents a terminal key press parsed by the backend. *key* is the normalized semantic
|
|
5
|
-
# action name (e.g., `:up`, `:down`, `:q`), while *char*, *ctrl*, *alt*, and *shift* capture raw
|
|
6
|
-
# input details for custom bindings.
|
|
7
|
-
KeyEvent = Data.define(:key, :char, :ctrl, :alt, :shift) do
|
|
8
|
-
# Constructs a key event with the required *key* symbol, plus optional *char* string and modifier booleans.
|
|
9
|
-
def initialize(key:, char: nil, ctrl: false, alt: false, shift: false)
|
|
10
|
-
super(key: key.to_sym, char: char, ctrl: ctrl, alt: alt, shift: shift)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
data/lib/charming/mouse_event.rb
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# MOUSE_BUTTON_MAP encodes terminal mouse button codes to semantic symbols. The constant is frozen and private.
|
|
5
|
-
MOUSE_BUTTON_MAP = {
|
|
6
|
-
0 => :left, 1 => :middle, 2 => :right, 3 => :release,
|
|
7
|
-
64 => :scroll_up, 65 => :scroll_down,
|
|
8
|
-
66 => :scroll_up, 67 => :scroll_down
|
|
9
|
-
}.freeze
|
|
10
|
-
private_constant :MOUSE_BUTTON_MAP
|
|
11
|
-
|
|
12
|
-
# MouseEvent represents a mouse input event. *button* encodes which button or action was triggered (left,
|
|
13
|
-
# right, scroll), while *x* and *y* provide the cursor position. Modifier booleans (*ctrl*, *alt*, *shift*)
|
|
14
|
-
# capture key state at the time of the event.
|
|
15
|
-
MouseEvent = Data.define(:button, :x, :y, :ctrl, :alt, :shift) do
|
|
16
|
-
def initialize(button:, x:, y:, ctrl: false, alt: false, shift: false)
|
|
17
|
-
super
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Returns the semantic symbol for *button* — one of `left`, `right`, `scroll_up`, etc. or `:unknown`.
|
|
21
|
-
def button_name
|
|
22
|
-
MOUSE_BUTTON_MAP.fetch(button, :unknown)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Returns `true` when the current event is a click (left, middle, or right button).
|
|
26
|
-
def click?
|
|
27
|
-
%i[left middle right].include?(button_name)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Returns `true` when the button name maps to either direction of scroll.
|
|
31
|
-
def scroll?
|
|
32
|
-
%i[scroll_up scroll_down].include?(button_name)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Returns `true` when the current event is a mouse release action.
|
|
36
|
-
def release?
|
|
37
|
-
button_name == :release
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# ResizeEvent represents a terminal window resize. *width* and *height* carry the new terminal dimensions
|
|
5
|
-
# in screen cells, replacing the previous Screen dimensions for all subsequent rendering.
|
|
6
|
-
ResizeEvent = Data.define(:width, :height)
|
|
7
|
-
end
|
data/lib/charming/task.rb
DELETED
data/lib/charming/task_event.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# TaskEvent represents background task completion. *name* is the declared task identifier, *value* carries
|
|
5
|
-
# the return result and *error* captures any exception raised during execution. The `error?` predicate
|
|
6
|
-
# simplifies error handling in controller handlers.
|
|
7
|
-
TaskEvent = Data.define(:name, :value, :error) do
|
|
8
|
-
def initialize(name:, value: nil, error: nil)
|
|
9
|
-
super
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Returns `true` when the task finished with a non-nil exception.
|
|
13
|
-
def error?
|
|
14
|
-
!error.nil?
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module TaskExecutor
|
|
5
|
-
class Threaded
|
|
6
|
-
def initialize(queue)
|
|
7
|
-
@queue = queue
|
|
8
|
-
@threads = []
|
|
9
|
-
@mutex = Mutex.new
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def submit(name, &block)
|
|
13
|
-
task = Task.new(name: name.to_sym, block: block)
|
|
14
|
-
thread = Thread.new(task) { |t| @queue << run(t) }
|
|
15
|
-
@mutex.synchronize { @threads << thread }
|
|
16
|
-
nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def shutdown(timeout: 0.0)
|
|
20
|
-
threads = @mutex.synchronize { @threads.dup }
|
|
21
|
-
threads.each { |thread| thread.join(timeout) }
|
|
22
|
-
threads.each do |thread|
|
|
23
|
-
next unless thread.alive?
|
|
24
|
-
|
|
25
|
-
thread.kill
|
|
26
|
-
thread.join(0)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def run(task)
|
|
33
|
-
TaskEvent.new(name: task.name, value: task.call)
|
|
34
|
-
rescue StandardError, ScriptError => e
|
|
35
|
-
TaskEvent.new(name: task.name, error: e)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class Inline
|
|
40
|
-
def initialize(queue)
|
|
41
|
-
@queue = queue
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def submit(name, &block)
|
|
45
|
-
task = Task.new(name: name.to_sym, block: block)
|
|
46
|
-
@queue << run(task)
|
|
47
|
-
nil
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def shutdown(timeout: 0.0)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
def run(task)
|
|
56
|
-
TaskEvent.new(name: task.name, value: task.call)
|
|
57
|
-
rescue StandardError, ScriptError => e
|
|
58
|
-
TaskEvent.new(name: task.name, error: e)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
data/lib/charming/timer_event.rb
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# TimerEvent represents a timed dispatch from the runtime loop. *name* is the declared timer identifier;
|
|
5
|
-
# *now* is the monotonically rising clock value at emission for throttle comparisons.
|
|
6
|
-
TimerEvent = Data.define(:name, :now)
|
|
7
|
-
end
|
data/lib/charming/ui/border.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module UI
|
|
5
|
-
class Border
|
|
6
|
-
attr_reader :top_left, :top_right, :bottom_left, :bottom_right, :horizontal, :vertical
|
|
7
|
-
|
|
8
|
-
def initialize(corners:, edges:)
|
|
9
|
-
@top_left, @top_right, @bottom_left, @bottom_right = corners
|
|
10
|
-
@horizontal, @vertical = edges
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.fetch(name)
|
|
14
|
-
STYLES.fetch(name.to_sym)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
Border::STYLES = {
|
|
19
|
-
normal: Border.new(
|
|
20
|
-
corners: ["+", "+", "+", "+"], edges: ["-", "|"]
|
|
21
|
-
),
|
|
22
|
-
rounded: Border.new(
|
|
23
|
-
corners: ["╭", "╮", "╰", "╯"], edges: ["─", "│"]
|
|
24
|
-
),
|
|
25
|
-
thick: Border.new(
|
|
26
|
-
corners: ["┏", "┓", "┗", "┛"], edges: ["━", "┃"]
|
|
27
|
-
),
|
|
28
|
-
double: Border.new(
|
|
29
|
-
corners: ["╔", "╗", "╚", "╝"], edges: ["═", "║"]
|
|
30
|
-
)
|
|
31
|
-
}.freeze
|
|
32
|
-
end
|
|
33
|
-
end
|