charming 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/charming/application.rb +22 -5
  3. data/lib/charming/cli.rb +3 -3
  4. data/lib/charming/controller/class_methods.rb +2 -2
  5. data/lib/charming/controller/command_palette.rb +2 -2
  6. data/lib/charming/controller/component_dispatching.rb +47 -3
  7. data/lib/charming/controller/focus.rb +123 -0
  8. data/lib/charming/controller/focus_management.rb +1 -1
  9. data/lib/charming/controller/rendering.rb +6 -17
  10. data/lib/charming/controller/session_state.rb +12 -1
  11. data/lib/charming/controller.rb +11 -2
  12. data/lib/charming/database/commands.rb +106 -0
  13. data/lib/charming/generators/component_generator.rb +1 -1
  14. data/lib/charming/generators/database_installer.rb +154 -0
  15. data/lib/charming/generators/model_generator.rb +2 -10
  16. data/lib/charming/generators/name.rb +1 -1
  17. data/lib/charming/generators/templates/app/application.template +1 -1
  18. data/lib/charming/generators/templates/app/layout.template +3 -6
  19. data/lib/charming/generators/templates/app/view.template +1 -1
  20. data/lib/charming/generators/templates/component/component.rb.template +1 -1
  21. data/lib/charming/generators/templates/screen/view.rb.template +1 -1
  22. data/lib/charming/generators/templates/view/view.rb.template +1 -1
  23. data/lib/charming/generators/view_generator.rb +1 -1
  24. data/lib/charming/internal/renderer/differential.rb +13 -5
  25. data/lib/charming/internal/terminal/tty_backend.rb +22 -2
  26. data/lib/charming/presentation/component.rb +3 -5
  27. data/lib/charming/presentation/components/activity_indicator.rb +173 -134
  28. data/lib/charming/presentation/components/command_palette.rb +94 -96
  29. data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
  30. data/lib/charming/presentation/components/empty_state.rb +47 -49
  31. data/lib/charming/presentation/components/form/builder.rb +52 -54
  32. data/lib/charming/presentation/components/form/confirm.rb +49 -51
  33. data/lib/charming/presentation/components/form/field.rb +94 -96
  34. data/lib/charming/presentation/components/form/input.rb +53 -55
  35. data/lib/charming/presentation/components/form/note.rb +27 -29
  36. data/lib/charming/presentation/components/form/select.rb +84 -86
  37. data/lib/charming/presentation/components/form/textarea.rb +67 -69
  38. data/lib/charming/presentation/components/form.rb +120 -122
  39. data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
  40. data/lib/charming/presentation/components/list.rb +123 -125
  41. data/lib/charming/presentation/components/markdown.rb +21 -23
  42. data/lib/charming/presentation/components/modal.rb +46 -48
  43. data/lib/charming/presentation/components/progressbar.rb +51 -53
  44. data/lib/charming/presentation/components/spinner.rb +40 -42
  45. data/lib/charming/presentation/components/table.rb +109 -111
  46. data/lib/charming/presentation/components/text_area.rb +219 -221
  47. data/lib/charming/presentation/components/text_input.rb +120 -122
  48. data/lib/charming/presentation/components/viewport.rb +218 -220
  49. data/lib/charming/presentation/layout/builder.rb +64 -66
  50. data/lib/charming/presentation/layout/overlay.rb +48 -50
  51. data/lib/charming/presentation/layout/pane.rb +129 -118
  52. data/lib/charming/presentation/layout/rect.rb +19 -16
  53. data/lib/charming/presentation/layout/screen_layout.rb +47 -42
  54. data/lib/charming/presentation/layout/split.rb +107 -102
  55. data/lib/charming/presentation/layout.rb +28 -30
  56. data/lib/charming/presentation/markdown/render_context.rb +30 -14
  57. data/lib/charming/presentation/markdown/renderer.rb +302 -79
  58. data/lib/charming/presentation/markdown/style_config.rb +215 -0
  59. data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -59
  60. data/lib/charming/presentation/markdown.rb +4 -6
  61. data/lib/charming/presentation/template_view.rb +22 -24
  62. data/lib/charming/presentation/templates/erb_handler.rb +4 -6
  63. data/lib/charming/presentation/templates.rb +47 -49
  64. data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
  65. data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
  66. data/lib/charming/presentation/ui/border.rb +24 -26
  67. data/lib/charming/presentation/ui/border_painter.rb +37 -39
  68. data/lib/charming/presentation/ui/canvas.rb +59 -61
  69. data/lib/charming/presentation/ui/style.rb +173 -175
  70. data/lib/charming/presentation/ui/theme.rb +133 -135
  71. data/lib/charming/presentation/ui/width.rb +12 -14
  72. data/lib/charming/presentation/ui.rb +69 -71
  73. data/lib/charming/presentation/view.rb +110 -105
  74. data/lib/charming/router.rb +3 -8
  75. data/lib/charming/runtime.rb +25 -10
  76. data/lib/charming/version.rb +1 -1
  77. data/lib/charming.rb +5 -4
  78. metadata +43 -9
  79. data/lib/charming/database_commands.rb +0 -103
  80. data/lib/charming/database_installer.rb +0 -152
  81. data/lib/charming/focus.rb +0 -121
  82. data/lib/charming/presentation/markdown/block_renderers.rb +0 -120
  83. data/lib/charming/presentation/markdown/inline_renderers.rb +0 -68
