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
@@ -1,55 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Components
6
- # EmptyState is a placeholder component for screens with no content. Renders one of three
7
- # states: a default "nothing to show" message, a "loading…" message, or an error message
8
- # with optional help text.
9
- class EmptyState < Component
10
- # *message* is shown in the default state. *loading* switches to the loading message
11
- # (overrides *message*). *loading_message* is the string rendered in the loading state.
12
- # *error* and *error_message* switch to the error state (the string form takes precedence).
13
- # *help* is an optional muted line shown below the error message.
14
- def initialize(message: "Nothing to show.", loading: false, loading_message: "Loading...", error: nil, error_message: nil, help: nil, theme: nil)
15
- super(theme: theme)
16
- @message = message
17
- @loading = loading
18
- @loading_message = loading_message
19
- @error = error
20
- @error_message = error_message
21
- @help = help
22
- end
23
-
24
- # Renders the appropriate state as styled text: loading → loading message, error →
25
- # error message + help, otherwise the default message.
26
- def render
27
- return loading_state if @loading
28
- return error_state if error?
29
-
30
- text @message, style: theme.muted
31
- end
32
-
33
- private
34
-
35
- # Renders the loading state as a muted line.
36
- def loading_state
37
- text @loading_message, style: theme.muted
38
- end
39
-
40
- # Renders the error state: the error message styled with the theme's warn style,
41
- # optionally followed by a muted help line.
42
- def error_state
43
- lines = [text(@error_message || @error.to_s, style: theme.warn)]
44
- lines << text(@help, style: theme.muted) if @help.to_s.strip != ""
45
-
46
- column(*lines)
47
- end
48
-
49
- # True when either the *error* or *error_message* string is non-blank.
50
- def error?
51
- @error.to_s.strip != "" || @error_message.to_s.strip != ""
52
- end
4
+ module Components
5
+ # EmptyState is a placeholder component for screens with no content. Renders one of three
6
+ # states: a default "nothing to show" message, a "loading…" message, or an error message
7
+ # with optional help text.
8
+ class EmptyState < Component
9
+ # *message* is shown in the default state. *loading* switches to the loading message
10
+ # (overrides *message*). *loading_message* is the string rendered in the loading state.
11
+ # *error* and *error_message* switch to the error state (the string form takes precedence).
12
+ # *help* is an optional muted line shown below the error message.
13
+ def initialize(message: "Nothing to show.", loading: false, loading_message: "Loading...", error: nil, error_message: nil, help: nil, theme: nil)
14
+ super(theme: theme)
15
+ @message = message
16
+ @loading = loading
17
+ @loading_message = loading_message
18
+ @error = error
19
+ @error_message = error_message
20
+ @help = help
21
+ end
22
+
23
+ # Renders the appropriate state as styled text: loading → loading message, error →
24
+ # error message + help, otherwise the default message.
25
+ def render
26
+ return loading_state if @loading
27
+ return error_state if error?
28
+
29
+ text @message, style: theme.muted
30
+ end
31
+
32
+ private
33
+
34
+ # Renders the loading state as a muted line.
35
+ def loading_state
36
+ text @loading_message, style: theme.muted
37
+ end
38
+
39
+ # Renders the error state: the error message styled with the theme's warn style,
40
+ # optionally followed by a muted help line.
41
+ def error_state
42
+ lines = [text(@error_message || @error.to_s, style: theme.warn)]
43
+ lines << text(@help, style: theme.muted) if @help.to_s.strip != ""
44
+
45
+ column(*lines)
46
+ end
47
+
48
+ # True when either the *error* or *error_message* string is non-blank.
49
+ def error?
50
+ @error.to_s.strip != "" || @error_message.to_s.strip != ""
53
51
  end
54
52
  end
55
53
  end
