Class: Heathrow::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/heathrow/config.rb

Constant Summary collapse

HEATHROWRC =
File.expand_path('~/.heathrow/heathrowrc')

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_path = HEATHROW_CONFIG, db = nil) ⇒ Config

Returns a new instance of Config.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/heathrow/config.rb', line 14

def initialize(config_path = HEATHROW_CONFIG, db = nil)
  @@instance = self
  @config_path = config_path
  @db = db
  @settings = load_config
  ensure_heathrow_directory

  # DSL state — populated by loading heathrowrc
  @identities = {}
  @folder_hooks = []
  @global_headers = {}
  @rc_settings = {}

  load_rc

  # Register as singleton so class methods work
  self.class.instance = self
end

Class Attribute Details

.instanceObject

Returns the value of attribute instance.



10
11
12
# File 'lib/heathrow/config.rb', line 10

def self.instance
  @@instance rescue nil
end

Instance Attribute Details

#settingsObject

Returns the value of attribute settings.



6
7
8
# File 'lib/heathrow/config.rb', line 6

def settings
  @settings
end

Class Method Details

.identity_for_folder(folder_name) ⇒ Object



203
204
205
# File 'lib/heathrow/config.rb', line 203

def identity_for_folder(folder_name)
  instance&.identity_for_folder(folder_name)
end

.identity_name_for_folder(folder_name) ⇒ Object



207
208
209
# File 'lib/heathrow/config.rb', line 207

def identity_name_for_folder(folder_name)
  instance&.identity_name_for_folder(folder_name)
end

Instance Method Details

#[](key) ⇒ Object



287
288
289
# File 'lib/heathrow/config.rb', line 287

def [](key)
  @settings[key]
end

#[]=(key, value) ⇒ Object



291
292
293
# File 'lib/heathrow/config.rb', line 291

def []=(key, value)
  @settings[key] = value
end

#bind(key, shell: nil, action: nil, prompt: nil, description: nil) ⇒ Object

Bind a key to a custom action Usage:

bind 'C-S', shell: 'mailq'              # Run shell command, show output
bind 'C-N', shell: 'notmuch search %q'  # %q = prompted query
bind 'C-O', action: :open_in_browser     # Call a Heathrow method
bind '\\',  shell: 'my-script %f'        # %f = current message file path


127
128
129
130
131
132
133
134
135
# File 'lib/heathrow/config.rb', line 127

def bind(key, shell: nil, action: nil, prompt: nil, description: nil)
  @custom_bindings ||= {}
  @custom_bindings[key.to_s] = {
    shell: shell,
    action: action,
    prompt: prompt,
    description: description
  }
end

#channel_name_mapObject



117
118
119
# File 'lib/heathrow/config.rb', line 117

def channel_name_map
  @channel_name_map || {}
end

#channel_names(mapping) ⇒ Object

Map Discord channel IDs to display names Usage: channel_names '123456789' => 'MyServer#general', '987654321' => 'MyServer#dev'



112
113
114
115
# File 'lib/heathrow/config.rb', line 112

def channel_names(mapping)
  @channel_name_map ||= {}
  @channel_name_map.merge!(mapping.transform_keys(&:to_s))
end

#create_default_configObject



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/heathrow/config.rb', line 230

def create_default_config
  default = {
    'version' => Heathrow::VERSION,
    'ui' => {},
    'polling' => {
      'enabled' => true,
      'default_interval' => 60
    },
    'notifications' => {
      'enabled' => false,
      'sound' => false
    }
  }

  save_config(default)
  default
end

#custom_bindingsObject



137
138
139
# File 'lib/heathrow/config.rb', line 137

def custom_bindings
  @custom_bindings || {}
end

#custom_themesObject



82
83
84
# File 'lib/heathrow/config.rb', line 82

def custom_themes
  @custom_themes || {}
end

#custom_viewsObject



105
106
107
# File 'lib/heathrow/config.rb', line 105

def custom_views
  @custom_views || []
end

#delete_db_setting(key) ⇒ Object



327
328
329
330
# File 'lib/heathrow/config.rb', line 327

def delete_db_setting(key)
  return unless @db
  @db.execute("DELETE FROM settings WHERE key = ?", key)