@@ -3,76 +3,75 @@
3
3
  require "rouge"
4
4
 
5
5
  module Charming
6
- module Presentation
7
- module Markdown
8
- # SyntaxHighlighter turns a code block string into ANSI-styled terminal text using
9
- # Rouge lexers. The theme provides markdown_code_* tokens for per-token styling;
10
- # when a token is undefined in the theme, the highlighter falls back to a sensible
11
- # base style (muted italic for comments, title for keywords, etc.).
12
- class SyntaxHighlighter
13
- # *theme* is the active Charming theme. Defaults to UI::Theme.default.
14
- def initialize(theme: UI::Theme.default)
15
- @theme = theme || UI::Theme.default
16
- end
6
+ module Markdown
7
+ # SyntaxHighlighter turns a code block string into ANSI-styled terminal text using
8
+ # Rouge lexers. The theme provides markdown_code_* tokens for per-token styling;
9
+ # when a token is undefined in the theme, the highlighter falls back to a sensible
10
+ # base style (muted italic for comments, title for keywords, etc.).
11
+ class SyntaxHighlighter
12
+ # *theme* is the active Charming theme. Defaults to UI::Theme.default.
13
+ def initialize(theme: UI::Theme.default, style: nil)
14
+ @theme = theme || UI::Theme.default
15
+ @style = style
16
+ end
17
17
 
18
- # Highlights *code* (using Rouge) for the given *language* (auto-detected when nil)
19
- # and returns a styled multi-line string. Each Rouge token is rendered with the
20
- # theme style matching its token type.
21
- def render(code, language: nil)
22
- lexer = lexer_for(language, code)
23
- lexer.lex(code.to_s).map do |token, value|
24
- style_for(token).render(value)
25
- end.join
26
- end
18
+ # Highlights *code* (using Rouge) for the given *language* (auto-detected when nil)
19
+ # and returns a styled multi-line string. Each Rouge token is rendered with the
20
+ # theme style matching its token type.
21
+ def render(code, language: nil)
22
+ lexer = lexer_for(language, code)
23
+ lexer.lex(code.to_s).map do |token, value|
24
+ style_for(token).render(value)
25
+ end.join
26
+ end
27
27
 
28
- private
28
+ private
29
29
 
30
- # The Charming theme used for token styling.
31
- attr_reader :theme
30
+ # The Charming theme used for token styling.
31
+ attr_reader :theme, :style
32
32
 
33
- # Picks a Rouge lexer for *language* and *code*, falling back to plain text.
34
- def lexer_for(language, code)
35
- Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
36
- end
33
+ # Picks a Rouge lexer for *language* and *code*, falling back to plain text.
34
+ def lexer_for(language, code)
35
+ Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
36
+ end
37
37
 
38
- # Returns the Charming style for a given Rouge *token*, mapping token qualifiers
39
- # to theme tokens and falling back to a sensible base style per category.
40
- def style_for(token)
41
- name = token_name(token)
38
+ # Returns the Charming style for a given Rouge *token*, mapping token qualifiers
39
+ # to theme tokens and falling back to a sensible base style per category.
40
+ def style_for(token)
41
+ name = token_name(token)
42
42
 