@@ -1,60 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Components
6
- class Form
7
- # Builder collects form field declarations inside a `form(:name) { ... }` block and
8
- # assembles them into a Form component when `build` is called. Each declaration method
9
- # appends a Field subclass instance to the builder's *fields* list.
10
- class Builder
11
- # The accumulated field list and the theme applied to each declared field.
12
- attr_reader :fields, :theme
13
-
14
- # Initializes an empty builder. *theme* is forwarded to every declared field unless
15
- # the field declaration explicitly overrides it.
16
- def initialize(theme: nil)
17
- @theme = theme
18
- @fields = []
19
- end
20
-
21
- # Appends a single-line Input field. *options* are passed through to Input.
22
- def input(name, **options)
23
- fields << Input.new(name, **field_options(options))
24
- end
25
-
26
- # Appends a multi-line Textarea field.
27
- def textarea(name, **options)
28
- fields << Textarea.new(name, **field_options(options))
29
- end
30
-
31
- # Appends a Select field with the given *options* array.
32
- def select(name, **options)
33
- fields << Select.new(name, **field_options(options))
34
- end
35
-
36
- # Appends a Confirm (boolean) field.
37
- def confirm(name, **options)
38
- fields << Confirm.new(name, **field_options(options))
39
- end
40
-
41
- # Appends a static Note (non-focusable).
42
- def note(text, **options)
43
- fields << Note.new(text, **field_options(options))
44
- end
45
-
46
- # Assembles the collected fields into a Form component, bound to *state* and using
47
- # the *theme* argument (falling back to the builder's theme).
48
- def build(state:, theme: nil)
49
- Components::Form.new(fields: fields, state: state, theme: theme || self.theme)
50
- end
51
-
52
- private
53
-
54
- # Merges the builder's theme into the per-field *options* so each field receives it.
55
- def field_options(options)
56
- {theme: theme}.merge(options)
57
- end
4
+ module Components
5
+ class Form
6
+ # Builder collects form field declarations inside a `form(:name) { ... }` block and
7
+ # assembles them into a Form component when `build` is called. Each declaration method
8
+ # appends a Field subclass instance to the builder's *fields* list.
9
+ class Builder
10
+ # The accumulated field list and the theme applied to each declared field.
11
+ attr_reader :fields, :theme
12
+
13
+ # Initializes an empty builder. *theme* is forwarded to every declared field unless
14
+ # the field declaration explicitly overrides it.
15
+ def initialize(theme: nil)
16
+ @theme = theme
17
+ @fields = []
18
+ end
19
+
20
+ # Appends a single-line Input field. *options* are passed through to Input.
21
+ def input(name, **options)
22
+ fields << Input.new(name, **field_options(options))
23
+ end
24
+
25
+ # Appends a multi-line Textarea field.
26
+ def textarea(name, **options)
27
+ fields << Textarea.new(name, **field_options(options))
28
+ end
29
+
30
+ # Appends a Select field with the given *options* array.
31
+ def select(name, **options)
32
+ fields << Select.new(name, **field_options(options))
33
+ end
34
+
35
+ # Appends a Confirm (boolean) field.
36
+ def confirm(name, **options)
37
+ fields << Confirm.new(name, **field_options(options))
38
+ end
39
+
40
+ # Appends a static Note (non-focusable).
41
+ def note(text, **options)
42
+ fields << Note.new(text, **field_options(options))
43
+ end
44
+
45
+ # Assembles the collected fields into a Form component, bound to *state* and using
46
+ # the *theme* argument (falling back to the builder's theme).
47
+ def build(state:, theme: nil)
48
+ Components::Form.new(fields: fields, state: state, theme: theme || self.theme)
49
+ end
50
+
51
+ private
52
+
53
+ # Merges the builder's theme into the per-field *options* so each field receives it.
54
+ def field_options(options)
55
+ {theme: theme}.merge(options)
58
56
  end
59
57
  end
60
58
  end