end

#ensure_heathrow_directoryObject



33
34
35
36
# File 'lib/heathrow/config.rb', line 33

def ensure_heathrow_directory
  dir = File.dirname(@config_path)
  FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
end

#folder_hook(pattern, identity_name) ⇒ Object



64
65
66
# File 'lib/heathrow/config.rb', line 64

def folder_hook(pattern, identity_name)
  @folder_hooks << { pattern: pattern, identity: identity_name.to_s }
end

#folder_hooksObject



172
173
174
# File 'lib/heathrow/config.rb', line 172

def folder_hooks
  @folder_hooks
end

#get(path, default = nil) ⇒ Object

Get config value — YAML overrides (from interactive settings) beat RC defaults



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/heathrow/config.rb', line 257

def get(path, default = nil)
  # First check YAML (interactive changes persist here)
  keys = path.split('.')
  yaml_val = @settings
  keys.each do |key|
    break unless yaml_val.is_a?(Hash)
    yaml_val = yaml_val[key]
  end
  return yaml_val unless yaml_val.nil?

  # Then check RC settings
  rc_key = path.sub(/^ui\./, '')
  val = @rc_settings[rc_key]
  return val unless val.nil?

  default
end

#get_db_setting(key, default = nil) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/heathrow/config.rb', line 309

def get_db_setting(key, default = nil)
  return default unless @db

  result = @db.get_first_value("SELECT value FROM settings WHERE key = ?", key)
  return default unless result

  begin
    JSON.parse(result)
  rescue JSON::ParserError
    result
  end
end

#global_headersObject



176
177
178
# File 'lib/heathrow/config.rb', line 176

def global_headers
  @global_headers
end

#has?(path) ⇒ Boolean

Returns:

  • (Boolean)


275
276
277
278
279
280
281
282
283
284
285
# File 'lib/heathrow/config.rb', line 275

def has?(path)
  keys = path.split('.')
  value = @settings

  keys.each do |key|
    return false unless value.is_a?(Hash) && value.key?(key)
    value = value[key]
  end

  true
end

#has_db_setting?(key) ⇒ Boolean

Returns:

  • (Boolean)


322
323
324
325
# File 'lib/heathrow/config.rb', line 322

def has_db_setting?(key)
  return false unless @db
  @db.get_first_value("SELECT COUNT(*) FROM settings WHERE key = ?", key).to_i > 0
end

#header(name, value) ⇒ Object



68
69
70
# File 'lib/heathrow/config.rb', line 68

def header(name, value)
  @global_headers[name.to_s] = value.to_s
end

#identitiesObject

── Identity resolution ──



168
169
170
# File 'lib/heathrow/config.rb', line 168

def identities
  @identities
end

#identity(name, from:, signature: nil, smtp: nil, headers: nil) ⇒ Object



55
56
57
58
59
60
61
62
# File 'lib/heathrow/config.rb', line 55

def identity(name, from:, signature: nil, smtp: nil, headers: nil)
  @identities[name.to_s] = {
    from: from,
    signature: signature ? File.expand_path(signature) : nil,
    smtp: smtp ? File.expand_path(smtp) : nil,
    headers: headers || {}
  }
end

#identity_for_folder(folder_name) ⇒ Object

Get identity for a given folder name (first matching folder_hook wins)



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/heathrow/config.rb', line 181

def identity_for_folder(folder_name)
  folder_name ||= 'INBOX'
  hook = @folder_hooks.find { |h| folder_name.match?(h[:pattern]) }
  identity_name = hook ? hook[:identity] : 'default'
  id = @identities[identity_name] || @identities['default']
  return nil unless id

  # Merge global headers into identity headers
  merged_headers = @global_headers.merge(id[:headers] || {})
  id.merge(headers: merged_headers)
end

#identity_name_for_folder(folder_name) ⇒ Object



193
194
195
196
197
# File 'lib/heathrow/config.rb', line 193

def identity_name_for_folder(folder_name)
  folder_name ||= 'INBOX'
  hook = @folder_hooks.find { |h| folder_name.match?(h[:pattern]) }
  hook ? hook[:identity] : 'default'
end

#load_configObject

── YAML config (legacy, still used for some things) ──