43
- case name
44
- when /Comment/
45
- theme_style(:markdown_code_comment, fallback: theme_style(:muted).italic)
46
- when /Keyword/
47
- theme_style(:markdown_code_keyword, fallback: theme_style(:title))
48
- when /String/
49
- theme_style(:markdown_code_string, fallback: theme_style(:warn))
50
- when /Number|Literal/
51
- theme_style(:markdown_code_literal, fallback: theme_style(:info))
52
- when /Name\.(Class|Constant|Function|Namespace)/
53
- theme_style(:markdown_code_constant, fallback: theme_style(:info))
54
- when /Error/
55
- theme_style(:markdown_code_error, fallback: theme_style(:warn).bold)
56
- else
57
- theme_style(:markdown_code, fallback: theme_style(:text))
58
- end
43
+ case name
44
+ when /Comment/
45
+ theme_style(:markdown_code_comment, fallback: theme_style(:muted).italic)
46
+ when /Keyword/
47
+ theme_style(:markdown_code_keyword, fallback: theme_style(:title))
48
+ when /String/
49
+ theme_style(:markdown_code_string, fallback: theme_style(:warn))
50
+ when /Number|Literal/
51
+ theme_style(:markdown_code_literal, fallback: theme_style(:info))
52
+ when /Name\.(Class|Constant|Function|Namespace)/
53
+ theme_style(:markdown_code_constant, fallback: theme_style(:info))
54
+ when /Error/
55
+ theme_style(:markdown_code_error, fallback: theme_style(:warn).bold)
56
+ else
57
+ theme_style(:markdown_code, fallback: theme_style(:text))
59
58
  end
59
+ end
60
60
 
61
- # Returns the qualified token name when the token object supports it, otherwise
62
- # the token's default `to_s`.
63
- def token_name(token)
64
- return token.qualname if token.respond_to?(:qualname)
61
+ # Returns the qualified token name when the token object supports it, otherwise
62
+ # the token's default `to_s`.
63
+ def token_name(token)
64
+ return token.qualname if token.respond_to?(:qualname)
65
65
 
66
- token.to_s
67
- end
66
+ token.to_s
67
+ end
68
68
 
69
- # Returns the theme's style for *name*, falling back to *fallback* (or a default
70
- # empty style) when the theme doesn't define it.
71
- def theme_style(name, fallback: nil)
72
- return theme.public_send(name) if theme.respond_to?(name)
69
+ # Returns the theme's style for *name*, falling back to *fallback* (or a default
70
+ # empty style) when the theme doesn't define it.
71
+ def theme_style(name, fallback: nil)
72
+ return theme.public_send(name) if theme.respond_to?(name)
73
73
 
74
- fallback || UI.style
75
- end
74
+ fallback || UI.style
76
75
  end
77
76
  end
78
77
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- # Markdown is the namespace for the Markdown rendering pipeline. Parsing is delegated to
6
- # Kramdown; per-block and per-inline element rendering is handled by `BlockRenderer`
7
- # and `InlineRenderer`; code blocks are highlighted by `SyntaxHighlighter` (Rouge-backed).
8
- module Markdown
9
- end
4
+ # Markdown is the namespace for the Markdown rendering pipeline. Parsing is delegated to
5
+ # Commonmarker; `Renderer` renders the AST, and code blocks are highlighted by
6
+ # `SyntaxHighlighter` (Rouge-backed).
7
+ module Markdown
10
8
  end
11
9
  end
@@ -1,34 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- # TemplateView wraps a resolved ERB template and exposes it as a renderable View. The
6
- # template is rendered with the view's helpers (`text`, `box`, `row`, `column`, `style`,
7
- # `theme`, etc.) and the view's assigns available as reader methods inside the template.
8
- class TemplateView < View
9
- def initialize(template:, namespace: nil, **assigns)
10
- super(**assigns)
11
- @template = template
12
- @namespace = namespace
13
- end
4
+ # TemplateView wraps a resolved ERB template and exposes it as a renderable View. The
5
+ # template is rendered with the view's helpers (`text`, `box`, `row`, `column`, `style`,
6
+ # `theme`, etc.) and the view's assigns available as reader methods inside the template.
7
+ class TemplateView < View
8
+ def initialize(template:, namespace: nil, **assigns)
9
+ super(**assigns)
10
+ @template = template
11
+ @namespace = namespace
12
+ end
14
13
 