@@ -1,67 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Components
6
- class Form
7
- # Confirm is a boolean Form field that renders a checkbox-style control. Space toggles
8
- # the value; y/Right sets it to true; n/Left sets it to false. Required confirms must
9
- # be accepted (value == true) to pass validation.
10
- class Confirm < Field
11
- # *value* is the initial boolean state (default: false). All other options are
12
- # forwarded to Field.
13
- def initialize(name, value: false, **options)
14
- super(name, **options)
15
- @initial_value = value
16
- end
4
+ module Components
5
+ class Form
6
+ # Confirm is a boolean Form field that renders a checkbox-style control. Space toggles
7
+ # the value; y/Right sets it to true; n/Left sets it to false. Required confirms must
8
+ # be accepted (value == true) to pass validation.
9
+ class Confirm < Field
10
+ # *value* is the initial boolean state (default: false). All other options are
11
+ # forwarded to Field.
12
+ def initialize(name, value: false, **options)
13
+ super(name, **options)
14
+ @initial_value = value
15
+ end
17
16
 
18
- # Handles the standard confirm keys: space toggles, y/right sets to true, n/left
19
- # sets to false, and a space character (when the event exposes `char`) also toggles.
20
- def handle_key(event)
21
- case Charming.key_of(event)
22
- when :space
23
- toggle
24
- when :y, :right
25
- state[:values][name] = true
26
- when :n, :left
27
- state[:values][name] = false
28
- else
29
- return nil unless event.respond_to?(:char) && event.char == " "
17
+ # Handles the standard confirm keys: space toggles, y/right sets to true, n/left
18
+ # sets to false, and a space character (when the event exposes `char`) also toggles.
19
+ def handle_key(event)
20
+ case Charming.key_of(event)
21
+ when :space
22
+ toggle
23
+ when :y, :right
24
+ state[:values][name] = true
25
+ when :n, :left
26
+ state[:values][name] = false
27
+ else
28
+ return nil unless event.respond_to?(:char) && event.char == " "
30
29
 
31
- toggle
32
- end
33
- :handled
30
+ toggle
34
31
  end
32
+ :handled
33
+ end
35
34
 
36
- # Returns ["must be accepted"] when required and the value is not true, otherwise
37
- # the result of the base Field validation.
38
- def validate
39
- return ["must be accepted"] if required? && value != true
35
+ # Returns ["must be accepted"] when required and the value is not true, otherwise
36
+ # the result of the base Field validation.
37
+ def validate
38
+ return ["must be accepted"] if required? && value != true
40
39
 
41
- super
42
- end
40
+ super
41
+ end
43
42
 
44
- private
43
+ private
45
44
 
46
- # The default value for a freshly-bound field is the *value* passed at construction.
47
- def default_value
48
- @initial_value
49
- end
45
+ # The default value for a freshly-bound field is the *value* passed at construction.
46
+ def default_value
47
+ @initial_value
48
+ end
50
49
 
51
- # Renders "[x] Label" or "[ ] Label" depending on the current value.
52
- def render_control
53
- "#{checked_marker} #{label}"
54
- end
50
+ # Renders "[x] Label" or "[ ] Label" depending on the current value.
51
+ def render_control
52
+ "#{checked_marker} #{label}"
53
+ end
55
54
 
56
- # Returns the checkbox marker string.
57
- def checked_marker
58
- value ? "[x]" : "[ ]"
59
- end
55
+ # Returns the checkbox marker string.
56
+ def checked_marker
57
+ value ? "[x]" : "[ ]"
58
+ end
60
59
 
61
- # Flips the current value (true ↔ false).
62
- def toggle
63
- state[:values][name] = !value
64
- end
60
+ # Flips the current value (true ↔ false).
61
+ def toggle
62
+ state[:values][name] = !value
65
63
  end
66
64
  end
67
65
  end