222
223
224
225
226
227
228
# File 'lib/heathrow/config.rb', line 222

def load_config
  if File.exist?(@config_path)
    YAML.load_file(@config_path)
  else
    create_default_config
  end
end

#load_rc(path = HEATHROWRC) ⇒ Object

── Load the RC file ──



143
144
145
146
147
148
149
150
151
# File 'lib/heathrow/config.rb', line 143

def load_rc(path = HEATHROWRC)
  return unless File.exist?(path)

  # Evaluate in the context of this Config instance so DSL methods work
  instance_eval(File.read(path), path)
rescue => e
  STDERR.puts "Error loading #{path}: #{e.message}"
  STDERR.puts e.backtrace.first(3).join("\n") if ENV['DEBUG']
end

#rc(key, default = nil) ⇒ Object

── RC settings access ── RC settings override YAML config values



215
216
217
218
# File 'lib/heathrow/config.rb', line 215

def rc(key, default = nil)
  val = @rc_settings[key.to_s]
  val.nil? ? default : val
end

#reload_rcObject

Reload the RC file (for interactive 'R' key)



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/heathrow/config.rb', line 154

def reload_rc
  @identities = {}
  @folder_hooks = []
  @global_headers = {}
  @rc_settings = {}
  @custom_themes = nil
  @custom_views = nil
  @custom_bindings = nil
  @channel_name_map = nil
  load_rc
end

#saveObject



252
253
254
# File 'lib/heathrow/config.rb', line 252

def save
  save_config
end

#save_config(config = @settings) ⇒ Object



248
249
250
# File 'lib/heathrow/config.rb', line 248

def save_config(config = @settings)
  File.write(@config_path, config.to_yaml)
end

#set(key, value) ⇒ Object

── RC file DSL methods (called from heathrowrc) ──



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/heathrow/config.rb', line 40

def set(key, value)
  key_s = key.to_s
  if key_s.include?('.')
    # Dot-notation path: save to YAML settings (e.g. 'ui.color_theme')
    keys = key_s.split('.')
    last_key = keys.pop
    parent = @settings
    keys.each { |k| parent[k] ||= {}; parent = parent[k] }
    parent[last_key] = value
  else
    # Simple key: RC setting (e.g. :color_theme)
    @rc_settings[key_s] = value
  end
end

#set_db_setting(key, value) ⇒ Object

── Database-backed settings ──



297
298
299
300
301
302
303
304
305
306
307
# File 'lib/heathrow/config.rb', line 297

def set_db_setting(key, value)
  return unless @db

  now = Time.now.to_i
  value_str = value.is_a?(String) ? value : value.to_json

  @db.execute <<-SQL, [key, value_str, now]
    INSERT OR REPLACE INTO settings (key, value, updated_at)
    VALUES (?, ?, ?)
  SQL
end

#theme(name, **colors) ⇒ Object

Define a custom color theme (or override a built-in) Usage in heathrowrc:

theme 'MyTheme', unread: 226, read: 249, accent: 10, thread: 255,
                dm: 201, tag: 14, quote1: 114, quote2: 180,
                quote3: 139, quote4: 109, sig: 242


77
78
79
80
# File 'lib/heathrow/config.rb', line 77

def theme(name, **colors)
  @custom_themes ||= {}
  @custom_themes[name.to_s] = colors
end

#view(key, name, **opts) ⇒ Object

Define a custom view (replaces hardcoded views in database.rb) Usage: view '1', 'Personal', folder: 'Personal' view '4', 'Work', folder: 'Work.Archive' view '5', 'RSS', source_type: 'rss'



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/heathrow/config.rb', line 90

def view(key, name, **opts)
  @custom_views ||= []
  filters = if opts[:folder]
    { 'rules' => [{ 'field' => 'folder', 'op' => 'like', 'value' => opts[:folder] }] }
  elsif opts[:source_type]
    { 'rules' => [{ 'field' => 'source_type', 'op' => '=', 'value' => opts[:source_type] }] }
  elsif opts[:filters]
    opts[:filters]
  else
    { 'rules' => [] }
  end
  @custom_views << { key: key.to_s, name: name, filters: filters,
                     sort_order: opts[:sort_order] }
end