15
- # Renders the wrapped template to a string, evaluated in the view's binding context.
16
- def render
17
- template.render(self).to_s
18
- end
14
+ # Renders the wrapped template to a string, evaluated in the view's binding context.
15
+ def render
16
+ template.render(self).to_s
17
+ end
19
18
 
20
- # Returns the binding used by ERB handlers to evaluate the template body. When *namespace*
21
- # is set, the binding is created by a proc generated in the namespace's context so the
22
- # template can resolve constants relative to the application.
23
- def template_binding
24
- return binding unless namespace
19
+ # Returns the binding used by ERB handlers to evaluate the template body. When *namespace*
20
+ # is set, the binding is created by a proc generated in the namespace's context so the
21
+ # template can resolve constants relative to the application.
22
+ def template_binding
23
+ return binding unless namespace
25
24
 
26
- namespace.module_eval("->(view) { view.instance_eval { binding } }", __FILE__, __LINE__).call(self)
27
- end
25
+ namespace.module_eval("->(view) { view.instance_eval { binding } }", __FILE__, __LINE__).call(self)
26
+ end
28
27
 
29
- private
28
+ private
30
29
 
31
- attr_reader :template, :namespace
32
- end
30
+ attr_reader :template, :namespace
33
31
  end
34
32
  end
@@ -3,12 +3,10 @@
3
3
  require "erb"
4
4
 
5
5
  module Charming
6
- module Presentation
7
- module Templates
8
- class ErbHandler
9
- def self.render(path, view)
10
- ERB.new(File.read(path), trim_mode: "-").result(view.template_binding)
11
- end
6
+ module Templates
7
+ class ErbHandler
8
+ def self.render(path, view)
9
+ ERB.new(File.read(path), trim_mode: "-").result(view.template_binding)
12
10
  end
13
11
  end
14
12
  end
@@ -1,67 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- # Templates resolves and renders view templates by name. Template handlers are registered
6
- # for file extensions (e.g., `.tui.erb`) and the resolver searches `app/views/<name><ext>`
7
- # under the application root, falling back through registered extensions when the first
8
- # match is not found.
9
- module Templates
10
- # A resolved template: an on-disk *path* paired with the *handler* responsible for rendering it.
11
- ResolvedTemplate = Data.define(:path, :handler) do
12
- # Renders the template against *view* by delegating to the registered handler.
13
- def render(view)
14
- handler.render(path, view)
15
- end
4
+ # Templates resolves and renders view templates by name. Template handlers are registered
5
+ # for file extensions (e.g., `.tui.erb`) and the resolver searches `app/views/<name><ext>`
6
+ # under the application root, falling back through registered extensions when the first
7
+ # match is not found.
8
+ module Templates
9
+ # A resolved template: an on-disk *path* paired with the *handler* responsible for rendering it.
10
+ ResolvedTemplate = Data.define(:path, :handler) do
11
+ # Renders the template against *view* by delegating to the registered handler.
12
+ def render(view)
13
+ handler.render(path, view)
16
14
  end
15
+ end
17
16
 
18
- # Raised when no template file matches the given name under the application root.
19
- MissingTemplateError = Class.new(Error)
20
-
21
- class << self
22
- # Registers a template *handler* for a file *extension* (e.g., ".tui.erb" => ErbHandler).
23
- # The handler responds to `.render(path, view)`.
24
- def register(extension, handler)
25
- handlers[extension] = handler
26
- end
17
+ # Raised when no template file matches the given name under the application root.
18
+ MissingTemplateError = Class.new(Error)
27
19
 
28
- # Resolves a template by *name* under `app/views` of *root* (defaults to the current
29
- # working directory). Raises MissingTemplateError when no matching file exists.
30
- def resolve(name, root: nil)
31
- views_root = File.join(root || Dir.pwd, "app", "views")
32
- searched_paths = candidate_paths(views_root, name.to_s)
20
+ class << self
21
+ # Registers a template *handler* for a file *extension* (e.g., ".tui.erb" => ErbHandler).
22
+ # The handler responds to `.render(path, view)`.
23
+ def register(extension, handler)
24
+ handlers[extension] = handler
25
+ end
33
26
 
