charming 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/charming/application.rb +11 -0
  4. data/lib/charming/cli.rb +23 -0
  5. data/lib/charming/controller/class_methods.rb +115 -0
  6. data/lib/charming/controller/command_palette.rb +135 -0
  7. data/lib/charming/controller/component_dispatching.rb +81 -0
  8. data/lib/charming/controller/dispatching.rb +60 -0
  9. data/lib/charming/controller/focus_management.rb +30 -0
  10. data/lib/charming/controller/rendering.rb +127 -0
  11. data/lib/charming/controller/session_state.rb +41 -0
  12. data/lib/charming/controller/sidebar_navigation.rb +111 -0
  13. data/lib/charming/controller.rb +35 -559
  14. data/lib/charming/database_commands.rb +16 -0
  15. data/lib/charming/database_installer.rb +27 -0
  16. data/lib/charming/focus.rb +58 -2
  17. data/lib/charming/generators/app_file_generator.rb +13 -0
  18. data/lib/charming/generators/app_generator.rb +123 -47
  19. data/lib/charming/generators/base.rb +26 -0
  20. data/lib/charming/generators/component_generator.rb +10 -10
  21. data/lib/charming/generators/controller_generator.rb +22 -11
  22. data/lib/charming/generators/model_generator.rb +38 -29
  23. data/lib/charming/generators/name.rb +10 -0
  24. data/lib/charming/generators/screen_generator.rb +78 -32
  25. data/lib/charming/generators/templates/app/Gemfile.template +5 -0
  26. data/lib/charming/generators/templates/app/README.md.template +9 -0
  27. data/lib/charming/generators/templates/app/Rakefile.template +3 -0
  28. data/lib/charming/generators/templates/app/application.template +13 -0
  29. data/lib/charming/generators/templates/app/application_controller.template +19 -0
  30. data/lib/charming/generators/templates/app/application_record.template +7 -0
  31. data/lib/charming/generators/templates/app/application_state.template +6 -0
  32. data/lib/charming/generators/templates/app/database_config.template +12 -0
  33. data/lib/charming/generators/templates/app/executable.template +7 -0
  34. data/lib/charming/generators/templates/app/gemspec.template +6 -0
  35. data/lib/charming/generators/templates/app/home_controller.template +6 -0
  36. data/lib/charming/generators/templates/app/home_state.template +7 -0
  37. data/lib/charming/generators/templates/app/keep.template +0 -0
  38. data/lib/charming/generators/templates/app/layout.template +113 -0
  39. data/lib/charming/generators/templates/app/root_file.template +20 -0
  40. data/lib/charming/generators/templates/app/routes.template +5 -0
  41. data/lib/charming/generators/templates/app/seeds.template +1 -0
  42. data/lib/charming/generators/templates/app/spec_controller.template +17 -0
  43. data/lib/charming/generators/templates/app/spec_helper.template +3 -0
  44. data/lib/charming/generators/templates/app/spec_state.template +17 -0
  45. data/lib/charming/generators/templates/app/spec_view.template +16 -0
  46. data/lib/charming/generators/templates/app/version.template +5 -0
  47. data/lib/charming/generators/templates/app/view.template +21 -0
  48. data/lib/charming/generators/templates/component/component.rb.template +9 -0
  49. data/lib/charming/generators/templates/controller/controller.rb.template +6 -0
  50. data/lib/charming/generators/templates/model/migration.rb.template +9 -0
  51. data/lib/charming/generators/templates/model/model.rb.template +6 -0
  52. data/lib/charming/generators/templates/model/spec.rb.template +9 -0
  53. data/lib/charming/generators/templates/screen/controller.rb.template +7 -0
  54. data/lib/charming/generators/templates/screen/spec_controller.rb.template +17 -0
  55. data/lib/charming/generators/templates/screen/spec_state.rb.template +17 -0
  56. data/lib/charming/generators/templates/screen/spec_view.rb.template +13 -0
  57. data/lib/charming/generators/templates/screen/state.rb.template +7 -0
  58. data/lib/charming/generators/templates/screen/view.rb.template +11 -0
  59. data/lib/charming/generators/templates/view/view.rb.template +11 -0
  60. data/lib/charming/generators/view_generator.rb +19 -3
  61. data/lib/charming/internal/renderer/differential.rb +15 -0
  62. data/lib/charming/internal/renderer/full_repaint.rb +6 -0
  63. data/lib/charming/internal/terminal/adapter.rb +29 -3
  64. data/lib/charming/internal/terminal/key_normalizer.rb +84 -0
  65. data/lib/charming/internal/terminal/memory_backend.rb +28 -1
  66. data/lib/charming/internal/terminal/mouse_parser.rb +81 -0
  67. data/lib/charming/internal/terminal/tty_backend.rb +43 -113
  68. data/lib/charming/presentation/components/empty_state.rb +13 -0
  69. data/lib/charming/presentation/components/form/builder.rb +14 -0
  70. data/lib/charming/presentation/components/form/confirm.rb +13 -0
  71. data/lib/charming/presentation/components/form/field.rb +25 -0
  72. data/lib/charming/presentation/components/form/input.rb +14 -0
  73. data/lib/charming/presentation/components/form/note.rb +9 -0
  74. data/lib/charming/presentation/components/form/select.rb +23 -0
  75. data/lib/charming/presentation/components/form/textarea.rb +16 -0
  76. data/lib/charming/presentation/components/form.rb +29 -0
  77. data/lib/charming/presentation/components/list.rb +28 -0
  78. data/lib/charming/presentation/components/markdown.rb +6 -0
  79. data/lib/charming/presentation/components/modal.rb +14 -0
  80. data/lib/charming/presentation/components/progressbar.rb +13 -0
  81. data/lib/charming/presentation/components/spinner.rb +10 -0
  82. data/lib/charming/presentation/components/table.rb +25 -0
  83. data/lib/charming/presentation/components/text_area.rb +48 -0
  84. data/lib/charming/presentation/components/text_input.rb +24 -0
  85. data/lib/charming/presentation/components/viewport.rb +52 -0
  86. data/lib/charming/presentation/layout/builder.rb +86 -0
  87. data/lib/charming/presentation/layout/overlay.rb +57 -0
  88. data/lib/charming/presentation/layout/pane.rb +145 -0
  89. data/lib/charming/presentation/layout/rect.rb +23 -0
  90. data/lib/charming/presentation/layout/screen_layout.rb +60 -0
  91. data/lib/charming/presentation/layout/split.rb +134 -0
  92. data/lib/charming/presentation/markdown/block_renderers.rb +120 -0
  93. data/lib/charming/presentation/markdown/inline_renderers.rb +68 -0
  94. data/lib/charming/presentation/markdown/render_context.rb +22 -0
  95. data/lib/charming/presentation/markdown/renderer.rb +45 -135
  96. data/lib/charming/presentation/markdown/syntax_highlighter.rb +16 -0
  97. data/lib/charming/presentation/markdown.rb +3 -0
  98. data/lib/charming/presentation/template_view.rb +7 -0
  99. data/lib/charming/presentation/templates.rb +17 -0
  100. data/lib/charming/presentation/ui/ansi_codes.rb +89 -0
  101. data/lib/charming/presentation/ui/ansi_slicer.rb +94 -0
  102. data/lib/charming/presentation/ui/border_painter.rb +58 -0
  103. data/lib/charming/presentation/ui/canvas.rb +82 -0
  104. data/lib/charming/presentation/ui/style.rb +62 -95
  105. data/lib/charming/presentation/ui.rb +15 -156
  106. data/lib/charming/presentation/view.rb +17 -0
  107. data/lib/charming/runtime.rb +2 -0
  108. data/lib/charming/tasks/inline_executor.rb +9 -0
  109. data/lib/charming/tasks/task.rb +3 -0
  110. data/lib/charming/tasks/threaded_executor.rb +12 -0
  111. data/lib/charming/version.rb +1 -1
  112. data/lib/charming.rb +13 -0
  113. metadata +59 -10
  114. data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -90
  115. data/lib/charming/generators/app_generator/basic_templates.rb +0 -81
  116. data/lib/charming/generators/app_generator/component_templates.rb +0 -36
  117. data/lib/charming/generators/app_generator/controller_template.rb +0 -60
  118. data/lib/charming/generators/app_generator/database_templates.rb +0 -45
  119. data/lib/charming/generators/app_generator/layout_template.rb +0 -66
  120. data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -69
  121. data/lib/charming/generators/app_generator/state_templates.rb +0 -30
  122. data/lib/charming/generators/app_generator/view_template.rb +0 -84
