Class: Charming::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/charming/application.rb

Overview

Application is a lightweight, Rails-inspired application base for building terminal-based apps. It provides routing (via a DSL), session storage, and task execution for managing async operations.

Constant Summary collapse

LOGGER_READER =
Object.new.freeze
THEME_READER =
Object.new.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeApplication

Initializes the session hash for per-request state storage, restoring a previously persisted session when ‘persist_session` is configured.



134
135
136
137
# File 'lib/charming/application.rb', line 134

def initialize
  @logger = self.class.logger
  @session = load_session
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



129
130
131
# File 'lib/charming/application.rb', line 129

def logger
  @logger
end

#sessionObject (readonly)

Returns the value of attribute session.



130
131
132
# File 'lib/charming/application.rb', line 130

def session
  @session
end

#task_executorObject

Returns the value of attribute task_executor.



129
130
131
# File 'lib/charming/application.rb', line 129

def task_executor
  @task_executor
end

Class Method Details

.default_theme(name = THEME_READER) ⇒ Object

Returns the default theme name, or sets it when name is given. When unset, falls back to the first registered theme. Used by ‘theme_for` when no name is provided.



79
80
81
82
83
# File 'lib/charming/application.rb', line 79

def default_theme(name = THEME_READER)
  return @default_theme || themes.keys.first if name == THEME_READER

  @default_theme = name.to_sym
end

.logger(value = LOGGER_READER) ⇒ Object

Returns or sets the app logger. Defaults to a null-device logger so app and framework code can safely call logging methods without writing into the terminal UI.



31
32
33
34
35
# File 'lib/charming/application.rb', line 31

def logger(value = LOGGER_READER)
  return configured_logger if value == LOGGER_READER

  @logger = value
end

.namespaceObject

Derives the module namespace from the class name — e.g., Admin::HomeController yields “Admin”. Mirrors Rails’ engine-style namespacing.



25
26
27
# File 'lib/charming/application.rb', line 25

def namespace
  ActiveSupport::Inflector.deconstantize(name.to_s)
end

.persist_session(to:) ⇒ Object

Opts into session persistence: the session hash is serialized as JSON to to when the app quits and reloaded on boot. Only JSON-safe values survive the round-trip (hash keys come back as symbols); non-serializable entries (state objects, procs) are skipped with a warning in the log.



98
99
100
# File 'lib/charming/application.rb', line 98

def persist_session(to:)
  @session_path = to
end

.root(path = THEME_READER) ⇒ Object

Returns the app’s filesystem root, used to resolve relative theme and template paths. Pass path to set it; without arguments it returns the current value (or nil if unset).



39
40
41
42
43
# File 'lib/charming/application.rb', line 39

def root(path = THEME_READER)
  return @root if path == THEME_READER

  @root = File.expand_path(path)
end

.routes(&block) ⇒ Object

Registers or returns the app’s Router. Accepts an optional block to define routes via DSL (screen, root). Lazily initializes a new Router per namespace.



17
18
19
20
21
# File 'lib/charming/application.rb', line 17

def routes(&block)
  @routes ||= Router.new(namespace: namespace)
  @routes.draw(&block) if block
  @routes
end

.session_pathObject

The configured session file path, walking the superclass chain. Nil when persistence is not enabled.



104
105
106
107
108
109
# File 'lib/charming/application.rb', line 104

def session_path
  return @session_path if instance_variable_defined?(:@session_path)
  return superclass.session_path if superclass.respond_to?(:session_path)

  nil
end

.theme(name, from: nil, built_in: nil, extends: nil, overrides: nil) ⇒ Object

Registers a named theme. Provide one of:

  • from: — path to a JSON theme file relative to the app root

  • built_in: — name of a bundled theme (“phosphor”, “catppuccin-mocha”, “catppuccin-latte”, “gruvbox-dark”, “nord”, “tokyonight”)

  • extends: — name of an already-registered theme to derive from, with overrides: (token name → style spec) merged on top:

    theme :dark, built_in: "tokyonight"
    theme :high_contrast, extends: :dark, overrides: {text: {foreground: "#ffffff"}}
    

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/charming/application.rb', line 54

def theme(name, from: nil, built_in: nil, extends: nil, overrides: nil)
  sources = [from, built_in, extends].compact
  raise ArgumentError, "theme expects from:, built_in:, or extends:" if sources.empty?
  raise ArgumentError, "theme expects only one of from:, built_in:, or extends:" if sources.length > 1
  raise ArgumentError, "overrides: requires extends:" if overrides && !extends

  themes[name.to_sym] = if extends
    parent = themes.fetch(extends.to_sym) do
      raise ArgumentError, "unknown parent theme: #{extends.inspect} (register it before extending)"
    end
    parent.merge(overrides || {})
  elsif built_in
    UI::Theme.load_builtin(built_in)
  else
    UI::Theme.load_file(resolve_theme_path(from))
  end
end

.theme_for(name = nil) ⇒ Object

Resolves a theme by name (or the default theme when name is nil). Returns the default built-in theme if no name is given and no default is registered.



87
88
89
90
91
92
# File 'lib/charming/application.rb', line 87

def theme_for(name = nil)
  theme_name = name || default_theme
  return UI::Theme.default unless theme_name

  themes.fetch(theme_name.to_sym)
end

.themesObject

Hash of all registered themes keyed by symbol, including those inherited from superclasses.



73
74
75
# File 'lib/charming/application.rb', line 73

def themes
  @themes ||= superclass.respond_to?(:themes) ? superclass.themes.dup : {}
end

Instance Method Details

#routesObject

Delegates to the class-level Router, providing instance access to route definitions.



153
154
155
# File 'lib/charming/application.rb', line 153

def routes
  self.class.routes
end

#save_sessionObject

Serializes the session to the configured ‘persist_session` path. Entries that don’t survive a JSON round-trip (state objects, procs, focus scopes) are skipped. No-op when persistence isn’t configured. Called by the Runtime on exit.



142
143
144
145
146
147
148
149
150
# File 'lib/charming/application.rb', line 142

def save_session
  path = self.class.session_path
  return unless path

  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, JSON.generate(serializable_session))
rescue => e
  logger.warn("session not saved: #{e.class}: #{e.message}")
end

#themeObject



157
158
159
# File 'lib/charming/application.rb', line 157

def theme
  self.class.theme_for(session[:theme])
end

#use_theme(name) ⇒ Object



161
162
163
164
# File 'lib/charming/application.rb', line 161

def use_theme(name)
  self.class.theme_for(name)
  session[:theme] = name.to_sym
end