34
- searched_paths.each do |path|
35
- next unless File.file?(path)
27
+ # Resolves a template by *name* under `app/views` of *root* (defaults to the current
28
+ # working directory). Raises MissingTemplateError when no matching file exists.
29
+ def resolve(name, root: nil)
30
+ views_root = File.join(root || Dir.pwd, "app", "views")
31
+ searched_paths = candidate_paths(views_root, name.to_s)
36
32
 
37
- return ResolvedTemplate.new(path: path, handler: handler_for(path))
38
- end
33
+ searched_paths.each do |path|
34
+ next unless File.file?(path)
39
35
 
40
- raise MissingTemplateError, "Missing template #{name.inspect}. Searched: #{searched_paths.join(", ")}"
36
+ return ResolvedTemplate.new(path: path, handler: handler_for(path))
41
37
  end
42
38
 
43
- # Hash of registered handlers keyed by extension. Populated by `register`.
44
- def handlers
45
- @handlers ||= {}
46
- end
39
+ raise MissingTemplateError, "Missing template #{name.inspect}. Searched: #{searched_paths.join(", ")}"
40
+ end
47
41
 
48
- private
42
+ # Hash of registered handlers keyed by extension. Populated by `register`.
43
+ def handlers
44
+ @handlers ||= {}
45
+ end
49
46
 
50
- # Returns candidate paths under *views_root* for *name*. When the bare path has a known
51
- # extension, returns it directly; otherwise returns the path with each registered extension
52
- # appended (in registration order).
53
- def candidate_paths(views_root, name)
54
- path = File.expand_path(name, views_root)
55
- return [path] if handler_for(path)
47
+ private
56
48
 
57
- handlers.keys.map { |extension| "#{path}#{extension}" }
58
- end
49
+ # Returns candidate paths under *views_root* for *name*. When the bare path has a known
50
+ # extension, returns it directly; otherwise returns the path with each registered extension
51
+ # appended (in registration order).
52
+ def candidate_paths(views_root, name)
53
+ path = File.expand_path(name, views_root)
54
+ return [path] if handler_for(path)
59
55
 
60
- # Looks up the handler whose registered extension matches the end of *path*. Returns nil
61
- # when no handler matches.
62
- def handler_for(path)
63
- handlers.find { |extension, _handler| path.end_with?(extension) }&.last
64
- end
56
+ handlers.keys.map { |extension| "#{path}#{extension}" }
57
+ end
58
+
59
+ # Looks up the handler whose registered extension matches the end of *path*. Returns nil
60
+ # when no handler matches.
61
+ def handler_for(path)
62
+ handlers.find { |extension, _handler| path.end_with?(extension) }&.last
65
63
  end
66
64
  end
67
65
  end
@@ -1,88 +1,86 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module UI
6
- class ANSICodes
7
- ATTRIBUTES = {
8
- bold: 1,
9
- faint: 2,
10
- italic: 3,
11
- underline: 4,
12
- reverse: 7,
13
- strikethrough: 9
14
- }.freeze
4
+ module UI
5
+ class ANSICodes
6
+ ATTRIBUTES = {
7
+ bold: 1,
8
+ faint: 2,
9
+ italic: 3,
10
+ underline: 4,
11
+ reverse: 7,
12
+ strikethrough: 9
13
+ }.freeze
15
14
 
16
- COLORS = {
17
- black: 30,
18
- red: 31,
19
- green: 32,
20
- yellow: 33,
21
- blue: 34,
22
- magenta: 35,
23
- cyan: 36,
24
- white: 37,
25
- bright_black: 90,
26
- bright_red: 91,
27
- bright_green: 92,
28
- bright_yellow: 93,
29
- bright_blue: 94,
30
- bright_magenta: 95,
31
- bright_cyan: 96,
32
- bright_white: 97
33
- }.freeze
15
+ COLORS = {
16
+ black: 30,
17
+ red: 31,
18
+ green: 32,
19
+ yellow: 33,
20
+ blue: 34,
21
+ magenta: 35,
22
+ cyan: 36,
23
+ white: 37,
24
+ bright_black: 90,
25
+ bright_red: 91,
26
+ bright_green: 92,
27
+ bright_yellow: 93,
28
+ bright_blue: 94,
29
+ bright_magenta: 95,
30
+ bright_cyan: 96,
31
+ bright_white: 97
32
+ }.freeze
34
33
 
