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.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -378
  3. data/lib/charming/application.rb +14 -3
  4. data/lib/charming/{application_model.rb → application_state.rb} +3 -3
  5. data/lib/charming/cli.rb +62 -3
  6. data/lib/charming/controller/class_methods.rb +115 -0
  7. data/lib/charming/controller/command_palette.rb +135 -0
  8. data/lib/charming/controller/component_dispatching.rb +81 -0
  9. data/lib/charming/controller/dispatching.rb +60 -0
  10. data/lib/charming/controller/focus_management.rb +30 -0
  11. data/lib/charming/controller/rendering.rb +127 -0
  12. data/lib/charming/controller/session_state.rb +41 -0
  13. data/lib/charming/controller/sidebar_navigation.rb +111 -0
  14. data/lib/charming/controller.rb +46 -448
  15. data/lib/charming/database_commands.rb +103 -0
  16. data/lib/charming/database_installer.rb +152 -0
  17. data/lib/charming/events/key_event.rb +15 -0
  18. data/lib/charming/events/mouse_event.rb +42 -0
  19. data/lib/charming/events/resize_event.rb +9 -0
  20. data/lib/charming/events/task_event.rb +19 -0
  21. data/lib/charming/events/timer_event.rb +9 -0
  22. data/lib/charming/focus.rb +58 -2
  23. data/lib/charming/generators/app_file_generator.rb +13 -0
  24. data/lib/charming/generators/app_generator.rb +147 -45
  25. data/lib/charming/generators/base.rb +26 -0
  26. data/lib/charming/generators/component_generator.rb +10 -10
  27. data/lib/charming/generators/controller_generator.rb +22 -14
  28. data/lib/charming/generators/model_generator.rb +128 -0
  29. data/lib/charming/generators/name.rb +10 -4
  30. data/lib/charming/generators/screen_generator.rb +84 -52
  31. data/lib/charming/generators/templates/app/Gemfile.template +5 -0
  32. data/lib/charming/generators/templates/app/README.md.template +9 -0
  33. data/lib/charming/generators/templates/app/Rakefile.template +3 -0
  34. data/lib/charming/generators/templates/app/application.template +13 -0
  35. data/lib/charming/generators/templates/app/application_controller.template +19 -0
  36. data/lib/charming/generators/templates/app/application_record.template +7 -0
  37. data/lib/charming/generators/templates/app/application_state.template +6 -0
  38. data/lib/charming/generators/templates/app/database_config.template +12 -0
  39. data/lib/charming/generators/templates/app/executable.template +7 -0
  40. data/lib/charming/generators/templates/app/gemspec.template +6 -0
  41. data/lib/charming/generators/templates/app/home_controller.template +6 -0
  42. data/lib/charming/generators/templates/app/home_state.template +7 -0
  43. data/lib/charming/generators/templates/app/keep.template +0 -0
  44. data/lib/charming/generators/templates/app/layout.template +113 -0
  45. data/lib/charming/generators/templates/app/root_file.template +20 -0
  46. data/lib/charming/generators/templates/app/routes.template +5 -0
  47. data/lib/charming/generators/templates/app/seeds.template +1 -0
  48. data/lib/charming/generators/templates/app/spec_controller.template +17 -0
  49. data/lib/charming/generators/templates/app/spec_helper.template +3 -0
  50. data/lib/charming/generators/templates/app/spec_state.template +17 -0
  51. data/lib/charming/generators/templates/app/spec_view.template +16 -0
  52. data/lib/charming/generators/templates/app/version.template +5 -0
  53. data/lib/charming/generators/templates/app/view.template +21 -0
  54. data/lib/charming/generators/templates/component/component.rb.template +9 -0
  55. data/lib/charming/generators/templates/controller/controller.rb.template +6 -0
  56. data/lib/charming/generators/templates/model/migration.rb.template +9 -0
  57. data/lib/charming/generators/templates/model/model.rb.template +6 -0
  58. data/lib/charming/generators/templates/model/spec.rb.template +9 -0
  59. data/lib/charming/generators/templates/screen/controller.rb.template +7 -0
  60. data/lib/charming/generators/templates/screen/spec_controller.rb.template +17 -0
  61. data/lib/charming/generators/templates/screen/spec_state.rb.template +17 -0
  62. data/lib/charming/generators/templates/screen/spec_view.rb.template +13 -0
  63. data/lib/charming/generators/templates/screen/state.rb.template +7 -0
  64. data/lib/charming/generators/templates/screen/view.rb.template +11 -0
  65. data/lib/charming/generators/templates/view/view.rb.template +11 -0
  66. data/lib/charming/generators/view_generator.rb +26 -13
  67. data/lib/charming/internal/renderer/differential.rb +17 -3
  68. data/lib/charming/internal/renderer/full_repaint.rb +6 -0
  69. data/lib/charming/internal/terminal/adapter.rb +29 -3
  70. data/lib/charming/internal/terminal/key_normalizer.rb +84 -0
  71. data/lib/charming/internal/terminal/memory_backend.rb +28 -1
  72. data/lib/charming/internal/terminal/mouse_parser.rb +81 -0
  73. data/lib/charming/internal/terminal/tty_backend.rb +62 -115
  74. data/lib/charming/presentation/component.rb +10 -0
  75. data/lib/charming/presentation/components/activity_indicator.rb +160 -0
  76. data/lib/charming/presentation/components/command_palette.rb +120 -0
  77. data/lib/charming/presentation/components/empty_state.rb +56 -0
  78. data/lib/charming/presentation/components/form/builder.rb +62 -0
  79. data/lib/charming/presentation/components/form/confirm.rb +69 -0
  80. data/lib/charming/presentation/components/form/field.rb +121 -0
  81. data/lib/charming/presentation/components/form/input.rb +71 -0
  82. data/lib/charming/presentation/components/form/note.rb +41 -0
  83. data/lib/charming/presentation/components/form/select.rb +112 -0
  84. data/lib/charming/presentation/components/form/textarea.rb +86 -0
  85. data/lib/charming/presentation/components/form.rb +156 -0
  86. data/lib/charming/presentation/components/keyboard_handler.rb +58 -0
  87. data/lib/charming/presentation/components/list.rb +132 -0
  88. data/lib/charming/presentation/components/markdown.rb +31 -0
  89. data/lib/charming/presentation/components/modal.rb +64 -0
  90. data/lib/charming/presentation/components/progressbar.rb +70 -0
  91. data/lib/charming/presentation/components/spinner.rb +49 -0
  92. data/lib/charming/presentation/components/table.rb +143 -0
  93. data/lib/charming/presentation/components/text_area.rb +267 -0
  94. data/lib/charming/presentation/components/text_input.rb +129 -0
  95. data/lib/charming/presentation/components/viewport.rb +272 -0
  96. data/lib/charming/presentation/layout/builder.rb +86 -0
  97. data/lib/charming/presentation/layout/overlay.rb +57 -0
  98. data/lib/charming/presentation/layout/pane.rb +145 -0
  99. data/lib/charming/presentation/layout/rect.rb +23 -0
  100. data/lib/charming/presentation/layout/screen_layout.rb +60 -0
  101. data/lib/charming/presentation/layout/split.rb +134 -0
  102. data/lib/charming/presentation/layout.rb +43 -0
  103. data/lib/charming/presentation/markdown/block_renderers.rb +120 -0
  104. data/lib/charming/presentation/markdown/inline_renderers.rb +68 -0
  105. data/lib/charming/presentation/markdown/render_context.rb +22 -0
  106. data/lib/charming/presentation/markdown/renderer.rb +113 -0
  107. data/lib/charming/presentation/markdown/syntax_highlighter.rb +79 -0
  108. data/lib/charming/presentation/markdown.rb +11 -0
  109. data/lib/charming/presentation/template_view.rb +34 -0
  110. data/lib/charming/presentation/templates/erb_handler.rb +15 -0
  111. data/lib/charming/presentation/templates.rb +68 -0
  112. data/lib/charming/presentation/ui/ansi_codes.rb +89 -0
  113. data/lib/charming/presentation/ui/ansi_slicer.rb +94 -0
  114. data/lib/charming/presentation/ui/border.rb +35 -0
  115. data/lib/charming/presentation/ui/border_painter.rb +58 -0
  116. data/lib/charming/presentation/ui/canvas.rb +82 -0
  117. data/lib/charming/presentation/ui/style.rb +213 -0
  118. data/lib/charming/presentation/ui/theme.rb +180 -0
  119. data/lib/charming/{ui → presentation/ui}/themes/phosphor.json +2 -2
  120. data/lib/charming/presentation/ui/width.rb +26 -0
  121. data/lib/charming/presentation/ui.rb +91 -0
  122. data/lib/charming/presentation/view.rb +135 -0
  123. data/lib/charming/runtime.rb +9 -7
  124. data/lib/charming/screen.rb +5 -1
  125. data/lib/charming/tasks/inline_executor.rb +37 -0
  126. data/lib/charming/tasks/task.rb +12 -0
  127. data/lib/charming/tasks/threaded_executor.rb +51 -0
  128. data/lib/charming/version.rb +1 -1
  129. data/lib/charming.rb +17 -0
  130. metadata +170 -36
  131. data/lib/charming/component.rb +0 -8
  132. data/lib/charming/components/activity_indicator.rb +0 -158
  133. data/lib/charming/components/command_palette.rb +0 -118
  134. data/lib/charming/components/keyboard_handler.rb +0 -22
  135. data/lib/charming/components/list.rb +0 -105
  136. data/lib/charming/components/modal.rb +0 -48
  137. data/lib/charming/components/progressbar.rb +0 -55
  138. data/lib/charming/components/spinner.rb +0 -37
  139. data/lib/charming/components/table.rb +0 -115
  140. data/lib/charming/components/text_input.rb +0 -103
  141. data/lib/charming/components/viewport.rb +0 -191
  142. data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -86
  143. data/lib/charming/generators/app_generator/basic_templates.rb +0 -69
  144. data/lib/charming/generators/app_generator/component_templates.rb +0 -36
  145. data/lib/charming/generators/app_generator/controller_template.rb +0 -69
  146. data/lib/charming/generators/app_generator/layout_template.rb +0 -160
  147. data/lib/charming/generators/app_generator/model_templates.rb +0 -30
  148. data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -70
  149. data/lib/charming/generators/app_generator/view_template.rb +0 -90
  150. data/lib/charming/key_event.rb +0 -13
  151. data/lib/charming/mouse_event.rb +0 -40
  152. data/lib/charming/resize_event.rb +0 -7
  153. data/lib/charming/task.rb +0 -7
  154. data/lib/charming/task_event.rb +0 -17
  155. data/lib/charming/task_executor.rb +0 -62
  156. data/lib/charming/timer_event.rb +0 -7
  157. data/lib/charming/ui/border.rb +0 -33
  158. data/lib/charming/ui/style.rb +0 -244
  159. data/lib/charming/ui/theme.rb +0 -178
  160. data/lib/charming/ui/width.rb +0 -24
  161. data/lib/charming/ui.rb +0 -230
  162. data/lib/charming/view.rb +0 -116
  163. /data/lib/charming/{generators.rb → generators/error.rb} +0 -0
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Components
5
- class Modal < Component
6
- def initialize(content:, title: nil, help: nil, width: 52, style: nil, theme: nil)
7
- super(theme: theme)
8
- @content = content
9
- @title = title
10
- @help = help
11
- @width = width
12
- @style = style
13
- end
14
-
15
- def render
16
- box(column(*lines, gap: 1), style: modal_style)
17
- end
18
-
19
- private
20
-
21
- attr_reader :content, :title, :help, :width
22
-
23
- def lines
24
- [title_line, help_line, render_content].compact
25
- end
26
-
27
- def title_line
28
- text(title, style: theme.title.align(:center).width(title_width)) if title
29
- end
30
-
31
- def help_line
32
- text(help, style: theme.muted) if help
33
- end
34
-
35
- def render_content
36
- content.respond_to?(:render) ? render_component(content) : content.to_s
37
- end
38
-
39
- def modal_style
40
- @style || theme.modal.width(width)
41
- end
42
-
43
- def title_width
44
- [width - 8, 0].max
45
- end
46
- end
47
- end
48
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Components
5
- class Progressbar < Component
6
- attr_accessor :total, :current, :label, :complete, :incomplete, :bar_format
7
-
8
- def initialize(total:, complete: "=", incomplete: " ", bar_format: :classic, label: nil)
9
- super()
10
- @total = [total.to_i, 0].max
11
- @complete = complete.to_s
12
- @incomplete = incomplete.to_s
13
- @bar_format = bar_format.to_sym
14
- @label = label
15
- @current = 0
16
- end
17
-
18
- def tick(count = 1)
19
- update(@current + count)
20
- self
21
- end
22
-
23
- def update(value)
24
- @current = value.to_i.clamp(0, @total)
25
- self
26
- end
27
-
28
- def complete!
29
- @current = @total
30
- self
31
- end
32
-
33
- def render
34
- width = [@total, 1].max
35
- completed = completed_width(width)
36
- incomplete = width - completed
37
- incomplete -= 1 if @current.zero?
38
- bar = (@complete * completed) + (@incomplete * incomplete)
39
- result = "[" + bar + "]"
40
-
41
- return result unless @label
42
-
43
- "#{result} #{@label}"
44
- end
45
-
46
- private
47
-
48
- def completed_width(width)
49
- return 0 unless @total.positive?
50
-
51
- ((width * @current) / @total.to_f).round
52
- end
53
- end
54
- end
55
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Components
5
- class Spinner < Component
6
- DEFAULT_FRAMES = ["-", "\\", "|", "/"].freeze
7
-
8
- attr_reader :frames, :index, :label
9
-
10
- def initialize(frames: DEFAULT_FRAMES, index: 0, label: nil)
11
- super()
12
- raise ArgumentError, "frames cannot be empty" if frames.empty?
13
-
14
- @frames = frames
15
- @index = index
16
- @label = label
17
- end
18
-
19
- def tick
20
- @index = (index + 1) % frames.length
21
- self
22
- end
23
-
24
- def render
25
- return frame unless label
26
-
27
- "#{frame} #{label}"
28
- end
29
-
30
- private
31
-
32
- def frame
33
- frames.fetch(index % frames.length)
34
- end
35
- end
36
- end
37
- end
@@ -1,115 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tty-table"
4
-
5
- module Charming
6
- module Components
7
- class Table < Component
8
- include KeyboardHandler
9
-
10
- KEY_ACTIONS = {
11
- up: :move_up,
12
- down: :move_down,
13
- home: :move_home,
14
- end: :move_end
15
- }.freeze
16
-
17
- HEADER_HEIGHT = 2
18
-
19
- attr_reader :header, :rows, :selected_index
20
-
21
- def initialize(header:, rows: [], selected_index: 0)
22
- super()
23
- @header = Array(header).map(&:to_s)
24
- @rows = Array(rows)
25
- @selected_index = clamp_index(selected_index)
26
- end
27
-
28
- def handle_key(event)
29
- return nil if rows.empty?
30
-
31
- case Charming.key_of(event)
32
- when :enter then [:selected, selected_row]
33
- else super
34
- end
35
- end
36
-
37
- def handle_mouse(event)
38
- return nil if rows.empty?
39
- return nil unless event.respond_to?(:click?) && event.click?
40
-
41
- clicked = event.y - HEADER_HEIGHT
42
- return nil if clicked.negative? || clicked >= rows.length
43
-
44
- @selected_index = clicked
45
- :handled
46
- end
47
-
48
- def selected_row
49
- rows[selected_index]
50
- end
51
-
52
- def render
53
- return "(empty table)" if header.empty? && rows.empty?
54
-
55
- normalized = rows.map { |row| normalize_row(row) }
56
- lines = TTY::Table.new(header: header, rows: normalized)
57
- .render(:unicode)
58
- .lines(chomp: true)
59
-
60
- compact_layout(lines)
61
- end
62
-
63
- private
64
-
65
- def normalize_row(row)
66
- cells = case row
67
- when Hash then row.values
68
- when String then [row]
69
- else Array(row)
70
- end
71
- return cells if header.length <= 1 || cells.length <= header.length
72
-
73
- kept = cells.first(header.length - 1)
74
- merged = cells[(header.length - 1)..].join(" ")
75
- kept + [merged]
76
- end
77
-
78
- def compact_layout(lines)
79
- return lines.join("\n") if lines.length < 4
80
-
81
- top, header_line, _separator, *rest = lines
82
- body = rest.first(rows.length)
83
- bottom = rest[rows.length]
84
-
85
- highlighted = body.each_with_index.map do |line, index|
86
- (index == selected_index) ? "\e[7m#{line}\e[m" : line
87
- end
88
-
89
- [top, header_line, *highlighted, bottom].compact.join("\n")
90
- end
91
-
92
- def move_up
93
- @selected_index -= 1 if selected_index.positive?
94
- end
95
-
96
- def move_down
97
- @selected_index += 1 if selected_index < rows.length - 1
98
- end
99
-
100
- def move_home
101
- @selected_index = 0
102
- end
103
-
104
- def move_end
105
- @selected_index = rows.length - 1
106
- end
107
-
108
- def clamp_index(value)
109
- return 0 if rows.empty?
110
-
111
- value.to_i.clamp(0, rows.length - 1)
112
- end
113
- end
114
- end
115
- end
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Components
5
- class TextInput < Component
6
- include KeyboardHandler
7
-
8
- # Maps editing keys (left/right/home/end/backspace/delete) to the instance
9
- # methods they dispatch via KeyboardHandler. Each symbol key (e.g., :left)
10
- # maps to a method (e.g., :move_left) that adjusts cursor position or text content.
11
- KEY_ACTIONS = {
12
- left: :move_left,
13
- right: :move_right,
14
- home: :move_home,
15
- end: :move_end,
16
- backspace: :delete_before_cursor,
17
- delete: :delete_at_cursor
18
- }.freeze
19
-
20
- attr_reader :value, :cursor
21
-
22
- def initialize(value: "", placeholder: "", width: nil, cursor: nil)
23
- super()
24
- @value = value.dup
25
- @placeholder = placeholder
26
- @width = width
27
- @cursor = cursor || @value.length
28
- clamp_position
29
- end
30
-
31
- def handle_key(event)
32
- return :handled if character_event?(event) && insert(event.char)
33
-
34
- super
35
- end
36
-
37
- def render
38
- rendered = render_value
39
- @width ? style.width(@width).render(rendered) : rendered
40
- end
41
-
42
- private
43
-
44
- attr_reader :placeholder
45
-
46
- def character_event?(event)
47
- event.respond_to?(:char) && event.char && event.char.length == 1 && printable?(event.char)
48
- end
49
-
50
- def printable?(char)
51
- !char.match?(/[[:cntrl:]]/)
52
- end
53
-
54
- def insert(char)
55
- @value = value[0...cursor] + char + value[cursor..]
56
- @cursor += char.length
57
- end
58
-
59
- def move_left
60
- @cursor -= 1 if cursor.positive?
61
- end
62
-
63
- def move_right
64
- @cursor += 1 if cursor < value.length
65
- end
66
-
67
- def move_home
68
- @cursor = 0
69
- end
70
-
71
- def move_end
72
- @cursor = value.length
73
- end
74
-
75
- def delete_before_cursor
76
- return if cursor.zero?
77
-
78
- @value = value[0...(cursor - 1)] + value[cursor..]
79
- @cursor -= 1
80
- end
81
-
82
- def delete_at_cursor
83
- return if cursor >= value.length
84
-
85
- @value = value[0...cursor] + value[(cursor + 1)..]
86
- end
87
-
88
- def render_value
89
- return cursor_marker + placeholder if value.empty?
90
-
91
- value[0...cursor] + cursor_marker + value[cursor..]
92
- end
93
-
94
- def cursor_marker
95
- "|"
96
- end
97
-
98
- def clamp_position
99
- @cursor = cursor.clamp(0, value.length)
100
- end
101
- end
102
- end
103
- end
@@ -1,191 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "unicode/display_width"
4
-
5
- module Charming
6
- module Components
7
- class Viewport < Component
8
- include KeyboardHandler
9
-
10
- ANSI_PATTERN = /\e\[[0-9;]*m/
11
- KEY_ACTIONS = {
12
- up: :scroll_up,
13
- down: :scroll_down,
14
- page_up: :page_up,
15
- page_down: :page_down,
16
- home: :scroll_home,
17
- end: :scroll_end,
18
- left: :scroll_left,
19
- right: :scroll_right
20
- }.freeze
21
-
22
- attr_reader :offset, :column
23
-
24
- def initialize(content:, width: nil, height: nil, offset: 0, column: 0)
25
- super()
26
- @content = content
27
- @width = width
28
- @height = height
29
- @offset = offset
30
- @column = column
31
- clamp_position
32
- end
33
-
34
- def render
35
- visible_lines.map { |line| render_line(line) }.join("\n")
36
- end
37
-
38
- def handle_mouse(event)
39
- return nil unless height
40
-
41
- if event.scroll?
42
- scroll_delta = (event.button_name == :scroll_up) ? -1 : 1
43
- @offset += scroll_delta
44
- clamp_position
45
- return :handled
46
- end
47
-
48
- return nil unless event.click?
49
-
50
- clicked_row = event.y
51
- return nil if clicked_row < offset || clicked_row >= offset + viewport_height
52
-
53
- @offset = clicked_row
54
- clamp_position
55
- :handled
56
- end
57
-
58
- private
59
-
60
- attr_reader :content, :width, :height
61
-
62
- def scroll_up
63
- @offset -= 1
64
- clamp_position
65
- end
66
-
67
- def scroll_down
68
- @offset += 1
69
- clamp_position
70
- end
71
-
72
- def page_up
73
- @offset -= page_size
74
- clamp_position
75
- end
76
-
77
- def page_down
78
- @offset += page_size
79
- clamp_position
80
- end
81
-
82
- def scroll_home
83
- @offset = 0
84
- @column = 0
85
- end
86
-
87
- def scroll_end
88
- @offset = max_offset
89
- @column = max_column
90
- end
91
-
92
- def scroll_left
93
- @column -= 1
94
- clamp_position
95
- end
96
-
97
- def scroll_right
98
- @column += 1
99
- clamp_position
100
- end
101
-
102
- def clamp_position
103
- @offset = offset.clamp(0, max_offset)
104
- @column = column.clamp(0, max_column)
105
- end
106
-
107
- def visible_lines
108
- lines = content_lines.slice(offset, viewport_height) || []
109
- return lines unless height
110
-
111
- lines + Array.new([height - lines.length, 0].max, "")
112
- end
113
-
114
- def render_line(line)
115
- return line unless width
116
-
117
- pad_line(clip_line(line), width)
118
- end
119
-
120
- def clip_line(line)
121
- clipped = clip_tokens(line.to_s)
122
- needs_reset?(clipped) ? "#{clipped}\e[0m" : clipped
123
- end
124
-
125
- def clip_tokens(line)
126
- state = {cursor: 0, output: +""}
127
- line.scan(/#{ANSI_PATTERN}|./mo) do |token|
128
- ansi?(token) ? append_ansi(state, token) : append_character(state, token)
129
- end
130
- state.fetch(:output)
131
- end
132
-
133
- def append_ansi(state, token)
134
- state.fetch(:output) << token
135
- end
136
-
137
- def append_character(state, char)
138
- char_width = Unicode::DisplayWidth.of(char)
139
- cursor = state.fetch(:cursor)
140
- state.fetch(:output) << char if visible?(cursor, char_width)
141
- state[:cursor] = cursor + char_width
142
- end
143
-
144
- def visible?(cursor, char_width)
145
- cursor >= column && cursor + char_width <= column + width
146
- end
147
-
148
- def needs_reset?(value)
149
- value.match?(ANSI_PATTERN) && !value.end_with?("\e[0m")
150
- end
151
-
152
- def pad_line(line, target_width)
153
- line + (" " * [target_width - UI::Width.measure(line), 0].max)
154
- end
155
-
156
- def content_lines
157
- rendered_content.lines(chomp: true)
158
- end
159
-
160
- def rendered_content
161
- content.respond_to?(:render) ? content.render.to_s : content.to_s
162
- end
163
-
164
- def viewport_height
165
- height || content_lines.length
166
- end
167
-
168
- def page_size
169
- [viewport_height, 1].max
170
- end
171
-
172
- def max_offset
173
- [content_lines.length - viewport_height, 0].max
174
- end
175
-
176
- def max_column
177
- return 0 unless width
178
-
179
- [content_width - width, 0].max
180
- end
181
-
182
- def content_width
183
- content_lines.map { |line| UI::Width.measure(line) }.max || 0
184
- end
185
-
186
- def ansi?(token)
187
- token.match?(ANSI_PATTERN)
188
- end
189
- end
190
- end
191
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module AppSpecTemplates
7
- def spec_model
8
- %(# frozen_string_literal: true
9
-
10
- require "#{app_name.snake_name}"
11
-
12
- RSpec.describe #{app_name.class_name}::HomeModel 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("#{app_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}::HomeController 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}::HomeView do
55
- describe "#render" do
56
- it "renders the model title" do
57
- view = described_class.new(
58
- home: double(title: "#{app_name.class_name}")
59
- )
60
-
61
- expect(view.render).to include("#{app_name.class_name}")
62
- end
63
- end
64
- end
65
- )
66
- end
67
-
68
- def spec_component
69
- %(# frozen_string_literal: true
70
-
71
- require "#{app_name.snake_name}"
72
-
73
- RSpec.describe #{app_name.class_name}::AppFrameComponent do
74
- describe "#render" do
75
- it "returns a string" do
76
- component = described_class.new(title: "#{app_name.class_name}")
77
- expect(component.render).to be_a(String)
78
- end
79
- end
80
- end
81
- )
82
- end
83
- end
84
- end
85
- end
86
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module BasicTemplates
7
- def gemfile
8
- %(# frozen_string_literal: true
9
-
10
- source "https://rubygems.org"
11
-
12
- gemspec
13
- )
14
- end
15
-
16
- def rakefile
17
- %(# frozen_string_literal: true
18
-
19
- require "bundler/gem_tasks"
20
- )
21
- end
22
-
23
- def readme
24
- %(# #{name.class_name}
25
-
26
- A Charming terminal user interface.
27
-
28
- Run it with:
29
-
30
- ```sh
31
- bundle exec #{name.snake_name}
32
- ```
33
- )
34
- end
35
-
36
- def gemspec
37
- %(# frozen_string_literal: true
38
-
39
- require_relative "lib/#{name.snake_name}/version"
40
-
41
- Gem::Specification.new do |spec|
42
- #{gemspec_attributes}
43
- #{gemspec_dependencies}
44
- end
45
- )
46
- end
47
-
48
- def gemspec_attributes
49
- %( spec.name = "#{name.snake_name}"
50
- spec.version = #{name.class_name}::VERSION
51
- spec.summary = "A Charming terminal user interface."
52
- spec.authors = ["TODO: Your name"]
53
- spec.email = ["TODO: Your email"]
54
- spec.files = Dir.glob("{app,config,exe,lib}/**/*") + %w[README.md]
55
- spec.bindir = "exe"
56
- spec.executables = ["#{name.snake_name}"]
57
- spec.require_paths = ["lib"]
58
- spec.required_ruby_version = ">= 4.0.0"
59
- spec.metadata["rubygems_mfa_required"] = "true")
60
- end
61
-
62
- def gemspec_dependencies
63
- %(
64
- spec.add_dependency "charming")
65
- end
66
- end
67
- end
68
- end
69
- end