@@ -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::Presentation::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,60 +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 :show, home: home, palette: command_palette
46
- end)
47
- end
48
-
49
- def controller_helpers
50
- %(
51
-
52
- private
53
- def home
54
- state(:home, HomeState)
55
- end)
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module DatabaseTemplates
7
- def database_config
8
- %(# frozen_string_literal: true
9
-
10
- require "active_record"
11
- require "fileutils"
12
-
13
- database_path = File.expand_path("../db/development.sqlite3", __dir__)
14
- FileUtils.mkdir_p(File.dirname(database_path))
15
-
16
- ActiveRecord::Base.establish_connection(
17
- adapter: "sqlite3",
18
- database: database_path
19
- )
20
- )
21
- end
22
-
23
- def application_record
24
- %(# frozen_string_literal: true
25
-
26
- module #{name.class_name}
27
- class ApplicationRecord < ActiveRecord::Base
28
- self.abstract_class = true
29
- end
30
- end
31
- )
32
- end
33
-
34
- def keep
35
- ""
36
- end
37
-
38
- def seeds
39
- %(# frozen_string_literal: true
40
- )
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module LayoutTemplate
7
- def layout
8
- %(<%
9
- palette_component = assigns.fetch(:palette, nil)
10
- sidebar_focused = controller.sidebar_focused?
11
- content_focused = controller.content_focused?
12
- sidebar_index = controller.sidebar_index
13
- narrow = screen.narrow?(below: 72, min_height: 20)
14
- layout = Charming::Presentation::Layout
15
- sidebar_width = narrow ? layout.available_width(screen, reserved: 6, min: 20) : 22
16
- main_content_width = narrow ? layout.available_width(screen, reserved: 6, min: 20) : layout.clamp_size(screen.width - sidebar_width - 13, min: 20)
17
- panel_height = narrow ? nil : layout.available_height(screen, reserved: 4, min: 5)
18
-
19
- app_title = text "#{name.class_name}", style: theme.header_accent.align(:center).width(sidebar_width)
20
- nav_items = controller.sidebar_routes.each_with_index.map do |route, index|
21
- current_route = controller.current_route?(route)
22
- cursor = sidebar_focused && index == sidebar_index ? ">" : " "
23
- active = current_route ? "●" : " "
24
- item_style = if sidebar_focused && index == sidebar_index
25
- theme.selected
26
- elsif current_route
27
- theme.title
28
- else
29
- theme.muted
30
- end
31
-
32
- text "\#{cursor} \#{active} \#{route.title}", style: item_style
33
- end
34
- navigation = column(*nav_items)
35
- shortcuts = text "tab focus\np commands\nq quit", style: theme.muted
36
-
37
- sidebar_style = sidebar_focused ? theme.title : theme.border
38
- sidebar_style = sidebar_style.border(:rounded).padding(1, 2).width(sidebar_width).height(panel_height)
39
- sidebar_style = sidebar_style.faint if palette_component
40
-
41
- main_content_style = content_focused ? theme.title : theme.border
42
- main_content_style = main_content_style.border(:rounded).padding(1, 2).width(main_content_width).height(panel_height)
43
- main_content_style = main_content_style.faint if palette_component
44
-
45
- sidebar = box(column(app_title, navigation, shortcuts, gap: 1), style: sidebar_style)
46
- main_content = box(yield_content, style: main_content_style)
47
- app_frame = layout.stack_or_row(sidebar, main_content, narrow: narrow, gap: 1)
48
- body = Charming::Presentation::UI.place(app_frame, width: screen.width, height: screen.height)
49
-
50
- if palette_component
51
- command_palette_modal = render_component Charming::Presentation::Components::Modal.new(
52
- content: palette_component,
53
- title: "Command palette",
54
- help: "Type to filter. Enter selects. Escape closes.",
55
- width: 52,
56
- theme: theme
57
- )
58
- body = Charming::Presentation::UI.overlay(body, command_palette_modal)
59
- end
60
- %><%= body %>
61
- )
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ScreenSpecTemplates
7
- def spec_state
8
- %(# frozen_string_literal: true
9
-
10
- require "#{app_name.snake_name}"
11
-
12
- RSpec.describe #{app_name.class_name}::#{name.class_name}State 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 state" 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 "#{name.snake_name}/show template" do
55
- describe "#render" do
56
- it "renders the state title" do
57
- template = Charming::Presentation::Templates.resolve("#{name.snake_name}/show", root: #{app_name.class_name}::Application.root)
58
- view = Charming::Presentation::TemplateView.new(template: template, #{name.snake_name}: double(title: "#{name.class_name}"))
59
-
60
- expect(view.render).to eq("#{name.class_name}")
61
- end
62
- end
63
- end
64
- )
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module StateTemplates
7
- def application_state
8
- %(# frozen_string_literal: true
9
-
10
- module #{name.class_name}
11
- class ApplicationState < Charming::ApplicationState
12
- end
13
- end
14
- )
15
- end
16
-
17
- def home_state
18
- %(# frozen_string_literal: true
19
-
20
- module #{name.class_name}
21
- class HomeState < ApplicationState
22
- attribute :title, :string, default: "#{name.class_name}"
23
- end
24
- end
25
- )
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,84 +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
- #{database_require}
24
-
25
- module #{name.class_name}
26
- end
27
-
28
- loader = Zeitwerk::Loader.new
29
- loader.tag = "#{name.snake_name}"
30
- loader.inflector.inflect("version" => "VERSION")
31
- loader.push_dir(File.expand_path("#{name.snake_name}", __dir__), namespace: #{name.class_name})
32
- #{model_loader}loader.push_dir(File.expand_path("../app/state", __dir__), namespace: #{name.class_name})
33
- loader.push_dir(File.expand_path("../app/components", __dir__), namespace: #{name.class_name})
34
- loader.push_dir(File.expand_path("../app/views", __dir__), namespace: #{name.class_name})
35
- loader.push_dir(File.expand_path("../app/controllers", __dir__), namespace: #{name.class_name})
36
- loader.setup
37
-
38
- require_relative "../config/routes"
39
- )
40
- end
41
-
42
- def application
43
- %(# frozen_string_literal: true
44
-
45
- module #{name.class_name}
46
- class Application < Charming::Application
47
- root File.expand_path("../..", __dir__)
48
-
49
- Charming::Presentation::UI::Theme.built_in_names.each do |theme_name|
50
- theme theme_name.to_sym, built_in: theme_name
51
- end
52
-
53
- default_theme :phosphor
54
- end
55
- end
56
- )
57
- end
58
-
59
- def version
60
- %(# frozen_string_literal: true
61
-
62
- module #{name.class_name}
63
- VERSION = "0.1.0"
64
- end
65
- )
66
- end
67
-
68
- def view
69
- %(<%= render_component AppFrameComponent.new(title: home.title, theme: theme) %>
70
- )
71
- end
72
-
73
- def database_require
74
- database? ? %(require_relative "../config/database") : ""
75
- end
76
-
77
- def model_loader
78
- database? ? %(loader.push_dir(File.expand_path("../app/models", __dir__), namespace: #{name.class_name})
79
- ) : ""
80
- end
81
- end
82
- end
83
- end
84
- end