35
- def initialize(attributes:, foreground:, background:)
36
- @attributes = attributes
37
- @foreground = foreground
38
- @background = background
39
- end
34
+ def initialize(attributes:, foreground:, background:)
35
+ @attributes = attributes
36
+ @foreground = foreground
37
+ @background = background
38
+ end
40
39
 
41
- def codes
42
- @codes ||= attribute_codes +
43
- color_codes(@foreground, foreground: true) +
44
- color_codes(@background, foreground: false)
45
- end
40
+ def codes
41
+ @codes ||= attribute_codes +
42
+ color_codes(@foreground, foreground: true) +
43
+ color_codes(@background, foreground: false)
44
+ end
46
45
 
47
- def apply(value)
48
- return value if codes.empty?
46
+ def apply(value)
47
+ return value if codes.empty?
49
48
 
50
- start = "\e[#{codes.join(";")}m"
51
- value.split("\n", -1).map { |line| "#{start}#{line.gsub("\e[0m", "\e[0m#{start}")}\e[0m" }.join("\n")
52
- end
49
+ start = "\e[#{codes.join(";")}m"
50
+ value.split("\n", -1).map { |line| "#{start}#{line.gsub("\e[0m", "\e[0m#{start}")}\e[0m" }.join("\n")
51
+ end
53
52
 
54
- private
53
+ private
55
54
 
56
- def attribute_codes
57
- @attributes.map { |attribute| ATTRIBUTES.fetch(attribute) }
58
- end
55
+ def attribute_codes
56
+ @attributes.map { |attribute| ATTRIBUTES.fetch(attribute) }
57
+ end
59
58
 
60
- def color_codes(color, foreground:)
61
- return [] unless color
62
- return indexed_color_code(color, foreground: foreground) if color.is_a?(Integer)
63
- return named_color_code(color, foreground: foreground) if COLORS.key?(color.to_sym)
64
- return truecolor_codes(color, foreground: foreground) if color.to_s.start_with?("#")
59
+ def color_codes(color, foreground:)
60
+ return [] unless color
61
+ return indexed_color_code(color, foreground: foreground) if color.is_a?(Integer)
62
+ return named_color_code(color, foreground: foreground) if COLORS.key?(color.to_sym)
63
+ return truecolor_codes(color, foreground: foreground) if color.to_s.start_with?("#")
65
64
 
66
- raise ArgumentError, "unknown color: #{color.inspect}"
67
- end
65
+ raise ArgumentError, "unknown color: #{color.inspect}"
66
+ end
68
67
 
69
- def named_color_code(color, foreground:)
70
- code = COLORS.fetch(color.to_sym)
71
- [foreground ? code : code + 10]
72
- end
68
+ def named_color_code(color, foreground:)
69
+ code = COLORS.fetch(color.to_sym)
70
+ [foreground ? code : code + 10]
71
+ end
73
72
 
74
- def indexed_color_code(color, foreground:)
75
- raise ArgumentError, "indexed color must be between 0 and 255" unless color.between?(0, 255)
73
+ def indexed_color_code(color, foreground:)
74
+ raise ArgumentError, "indexed color must be between 0 and 255" unless color.between?(0, 255)
76
75
 
77
- [foreground ? 38 : 48, 5, color]
78
- end
76
+ [foreground ? 38 : 48, 5, color]
77
+ end
79
78
 
80
- def truecolor_codes(color, foreground:)
81
- hex = color.to_s.delete_prefix("#")
82
- raise ArgumentError, "truecolor must be #rrggbb" unless hex.match?(/\A[0-9a-fA-F]{6}\z/)
79
+ def truecolor_codes(color, foreground:)
80
+ hex = color.to_s.delete_prefix("#")
81
+ raise ArgumentError, "truecolor must be #rrggbb" unless hex.match?(/\A[0-9a-fA-F]{6}\z/)
83
82
 
84
- [foreground ? 38 : 48, 2, hex[0..1].to_i(16), hex[2..3].to_i(16), hex[4..5].to_i(16)]
85
- end
83
+ [foreground ? 38 : 48, 2, hex[0..1].to_i(16), hex[2..3].to_i(16), hex[4..5].to_i(16)]
86
84
  end
87
85
  end
88
86
  end