charming 0.1.0 → 0.1.1
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 +3 -3
- data/lib/charming/{application_model.rb → application_state.rb} +3 -3
- data/lib/charming/cli.rb +39 -3
- data/lib/charming/controller.rb +146 -24
- data/lib/charming/database_commands.rb +87 -0
- data/lib/charming/database_installer.rb +125 -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/generators/app_generator/app_spec_templates.rb +12 -8
- data/lib/charming/generators/app_generator/basic_templates.rb +14 -2
- data/lib/charming/generators/app_generator/component_templates.rb +1 -1
- data/lib/charming/generators/app_generator/controller_template.rb +3 -12
- data/lib/charming/generators/app_generator/database_templates.rb +45 -0
- data/lib/charming/generators/app_generator/layout_template.rb +51 -145
- data/lib/charming/generators/app_generator/screen_spec_templates.rb +7 -8
- data/lib/charming/generators/app_generator/{model_templates.rb → state_templates.rb} +5 -5
- data/lib/charming/generators/app_generator/view_template.rb +12 -18
- data/lib/charming/generators/app_generator.rb +37 -11
- data/lib/charming/generators/component_generator.rb +1 -1
- data/lib/charming/generators/controller_generator.rb +1 -4
- data/lib/charming/generators/model_generator.rb +119 -0
- data/lib/charming/generators/name.rb +0 -4
- data/lib/charming/generators/screen_generator.rb +14 -28
- data/lib/charming/generators/view_generator.rb +11 -14
- data/lib/charming/internal/renderer/differential.rb +2 -3
- data/lib/charming/internal/terminal/tty_backend.rb +25 -8
- 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 +43 -0
- data/lib/charming/presentation/components/form/builder.rb +48 -0
- data/lib/charming/presentation/components/form/confirm.rb +56 -0
- data/lib/charming/presentation/components/form/field.rb +96 -0
- data/lib/charming/presentation/components/form/input.rb +57 -0
- data/lib/charming/presentation/components/form/note.rb +32 -0
- data/lib/charming/presentation/components/form/select.rb +89 -0
- data/lib/charming/presentation/components/form/textarea.rb +70 -0
- data/lib/charming/presentation/components/form.rb +127 -0
- data/lib/charming/presentation/components/keyboard_handler.rb +58 -0
- data/lib/charming/presentation/components/list.rb +104 -0
- data/lib/charming/presentation/components/markdown.rb +25 -0
- data/lib/charming/presentation/components/modal.rb +50 -0
- data/lib/charming/presentation/components/progressbar.rb +57 -0
- data/lib/charming/presentation/components/spinner.rb +39 -0
- data/lib/charming/presentation/components/table.rb +118 -0
- data/lib/charming/presentation/components/text_area.rb +219 -0
- data/lib/charming/presentation/components/text_input.rb +105 -0
- data/lib/charming/presentation/components/viewport.rb +220 -0
- data/lib/charming/presentation/layout.rb +43 -0
- data/lib/charming/presentation/markdown/renderer.rb +203 -0
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +63 -0
- data/lib/charming/presentation/markdown.rb +8 -0
- data/lib/charming/presentation/template_view.rb +27 -0
- data/lib/charming/presentation/templates/erb_handler.rb +15 -0
- data/lib/charming/presentation/templates.rb +51 -0
- data/lib/charming/presentation/ui/border.rb +35 -0
- data/lib/charming/presentation/ui/style.rb +246 -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 +232 -0
- data/lib/charming/presentation/view.rb +118 -0
- data/lib/charming/runtime.rb +7 -7
- data/lib/charming/screen.rb +5 -1
- data/lib/charming/tasks/inline_executor.rb +28 -0
- data/lib/charming/tasks/task.rb +9 -0
- data/lib/charming/{task_executor.rb → tasks/threaded_executor.rb} +4 -27
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +4 -0
- metadata +114 -29
- 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/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/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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
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
|
|
15
|
+
|
|
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
|
|
20
|
+
|
|
21
|
+
# Renders the view's body. Default is empty — subclasses override to return visible text.
|
|
22
|
+
def render
|
|
23
|
+
""
|
|
24
|
+
end
|
|
25
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :assigns
|
|
35
|
+
|
|
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
|
|
40
|
+
|
|
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
|
|
45
|
+
|
|
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
|
|
53
|
+
|
|
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
|
|
60
|
+
|
|
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
|
|
65
|
+
|
|
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
|
|
70
|
+
|
|
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
|
|
75
|
+
|
|
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
|
|
80
|
+
|
|
81
|
+
# Yields the layout's `content` slot — used by view templates to inject their body into a layout wrapper (e.g., sidebar).
|
|
82
|
+
def yield_content
|
|
83
|
+
assigns.fetch(:content, "")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Evaluates a block in the view's context with a clean output buffer. Captures text written via `text`/`box`
|
|
87
|
+
# and returns joined content. Resets buffer afterward for parent rendering.
|
|
88
|
+
def capture(&)
|
|
89
|
+
previous_buffer = @output_buffer
|
|
90
|
+
@output_buffer = []
|
|
91
|
+
result = instance_eval(&)
|
|
92
|
+
@output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
|
|
93
|
+
ensure
|
|
94
|
+
@output_buffer = previous_buffer
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Appends a value to the current output buffer (if one is active). Used by rendering helpers.
|
|
98
|
+
def append_to_buffer(value)
|
|
99
|
+
@output_buffer << value if @output_buffer
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Applies a style object's `render` method to a string, returning styled output or raw text when style is nil.
|
|
103
|
+
def apply_style(value, style_object)
|
|
104
|
+
style_object ? style_object.render(value) : value
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Dynamically defines read-only accessor methods for each assign key as singleton methods on self.
|
|
108
|
+
# Skips keys where the view already responds (controller methods take precedence).
|
|
109
|
+
def define_assign_readers
|
|
110
|
+
assigns.each_key do |name|
|
|
111
|
+
next if respond_to?(name, true)
|
|
112
|
+
|
|
113
|
+
define_singleton_method(name) { assigns.fetch(name) }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/charming/runtime.rb
CHANGED
|
@@ -76,16 +76,16 @@ module Charming
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def controller(event: nil)
|
|
79
|
-
@route.controller_class.new(application: @application, event: event, params: @route.params, screen: screen)
|
|
79
|
+
@route.controller_class.new(application: @application, event: event, params: @route.params, screen: screen, route: @route)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Type-based dispatcher: routes resize, task, timer, mouse, and key events
|
|
83
83
|
# to the appropriate handler. Falls back to key dispatch for unclassified events.
|
|
84
84
|
def dispatch_event(event)
|
|
85
|
-
return dispatch_resize(event) if event.is_a?(ResizeEvent)
|
|
86
|
-
return dispatch_task(event) if event.is_a?(TaskEvent)
|
|
87
|
-
return dispatch_timer(event) if event.is_a?(TimerEvent)
|
|
88
|
-
return dispatch_mouse(event) if event.is_a?(MouseEvent)
|
|
85
|
+
return dispatch_resize(event) if event.is_a?(Events::ResizeEvent)
|
|
86
|
+
return dispatch_task(event) if event.is_a?(Events::TaskEvent)
|
|
87
|
+
return dispatch_timer(event) if event.is_a?(Events::TimerEvent)
|
|
88
|
+
return dispatch_mouse(event) if event.is_a?(Events::MouseEvent)
|
|
89
89
|
|
|
90
90
|
dispatch_key(event)
|
|
91
91
|
end
|
|
@@ -133,7 +133,7 @@ module Charming
|
|
|
133
133
|
|
|
134
134
|
now = clock_now
|
|
135
135
|
timer[:next_at] = now + timer.fetch(:binding).interval
|
|
136
|
-
TimerEvent.new(name: timer.fetch(:binding).name, now: now)
|
|
136
|
+
Events::TimerEvent.new(name: timer.fetch(:binding).name, now: now)
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
# Pops a task event from the thread-safe queue if one is available.
|
|
@@ -161,7 +161,7 @@ module Charming
|
|
|
161
161
|
|
|
162
162
|
# Constructs a task executor: supports explicit instances, callable factories, or the default Threaded executor.
|
|
163
163
|
def build_task_executor(task_executor)
|
|
164
|
-
return
|
|
164
|
+
return Tasks::ThreadedExecutor.new(@task_queue) unless task_executor
|
|
165
165
|
return task_executor if task_executor.respond_to?(:submit)
|
|
166
166
|
return task_executor.call(@task_queue) if task_executor.respond_to?(:call) && !task_executor.respond_to?(:new)
|
|
167
167
|
|
data/lib/charming/screen.rb
CHANGED
|
@@ -4,5 +4,9 @@ module Charming
|
|
|
4
4
|
# Screen represents the terminal viewport dimensions as a simple Data class.
|
|
5
5
|
# The `width` and `height` values flow from the backend through the runtime
|
|
6
6
|
# loop into every controller dispatch for layout calculations.
|
|
7
|
-
Screen = Data.define(:width, :height)
|
|
7
|
+
Screen = Data.define(:width, :height) do
|
|
8
|
+
def narrow?(below:, min_height: nil)
|
|
9
|
+
width < below && (min_height.nil? || height >= min_height)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
8
12
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Charming
|
|
4
|
+
module Tasks
|
|
5
|
+
class InlineExecutor
|
|
6
|
+
def initialize(queue)
|
|
7
|
+
@queue = queue
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def submit(name, &block)
|
|
11
|
+
task = Task.new(name: name.to_sym, block: block)
|
|
12
|
+
@queue << run(task)
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def shutdown(timeout: 0.0)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def run(task)
|
|
22
|
+
Events::TaskEvent.new(name: task.name, value: task.call)
|
|
23
|
+
rescue StandardError, ScriptError => e
|
|
24
|
+
Events::TaskEvent.new(name: task.name, error: e)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
class
|
|
4
|
+
module Tasks
|
|
5
|
+
class ThreadedExecutor
|
|
6
6
|
def initialize(queue)
|
|
7
7
|
@queue = queue
|
|
8
8
|
@threads = []
|
|
@@ -30,32 +30,9 @@ module Charming
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def run(task)
|
|
33
|
-
TaskEvent.new(name: task.name, value: task.call)
|
|
33
|
+
Events::TaskEvent.new(name: task.name, value: task.call)
|
|
34
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)
|
|
35
|
+
Events::TaskEvent.new(name: task.name, error: e)
|
|
59
36
|
end
|
|
60
37
|
end
|
|
61
38
|
end
|
data/lib/charming/version.rb
CHANGED
data/lib/charming.rb
CHANGED
|
@@ -6,6 +6,7 @@ loader = Zeitwerk::Loader.for_gem
|
|
|
6
6
|
loader.inflector.inflect(
|
|
7
7
|
"cli" => "CLI",
|
|
8
8
|
"ui" => "UI",
|
|
9
|
+
"erb_handler" => "ErbHandler",
|
|
9
10
|
"tty_backend" => "TTYBackend"
|
|
10
11
|
)
|
|
11
12
|
loader.setup
|
|
@@ -22,3 +23,6 @@ module Charming
|
|
|
22
23
|
key.to_sym
|
|
23
24
|
end
|
|
24
25
|
end
|
|
26
|
+
|
|
27
|
+
Charming::Presentation::Templates.register ".tui.erb", Charming::Presentation::Templates::ErbHandler
|
|
28
|
+
Charming::Presentation::Templates.register ".txt.erb", Charming::Presentation::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.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- pando
|
|
@@ -29,6 +29,68 @@ dependencies:
|
|
|
29
29
|
- - ">="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: 8.1.2
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activerecord
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '8.1'
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 8.1.2
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '8.1'
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 8.1.2
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: kramdown
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '2.5'
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '2.5'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: rouge
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '5.0'
|
|
73
|
+
type: :runtime
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - "~>"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '5.0'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: sqlite3
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - "~>"
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '2.0'
|
|
87
|
+
type: :runtime
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - "~>"
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '2.0'
|
|
32
94
|
- !ruby/object:Gem::Dependency
|
|
33
95
|
name: tty-cursor
|
|
34
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -141,35 +203,33 @@ files:
|
|
|
141
203
|
- exe/charming
|
|
142
204
|
- lib/charming.rb
|
|
143
205
|
- lib/charming/application.rb
|
|
144
|
-
- lib/charming/
|
|
206
|
+
- lib/charming/application_state.rb
|
|
145
207
|
- lib/charming/cli.rb
|
|
146
|
-
- lib/charming/component.rb
|
|
147
|
-
- lib/charming/components/activity_indicator.rb
|
|
148
|
-
- lib/charming/components/command_palette.rb
|
|
149
|
-
- lib/charming/components/keyboard_handler.rb
|
|
150
|
-
- lib/charming/components/list.rb
|
|
151
|
-
- lib/charming/components/modal.rb
|
|
152
|
-
- lib/charming/components/progressbar.rb
|
|
153
|
-
- lib/charming/components/spinner.rb
|
|
154
|
-
- lib/charming/components/table.rb
|
|
155
|
-
- lib/charming/components/text_input.rb
|
|
156
|
-
- lib/charming/components/viewport.rb
|
|
157
208
|
- lib/charming/controller.rb
|
|
209
|
+
- lib/charming/database_commands.rb
|
|
210
|
+
- lib/charming/database_installer.rb
|
|
211
|
+
- lib/charming/events/key_event.rb
|
|
212
|
+
- lib/charming/events/mouse_event.rb
|
|
213
|
+
- lib/charming/events/resize_event.rb
|
|
214
|
+
- lib/charming/events/task_event.rb
|
|
215
|
+
- lib/charming/events/timer_event.rb
|
|
158
216
|
- lib/charming/focus.rb
|
|
159
|
-
- lib/charming/generators.rb
|
|
160
217
|
- lib/charming/generators/app_file_generator.rb
|
|
161
218
|
- lib/charming/generators/app_generator.rb
|
|
162
219
|
- lib/charming/generators/app_generator/app_spec_templates.rb
|
|
163
220
|
- lib/charming/generators/app_generator/basic_templates.rb
|
|
164
221
|
- lib/charming/generators/app_generator/component_templates.rb
|
|
165
222
|
- lib/charming/generators/app_generator/controller_template.rb
|
|
223
|
+
- lib/charming/generators/app_generator/database_templates.rb
|
|
166
224
|
- lib/charming/generators/app_generator/layout_template.rb
|
|
167
|
-
- lib/charming/generators/app_generator/model_templates.rb
|
|
168
225
|
- lib/charming/generators/app_generator/screen_spec_templates.rb
|
|
226
|
+
- lib/charming/generators/app_generator/state_templates.rb
|
|
169
227
|
- lib/charming/generators/app_generator/view_template.rb
|
|
170
228
|
- lib/charming/generators/base.rb
|
|
171
229
|
- lib/charming/generators/component_generator.rb
|
|
172
230
|
- lib/charming/generators/controller_generator.rb
|
|
231
|
+
- lib/charming/generators/error.rb
|
|
232
|
+
- lib/charming/generators/model_generator.rb
|
|
173
233
|
- lib/charming/generators/name.rb
|
|
174
234
|
- lib/charming/generators/screen_generator.rb
|
|
175
235
|
- lib/charming/generators/view_generator.rb
|
|
@@ -178,25 +238,50 @@ files:
|
|
|
178
238
|
- lib/charming/internal/terminal/adapter.rb
|
|
179
239
|
- lib/charming/internal/terminal/memory_backend.rb
|
|
180
240
|
- lib/charming/internal/terminal/tty_backend.rb
|
|
181
|
-
- lib/charming/
|
|
182
|
-
- lib/charming/
|
|
183
|
-
- lib/charming/
|
|
241
|
+
- lib/charming/presentation/component.rb
|
|
242
|
+
- lib/charming/presentation/components/activity_indicator.rb
|
|
243
|
+
- lib/charming/presentation/components/command_palette.rb
|
|
244
|
+
- lib/charming/presentation/components/empty_state.rb
|
|
245
|
+
- lib/charming/presentation/components/form.rb
|
|
246
|
+
- lib/charming/presentation/components/form/builder.rb
|
|
247
|
+
- lib/charming/presentation/components/form/confirm.rb
|
|
248
|
+
- lib/charming/presentation/components/form/field.rb
|
|
249
|
+
- lib/charming/presentation/components/form/input.rb
|
|
250
|
+
- lib/charming/presentation/components/form/note.rb
|
|
251
|
+
- lib/charming/presentation/components/form/select.rb
|
|
252
|
+
- lib/charming/presentation/components/form/textarea.rb
|
|
253
|
+
- lib/charming/presentation/components/keyboard_handler.rb
|
|
254
|
+
- lib/charming/presentation/components/list.rb
|
|
255
|
+
- lib/charming/presentation/components/markdown.rb
|
|
256
|
+
- lib/charming/presentation/components/modal.rb
|
|
257
|
+
- lib/charming/presentation/components/progressbar.rb
|
|
258
|
+
- lib/charming/presentation/components/spinner.rb
|
|
259
|
+
- lib/charming/presentation/components/table.rb
|
|
260
|
+
- lib/charming/presentation/components/text_area.rb
|
|
261
|
+
- lib/charming/presentation/components/text_input.rb
|
|
262
|
+
- lib/charming/presentation/components/viewport.rb
|
|
263
|
+
- lib/charming/presentation/layout.rb
|
|
264
|
+
- lib/charming/presentation/markdown.rb
|
|
265
|
+
- lib/charming/presentation/markdown/renderer.rb
|
|
266
|
+
- lib/charming/presentation/markdown/syntax_highlighter.rb
|
|
267
|
+
- lib/charming/presentation/template_view.rb
|
|
268
|
+
- lib/charming/presentation/templates.rb
|
|
269
|
+
- lib/charming/presentation/templates/erb_handler.rb
|
|
270
|
+
- lib/charming/presentation/ui.rb
|
|
271
|
+
- lib/charming/presentation/ui/border.rb
|
|
272
|
+
- lib/charming/presentation/ui/style.rb
|
|
273
|
+
- lib/charming/presentation/ui/theme.rb
|
|
274
|
+
- lib/charming/presentation/ui/themes/phosphor.json
|
|
275
|
+
- lib/charming/presentation/ui/width.rb
|
|
276
|
+
- lib/charming/presentation/view.rb
|
|
184
277
|
- lib/charming/response.rb
|
|
185
278
|
- lib/charming/router.rb
|
|
186
279
|
- lib/charming/runtime.rb
|
|
187
280
|
- lib/charming/screen.rb
|
|
188
|
-
- lib/charming/
|
|
189
|
-
- lib/charming/
|
|
190
|
-
- lib/charming/
|
|
191
|
-
- lib/charming/timer_event.rb
|
|
192
|
-
- lib/charming/ui.rb
|
|
193
|
-
- lib/charming/ui/border.rb
|
|
194
|
-
- lib/charming/ui/style.rb
|
|
195
|
-
- lib/charming/ui/theme.rb
|
|
196
|
-
- lib/charming/ui/themes/phosphor.json
|
|
197
|
-
- lib/charming/ui/width.rb
|
|
281
|
+
- lib/charming/tasks/inline_executor.rb
|
|
282
|
+
- lib/charming/tasks/task.rb
|
|
283
|
+
- lib/charming/tasks/threaded_executor.rb
|
|
198
284
|
- lib/charming/version.rb
|
|
199
|
-
- lib/charming/view.rb
|
|
200
285
|
- sig/charming.rbs
|
|
201
286
|
homepage: https://github.com/pandorocks/charming
|
|
202
287
|
licenses:
|
data/lib/charming/component.rb
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
# Component is the base class for all reusable terminal widgets. It inherits from View to gain assigns,
|
|
5
|
-
# helper methods (text, box, row, column, etc.), and rendering via render.
|
|
6
|
-
class Component < View
|
|
7
|
-
end
|
|
8
|
-
end
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Components
|
|
5
|
-
# ActivityIndicator renders a color-gradient progress or loading indicator
|
|
6
|
-
# as styled text. It produces a fixed-width row of characters whose colors
|
|
7
|
-
# interpolate between two gradient endpoints (or cycle through a single
|
|
8
|
-
# color). A label can be appended after the bar and an ellipsis that cycles
|
|
9
|
-
# through frames, useful for "loading" state display. Call `tick` to advance
|
|
10
|
-
# the frame counter, and call `render` to produce the styled output string.
|
|
11
|
-
class ActivityIndicator < Component
|
|
12
|
-
# Default character pool used for generating each position's character via stable hashing.
|
|
13
|
-
DEFAULT_CHARS = "0123456789abcdefABCDEF~!@#$%^&*+=_".chars.freeze
|
|
14
|
-
|
|
15
|
-
# The default two-color gradient applied across the bar width (red to cyan).
|
|
16
|
-
# The cyan endpoint mirrors the Phosphor theme palette's "cyan" token so the bar
|
|
17
|
-
# remains legible on Phosphor's dark navy background; gradient: accepts raw hex,
|
|
18
|
-
# so callers using a different theme should pass their own endpoints.
|
|
19
|
-
DEFAULT_GRADIENT = ["#ff0000", "#6FD0E3"].freeze
|
|
20
|
-
|
|
21
|
-
# The default label color for ellipsis and text portions when no custom
|
|
22
|
-
# label_style is provided.
|
|
23
|
-
DEFAULT_LABEL_COLOR = "#cccccc"
|
|
24
|
-
|
|
25
|
-
# Ellipsis frame sequence: four states cycle through "., "..", "...", and "" (empty).
|
|
26
|
-
ELLIPSIS_FRAMES = [".", "..", "...", ""].freeze
|
|
27
|
-
|
|
28
|
-
# Number of frames in the animation cycle before the indicator pattern repeats.
|
|
29
|
-
FRAME_COUNT = 10
|
|
30
|
-
|
|
31
|
-
# FNV-1a variant constants used by stable_hash for reproducible character selection per position.
|
|
32
|
-
FNV_OFFSET = 2_166_136_261
|
|
33
|
-
FNV_PRIME = 16_777_619
|
|
34
|
-
FNV_MASK = 0xffffffff
|
|
35
|
-
|
|
36
|
-
attr_reader :width, :label, :index, :seed, :chars, :gradient, :label_style
|
|
37
|
-
|
|
38
|
-
# Initializes a new ActivityIndicator with configurable visual parameters.
|
|
39
|
-
# width — Display width of the gradient bar in characters (minimum 1). Default: 10.
|
|
40
|
-
# label — Optional text label shown adjacent to the indicator.
|
|
41
|
-
# index — Initial frame index for the ellipsis/frame animations. Default: 0.
|
|
42
|
-
# seed — Hash seed that determines which characters appear at each position.
|
|
43
|
-
# chars — Character pool to draw from (default is DEFAULT_CHARS).
|
|
44
|
-
# gradient — Two-element array of hex color strings ["#rrggbb", "#rrggbb"] for interpolation.
|
|
45
|
-
# label_style — A Style object to use for rendering the label text; falls back to a gray foreground.
|
|
46
|
-
def initialize(width: 10, label: nil, index: 0, seed: 0, chars: DEFAULT_CHARS,
|
|
47
|
-
gradient: DEFAULT_GRADIENT, label_style: nil)
|
|
48
|
-
super()
|
|
49
|
-
raise ArgumentError, "chars cannot be empty" if chars.empty?
|
|
50
|
-
|
|
51
|
-
@width = [width.to_i, 1].max
|
|
52
|
-
@label = label
|
|
53
|
-
@index = index.to_i
|
|
54
|
-
@seed = seed
|
|
55
|
-
@chars = chars.map(&:to_s)
|
|
56
|
-
@gradient = gradient
|
|
57
|
-
@label_style = label_style
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Advances the frame counter forward by +count+ steps, allowing the displayed pattern to change.
|
|
61
|
-
# Accepts an integer count (converted via +to_i+). Returns self for chaining.
|
|
62
|
-
def tick(count = 1)
|
|
63
|
-
@index += count.to_i
|
|
64
|
-
self
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Renders the activity indicator as a styled string. If a label was provided,
|
|
68
|
-
# produces "bar ellipsis" alongside it; otherwise produces only the gradient bar.
|
|
69
|
-
# Returns a formatted string suitable for terminal rendering.
|
|
70
|
-
def render
|
|
71
|
-
return indicator unless label
|
|
72
|
-
|
|
73
|
-
"#{indicator} #{styled_label}#{styled_ellipsis}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private
|
|
77
|
-
|
|
78
|
-
# Renders the full gradient bar as an array of styled characters joined into a single string.
|
|
79
|
-
# Each character at +position+ is selected by hashing together seed, frame, and position —
|
|
80
|
-
# making the pattern stable across renders — then styled with the interpolated gradient color
|
|
81
|
-
# at that position.
|
|
82
|
-
def indicator
|
|
83
|
-
Array.new(width) { |position| styled_char(position) }.join
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Selects a character for the bar at the given +position+, styles it with the gradient color
|
|
87
|
-
# interpolated for that position, and returns the result as a formatted string via +render+.
|
|
88
|
-
def styled_char(position)
|
|
89
|
-
style.foreground(color_at(position)).render(char_at(position))
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Chooses a character from self.chars by hashing seed:frame:position together with a stable
|
|
93
|
-
# FNV-1a hash. The resulting index is modulated against the character pool length, ensuring
|
|
94
|
-
# reproducible output across renders.
|
|
95
|
-
def char_at(position)
|
|
96
|
-
chars.fetch(stable_hash("#{seed}:#{frame}:#{position}") % chars.length)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Renders the label text in its own style (or fallback gray color) via a Style renderer call.
|
|
100
|
-
def styled_label
|
|
101
|
-
label_style_or_default.render(label.to_s)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Renders an ellipsis frame (".", "..", "...", or empty) based on (index / 4) mod 4, styled with the label style.
|
|
105
|
-
def styled_ellipsis
|
|
106
|
-
label_style_or_default.render(ellipsis_frame)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Returns the current ellipsis frame string: one of ".", "..", "...", "". Cycles through four frames per tick.
|
|
110
|
-
def ellipsis_frame
|
|
111
|
-
ELLIPSIS_FRAMES.fetch((index / 4) % ELLIPSIS_FRAMES.length)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Returns the label style if set, otherwise produces a gray foreground style for fallback rendering.
|
|
115
|
-
def label_style_or_default
|
|
116
|
-
label_style || style.foreground(DEFAULT_LABEL_COLOR)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Interpolates between gradient[0] and gradient[1] at the fractional +position+ (0.0 to 1.0).
|
|
120
|
-
# Returns the first gradient color if width is 1; otherwise returns a blended hex string based on position.
|
|
121
|
-
def color_at(position)
|
|
122
|
-
return gradient.first unless width > 1
|
|
123
|
-
|
|
124
|
-
blend(gradient.first, gradient.last, position / (width - 1).to_f)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Blends two hex colors by interpolating their red/green/blue components at fractional +amount+.
|
|
128
|
-
# Accepts strings like "#ff0000" and produces a new "#rrggbb" string.
|
|
129
|
-
def blend(start_hex, end_hex, amount)
|
|
130
|
-
start_rgb = rgb(start_hex)
|
|
131
|
-
end_rgb = rgb(end_hex)
|
|
132
|
-
mixed = start_rgb.zip(end_rgb).map { |from, to| (from + ((to - from) * amount)).round }
|
|
133
|
-
"#%02x%02x%02x" % mixed
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Decomposes a hex color string ("#rrggbb") into an array of three integers [r, g, b].
|
|
137
|
-
def rgb(hex)
|
|
138
|
-
value = hex.to_s.delete_prefix("#")
|
|
139
|
-
raise ArgumentError, "gradient colors must be #rrggbb" unless value.match?(/\A[0-9a-fA-F]{6}\z/)
|
|
140
|
-
|
|
141
|
-
[value[0..1], value[2..3], value[4..5]].map { |part| part.to_i(16) }
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Advances the animation frame counter, wrapping around after +FRAME_COUNT+ (10) steps.
|
|
145
|
-
def frame
|
|
146
|
-
index % FRAME_COUNT
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Produces a deterministic integer hash from the input string using FNV-1a hashing, ensuring the same
|
|
150
|
-
# characters appear at the same positions across multiple renderings of this indicator.
|
|
151
|
-
def stable_hash(value)
|
|
152
|
-
value.bytes.reduce(FNV_OFFSET) do |hash, byte|
|
|
153
|
-
((hash ^ byte) * FNV_PRIME) & FNV_MASK
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|