@@ -1,119 +1,117 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Components
6
- class Form
7
- # Field is the abstract base class for Form fields. Subclasses define `default_value`
8
- # and `render_control` (or override `render`); the base class supplies validation,
9
- # help-line rendering, error-line rendering, and value lookup against the form state.
10
- class Field < Component
11
- # The field's name symbol, human-readable label, optional help text, and bound state hash.
12
- attr_reader :name, :label, :help, :state
13
-
14
- # *name* is the value key (a Symbol). *label* defaults to a humanized version of *name*.
15
- # *required* enables a "is required" validator. *validate* is an optional callable
16
- # (returning nil/true ok, false "is invalid", Array messages, anything else → stringified).
17
- # *help* is an optional muted helper line rendered under the field.
18
- def initialize(name, label: nil, required: false, validate: nil, help: nil, theme: nil)
19
- super(theme: theme)
20
- @name = name.to_sym
21
- @label = label || humanize(name)
22
- @required = required
23
- @validator = validate
24
- @help = help
25
- end
4
+ module Components
5
+ class Form
6
+ # Field is the abstract base class for Form fields. Subclasses define `default_value`
7
+ # and `render_control` (or override `render`); the base class supplies validation,
8
+ # help-line rendering, error-line rendering, and value lookup against the form state.
9
+ class Field < Component
10
+ # The field's name symbol, human-readable label, optional help text, and bound state hash.
11
+ attr_reader :name, :label, :help, :state
12
+
13
+ # *name* is the value key (a Symbol). *label* defaults to a humanized version of *name*.
14
+ # *required* enables a "is required" validator. *validate* is an optional callable
15
+ # (returning nil/true ok, false → "is invalid", Array messages, anything else → stringified).
16
+ # *help* is an optional muted helper line rendered under the field.
17
+ def initialize(name, label: nil, required: false, validate: nil, help: nil, theme: nil)
18
+ super(theme: theme)
19
+ @name = name.to_sym
20
+ @label = label || humanize(name)
21
+ @required = required
22
+ @validator = validate
23
+ @help = help
24
+ end
26
25
 
27
- # Binds the field to the form's *state* hash, ensuring the field's per-field state
28
- # and a default value are present.
29
- def bind(state)
30
- @state = state
31
- state[:fields][name] ||= {}
32
- state[:values][name] = default_value unless state[:values].key?(name)
33
- end
26
+ # Binds the field to the form's *state* hash, ensuring the field's per-field state
27
+ # and a default value are present.
28
+ def bind(state)
29
+ @state = state
30
+ state[:fields][name] ||= {}
31
+ state[:values][name] = default_value unless state[:values].key?(name)
32
+ end
34
33
 
35
- # Subclasses that participate in Tab/Enter navigation return true. Default is true.
36
- def focusable?
37
- true
38
- end
34
+ # Subclasses that participate in Tab/Enter navigation return true. Default is true.
35
+ def focusable?
36
+ true
37
+ end
39
38
 
40
- # Default key handler returns nil (no key handling). Subclasses override.
41
- def handle_key(_event)
42
- nil
43
- end
39
+ # Default key handler returns nil (no key handling). Subclasses override.
40
+ def handle_key(_event)
41
+ nil
42
+ end
44
43
 
45
- # Renders the field as a control line prefixed with ">" (active) or " " (inactive),
46
- # optionally followed by the help line and any error lines.
47
- def render(active: false)
48
- line = "#{active ? ">" : " "} #{render_control}"
49
- line = theme.selected.render(line) if active
50
- [line, help_line, *error_lines].compact.join("\n")
51
- end
44
+ # Renders the field as a control line prefixed with ">" (active) or " " (inactive),
45
+ # optionally followed by the help line and any error lines.
46
+ def render(active: false)
47
+ line = "#{active ? ">" : " "} #{render_control}"
48
+ line = theme.selected.render(line) if active
49
+ [line, help_line, *error_lines].compact.join("\n")
50
+ end
52
51
 
