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.
- checksums.yaml +4 -4
- data/lib/charming/application.rb +22 -5
- data/lib/charming/cli.rb +3 -3
- data/lib/charming/controller/class_methods.rb +2 -2
- data/lib/charming/controller/command_palette.rb +2 -2
- data/lib/charming/controller/component_dispatching.rb +47 -3
- data/lib/charming/controller/focus.rb +123 -0
- data/lib/charming/controller/focus_management.rb +1 -1
- data/lib/charming/controller/rendering.rb +6 -17
- data/lib/charming/controller/session_state.rb +12 -1
- data/lib/charming/controller.rb +11 -2
- data/lib/charming/database/commands.rb +106 -0
- data/lib/charming/generators/component_generator.rb +1 -1
- data/lib/charming/generators/database_installer.rb +154 -0
- data/lib/charming/generators/model_generator.rb +2 -10
- data/lib/charming/generators/name.rb +1 -1
- data/lib/charming/generators/templates/app/application.template +1 -1
- data/lib/charming/generators/templates/app/layout.template +3 -6
- data/lib/charming/generators/templates/app/view.template +1 -1
- data/lib/charming/generators/templates/component/component.rb.template +1 -1
- data/lib/charming/generators/templates/screen/view.rb.template +1 -1
- data/lib/charming/generators/templates/view/view.rb.template +1 -1
- data/lib/charming/generators/view_generator.rb +1 -1
- data/lib/charming/internal/renderer/differential.rb +13 -5
- data/lib/charming/internal/terminal/tty_backend.rb +22 -2
- data/lib/charming/presentation/component.rb +3 -5
- data/lib/charming/presentation/components/activity_indicator.rb +173 -134
- data/lib/charming/presentation/components/command_palette.rb +94 -96
- data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
- data/lib/charming/presentation/components/empty_state.rb +47 -49
- data/lib/charming/presentation/components/form/builder.rb +52 -54
- data/lib/charming/presentation/components/form/confirm.rb +49 -51
- data/lib/charming/presentation/components/form/field.rb +94 -96
- data/lib/charming/presentation/components/form/input.rb +53 -55
- data/lib/charming/presentation/components/form/note.rb +27 -29
- data/lib/charming/presentation/components/form/select.rb +84 -86
- data/lib/charming/presentation/components/form/textarea.rb +67 -69
- data/lib/charming/presentation/components/form.rb +120 -122
- data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
- data/lib/charming/presentation/components/list.rb +123 -125
- data/lib/charming/presentation/components/markdown.rb +21 -23
- data/lib/charming/presentation/components/modal.rb +46 -48
- data/lib/charming/presentation/components/progressbar.rb +51 -53
- data/lib/charming/presentation/components/spinner.rb +40 -42
- data/lib/charming/presentation/components/table.rb +109 -111
- data/lib/charming/presentation/components/text_area.rb +219 -221
- data/lib/charming/presentation/components/text_input.rb +120 -122
- data/lib/charming/presentation/components/viewport.rb +218 -220
- data/lib/charming/presentation/layout/builder.rb +64 -66
- data/lib/charming/presentation/layout/overlay.rb +48 -50
- data/lib/charming/presentation/layout/pane.rb +129 -118
- data/lib/charming/presentation/layout/rect.rb +19 -16
- data/lib/charming/presentation/layout/screen_layout.rb +47 -42
- data/lib/charming/presentation/layout/split.rb +107 -102
- data/lib/charming/presentation/layout.rb +28 -30
- data/lib/charming/presentation/markdown/render_context.rb +30 -14
- data/lib/charming/presentation/markdown/renderer.rb +302 -79
- data/lib/charming/presentation/markdown/style_config.rb +215 -0
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -59
- data/lib/charming/presentation/markdown.rb +4 -6
- data/lib/charming/presentation/template_view.rb +22 -24
- data/lib/charming/presentation/templates/erb_handler.rb +4 -6
- data/lib/charming/presentation/templates.rb +47 -49
- data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
- data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
- data/lib/charming/presentation/ui/border.rb +24 -26
- data/lib/charming/presentation/ui/border_painter.rb +37 -39
- data/lib/charming/presentation/ui/canvas.rb +59 -61
- data/lib/charming/presentation/ui/style.rb +173 -175
- data/lib/charming/presentation/ui/theme.rb +133 -135
- data/lib/charming/presentation/ui/width.rb +12 -14
- data/lib/charming/presentation/ui.rb +69 -71
- data/lib/charming/presentation/view.rb +110 -105
- data/lib/charming/router.rb +3 -8
- data/lib/charming/runtime.rb +25 -10
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +5 -4
- metadata +43 -9
- data/lib/charming/database_commands.rb +0 -103
- data/lib/charming/database_installer.rb +0 -152
- data/lib/charming/focus.rb +0 -121
- data/lib/charming/presentation/markdown/block_renderers.rb +0 -120
- 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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
28
|
+
private
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
# The Charming theme used for token styling.
|
|
31
|
+
attr_reader :theme, :style
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
66
|
+
token.to_s
|
|
67
|
+
end
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
25
|
+
namespace.module_eval("->(view) { view.instance_eval { binding } }", __FILE__, __LINE__).call(self)
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
private
|
|
30
29
|
|
|
31
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
33
|
+
searched_paths.each do |path|
|
|
34
|
+
next unless File.file?(path)
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
return ResolvedTemplate.new(path: path, handler: handler_for(path))
|
|
41
37
|
end
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@handlers ||= {}
|
|
46
|
-
end
|
|
39
|
+
raise MissingTemplateError, "Missing template #{name.inspect}. Searched: #{searched_paths.join(", ")}"
|
|
40
|
+
end
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
# Hash of registered handlers keyed by extension. Populated by `register`.
|
|
43
|
+
def handlers
|
|
44
|
+
@handlers ||= {}
|
|
45
|
+
end
|
|
49
46
|
|
|
50
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
def initialize(attributes:, foreground:, background:)
|
|
35
|
+
@attributes = attributes
|
|
36
|
+
@foreground = foreground
|
|
37
|
+
@background = background
|
|
38
|
+
end
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
def codes
|
|
41
|
+
@codes ||= attribute_codes +
|
|
42
|
+
color_codes(@foreground, foreground: true) +
|
|
43
|
+
color_codes(@background, foreground: false)
|
|
44
|
+
end
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
def apply(value)
|
|
47
|
+
return value if codes.empty?
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
53
|
+
private
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
def attribute_codes
|
|
56
|
+
@attributes.map { |attribute| ATTRIBUTES.fetch(attribute) }
|
|
57
|
+
end
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
65
|
+
raise ArgumentError, "unknown color: #{color.inspect}"
|
|
66
|
+
end
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
def named_color_code(color, foreground:)
|
|
69
|
+
code = COLORS.fetch(color.to_sym)
|
|
70
|
+
[foreground ? code : code + 10]
|
|
71
|
+
end
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
76
|
+
[foreground ? 38 : 48, 5, color]
|
|
77
|
+
end
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|