53
- # Returns an array of validation error messages. Includes "is required" when
54
- # the field is required and blank, plus any messages produced by the user validator.
55
- def validate
56
- messages = []
57
- messages << "is required" if required? && blank?(value)
58
- messages.concat(validator_messages) if @validator
59
- messages
60
- end
52
+ # Returns an array of validation error messages. Includes "is required" when
53
+ # the field is required and blank, plus any messages produced by the user validator.
54
+ def validate
55
+ messages = []
56
+ messages << "is required" if required? && blank?(value)
57
+ messages.concat(validator_messages) if @validator
58
+ messages
59
+ end
61
60
 
62
- # The current value of this field from the bound state.
63
- def value
64
- state[:values][name]
65
- end
61
+ # The current value of this field from the bound state.
62
+ def value
63
+ state[:values][name]
64
+ end
66
65
 
67
- private
66
+ private
68
67
 
69
- # The default value assigned to a freshly-bound field. Subclasses override.
70
- def default_value
71
- nil
72
- end
68
+ # The default value assigned to a freshly-bound field. Subclasses override.
69
+ def default_value
70
+ nil
71
+ end
73
72
 
74
- # Renders the control portion (label + value). Default: "Label: <value>".
75
- def render_control
76
- "#{label}: #{value}"
77
- end
73
+ # Renders the control portion (label + value). Default: "Label: <value>".
74
+ def render_control
75
+ "#{label}: #{value}"
76
+ end
78
77
 
79
- # True when the field was declared with `required: true`.
80
- def required?
81
- @required
82
- end
78
+ # True when the field was declared with `required: true`.
79
+ def required?
80
+ @required
81
+ end
83
82
 
84
- # True when *value* is nil, an empty string, or responds to `empty?` with true.
85
- def blank?(value)
86
- return true if value.nil?
87
- return value.strip.empty? if value.is_a?(String)
83
+ # True when *value* is nil, an empty string, or responds to `empty?` with true.
84
+ def blank?(value)
85
+ return true if value.nil?
86
+ return value.strip.empty? if value.is_a?(String)
88
87
 
89
- value.respond_to?(:empty?) && value.empty?
90
- end
88
+ value.respond_to?(:empty?) && value.empty?
89
+ end
91
90
 
92
- # Normalizes the user validator's return value into an array of error message strings.
93
- def validator_messages
94
- result = @validator.call(value)
95
- case result
96
- when nil, true then []
97
- when false then ["is invalid"]
98
- when Array then result
99
- else [result.to_s]
100
- end
91
+ # Normalizes the user validator's return value into an array of error message strings.
92
+ def validator_messages
93
+ result = @validator.call(value)
94
+ case result
95
+ when nil, true then []
96
+ when false then ["is invalid"]
97
+ when Array then result
98
+ else [result.to_s]
101
99
  end
100
+ end
102
101
 
103
- # The muted help line (with two-space indent) when help text was given.
104
- def help_line
105
- " #{theme.muted.render(help)}" if help
106
- end
102
+ # The muted help line (with two-space indent) when help text was given.
103
+ def help_line
104
+ " #{theme.muted.render(help)}" if help
105
+ end
107
106
 
108
- # The list of error lines (with two-space indent) for any errors stored against this field.
109
- def error_lines
110
- Array(state[:errors][name]).map { |message| " #{theme.warn.render(message)}" }
111
- end
107
+ # The list of error lines (with two-space indent) for any errors stored against this field.
108
+ def error_lines
109
+ Array(state[:errors][name]).map { |message| " #{theme.warn.render(message)}" }
110
+ end
112
111
 
113
- # Converts a snake_case symbol/string to a humanized "Capitalized" string.
114
- def humanize(value)
115
- value.to_s.tr("_", " ").capitalize
116
- end
112
+ # Converts a snake_case symbol/string to a humanized "Capitalized" string.
113
+ def humanize(value)
114
+ ActiveSupport::Inflector.humanize(value)
117
115
  end
118
116
  end
119
117
  end