htm 0.0.30 → 0.0.31
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/htm/config/defaults.yml +25 -13
- data/lib/htm/config.rb +21 -112
- data/lib/htm/version.rb +1 -1
- metadata +4 -7
- data/lib/htm/config/section.rb +0 -74
- data/lib/htm/loaders/defaults_loader.rb +0 -166
- data/lib/htm/loaders/xdg_config_loader.rb +0 -116
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48e67477eb98226aeae3ecfbf88e7784469631f08b635b343fc426e3cff21fd7
|
|
4
|
+
data.tar.gz: cbff8e499ac3a382f3e1310573a54535e96fe495172aab6d5df8ad29369cf1ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7eb33fc51efcdb6ae28bec86897c57fb15604db0bd7a0648bcaed45506161815003c13d85dc5ba12365fc543b6900d088229242ee22d707717f97a7d60aa8c4
|
|
7
|
+
data.tar.gz: 51b0af166ba19f5f5c9ed633711fc611f83c75b5b769efea6f0ac80384d19a0dec003352668f1d16f4608770541f819c8700809ddad46c791c973016ebe4cb4d
|
data/lib/htm/config/defaults.yml
CHANGED
|
@@ -49,7 +49,7 @@ defaults:
|
|
|
49
49
|
# Access: HTM.config.embedding.provider, HTM.config.embedding.model, etc.
|
|
50
50
|
# ---------------------------------------------------------------------------
|
|
51
51
|
embedding:
|
|
52
|
-
provider: ollama
|
|
52
|
+
provider: :ollama
|
|
53
53
|
model: nomic-embed-text:latest
|
|
54
54
|
dimensions: 768
|
|
55
55
|
timeout: 120
|
|
@@ -65,7 +65,7 @@ defaults:
|
|
|
65
65
|
# %{taxonomy_context} - existing taxonomy info or new taxonomy message
|
|
66
66
|
# ---------------------------------------------------------------------------
|
|
67
67
|
tag:
|
|
68
|
-
provider: ollama
|
|
68
|
+
provider: :ollama
|
|
69
69
|
model: gemma3:latest
|
|
70
70
|
timeout: 180
|
|
71
71
|
max_depth: 4
|
|
@@ -110,7 +110,7 @@ defaults:
|
|
|
110
110
|
# %{text} - the content to extract propositions from
|
|
111
111
|
# ---------------------------------------------------------------------------
|
|
112
112
|
proposition:
|
|
113
|
-
provider: ollama
|
|
113
|
+
provider: :ollama
|
|
114
114
|
model: gemma3:latest
|
|
115
115
|
timeout: 180
|
|
116
116
|
enabled: true
|
|
@@ -164,11 +164,12 @@ defaults:
|
|
|
164
164
|
|
|
165
165
|
# ---------------------------------------------------------------------------
|
|
166
166
|
# Chunking Configuration (for file loading)
|
|
167
|
-
# Access: HTM.config.chunking.
|
|
167
|
+
# Access: HTM.config.chunking.chunk_size, HTM.config.chunking.chunk_overlap
|
|
168
|
+
# Note: Using chunk_size/chunk_overlap to avoid collision with Enumerable#size
|
|
168
169
|
# ---------------------------------------------------------------------------
|
|
169
170
|
chunking:
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
chunk_size: 1024
|
|
172
|
+
chunk_overlap: 64
|
|
172
173
|
|
|
173
174
|
# ---------------------------------------------------------------------------
|
|
174
175
|
# Circuit Breaker Configuration
|
|
@@ -195,16 +196,16 @@ defaults:
|
|
|
195
196
|
# Access: HTM.config.job.backend
|
|
196
197
|
# ---------------------------------------------------------------------------
|
|
197
198
|
job:
|
|
198
|
-
backend: fiber
|
|
199
|
+
backend: :fiber
|
|
199
200
|
|
|
200
201
|
# ---------------------------------------------------------------------------
|
|
201
202
|
# General Settings
|
|
202
203
|
# Access: HTM.config.week_start, HTM.config.connection_timeout, etc.
|
|
203
204
|
# ---------------------------------------------------------------------------
|
|
204
|
-
week_start: sunday
|
|
205
|
+
week_start: :sunday
|
|
205
206
|
connection_timeout: 60
|
|
206
207
|
telemetry_enabled: false
|
|
207
|
-
log_level: info
|
|
208
|
+
log_level: :info
|
|
208
209
|
|
|
209
210
|
# ---------------------------------------------------------------------------
|
|
210
211
|
# Provider Credentials
|
|
@@ -250,7 +251,7 @@ defaults:
|
|
|
250
251
|
development:
|
|
251
252
|
database:
|
|
252
253
|
name: htm_development
|
|
253
|
-
log_level: debug
|
|
254
|
+
log_level: :debug
|
|
254
255
|
|
|
255
256
|
# =============================================================================
|
|
256
257
|
# Test Environment Overrides
|
|
@@ -259,8 +260,8 @@ test:
|
|
|
259
260
|
database:
|
|
260
261
|
name: htm_test
|
|
261
262
|
job:
|
|
262
|
-
backend: inline
|
|
263
|
-
log_level: warn
|
|
263
|
+
backend: :inline
|
|
264
|
+
log_level: :warn
|
|
264
265
|
telemetry_enabled: false
|
|
265
266
|
|
|
266
267
|
# =============================================================================
|
|
@@ -270,5 +271,16 @@ production:
|
|
|
270
271
|
database:
|
|
271
272
|
pool_size: 25
|
|
272
273
|
sslmode: require
|
|
273
|
-
log_level: warn
|
|
274
|
+
log_level: :warn
|
|
274
275
|
telemetry_enabled: true
|
|
276
|
+
|
|
277
|
+
# =============================================================================
|
|
278
|
+
# Examples Environment Overrides (for running example scripts)
|
|
279
|
+
# =============================================================================
|
|
280
|
+
examples:
|
|
281
|
+
database:
|
|
282
|
+
name: htm_examples
|
|
283
|
+
job:
|
|
284
|
+
backend: :inline
|
|
285
|
+
log_level: :info
|
|
286
|
+
telemetry_enabled: false
|
data/lib/htm/config.rb
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'myway_config'
|
|
4
4
|
require 'logger'
|
|
5
|
-
require 'yaml'
|
|
6
5
|
|
|
7
6
|
# Define Config class first to establish superclass
|
|
8
7
|
class HTM
|
|
9
|
-
class Config <
|
|
8
|
+
class Config < MywayConfig::Base
|
|
10
9
|
end
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
require_relative 'config/section'
|
|
14
12
|
require_relative 'config/validator'
|
|
15
13
|
require_relative 'config/database'
|
|
16
14
|
require_relative 'config/builder'
|
|
@@ -61,35 +59,11 @@ class HTM
|
|
|
61
59
|
|
|
62
60
|
config_name :htm
|
|
63
61
|
env_prefix :htm
|
|
62
|
+
defaults_path File.expand_path('config/defaults.yml', __dir__)
|
|
64
63
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Path to bundled defaults file (defines both schema and default values)
|
|
70
|
-
DEFAULTS_PATH = File.expand_path('config/defaults.yml', __dir__).freeze
|
|
71
|
-
|
|
72
|
-
# Load schema from defaults.yml at class definition time
|
|
73
|
-
begin
|
|
74
|
-
defaults_content = File.read(DEFAULTS_PATH)
|
|
75
|
-
raw_yaml = YAML.safe_load(
|
|
76
|
-
defaults_content,
|
|
77
|
-
permitted_classes: [Symbol],
|
|
78
|
-
symbolize_names: true,
|
|
79
|
-
aliases: true
|
|
80
|
-
) || {}
|
|
81
|
-
SCHEMA = raw_yaml[:defaults] || {}
|
|
82
|
-
rescue StandardError => e
|
|
83
|
-
warn "HTM: Could not load schema from #{DEFAULTS_PATH}: #{e.message}"
|
|
84
|
-
SCHEMA = {}
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Nested section attributes (defined as hashes, converted to ConfigSection)
|
|
88
|
-
attr_config :database, :service, :embedding, :tag, :proposition,
|
|
89
|
-
:chunking, :circuit_breaker, :relevance, :job, :providers
|
|
90
|
-
|
|
91
|
-
# Top-level scalar attributes
|
|
92
|
-
attr_config :week_start, :connection_timeout, :telemetry_enabled, :log_level
|
|
64
|
+
# Auto-configure attributes and coercions from defaults.yml schema
|
|
65
|
+
# This replaces manual attr_config and coerce_types declarations
|
|
66
|
+
auto_configure!
|
|
93
67
|
|
|
94
68
|
# Custom environment detection: HTM_ENV > RAILS_ENV > RACK_ENV > 'development'
|
|
95
69
|
class << self
|
|
@@ -102,60 +76,6 @@ class HTM
|
|
|
102
76
|
end
|
|
103
77
|
end
|
|
104
78
|
|
|
105
|
-
# ==========================================================================
|
|
106
|
-
# Type Coercion
|
|
107
|
-
# ==========================================================================
|
|
108
|
-
|
|
109
|
-
TO_SYMBOL = ->(v) { v.nil? ? nil : v.to_s.to_sym }
|
|
110
|
-
|
|
111
|
-
# Create a coercion that merges incoming value with SCHEMA defaults for a section.
|
|
112
|
-
# This ensures env vars like HTM_DATABASE__URL don't lose other defaults.
|
|
113
|
-
def self.config_section_with_defaults(section_key)
|
|
114
|
-
defaults = SCHEMA[section_key] || {}
|
|
115
|
-
->(v) {
|
|
116
|
-
return v if v.is_a?(ConfigSection)
|
|
117
|
-
incoming = v || {}
|
|
118
|
-
# Deep merge: defaults first, then overlay incoming values
|
|
119
|
-
merged = deep_merge_hashes(defaults.dup, incoming)
|
|
120
|
-
ConfigSection.new(merged)
|
|
121
|
-
}
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Deep merge helper for coercion
|
|
125
|
-
def self.deep_merge_hashes(base, overlay)
|
|
126
|
-
base.merge(overlay) do |_key, old_val, new_val|
|
|
127
|
-
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
128
|
-
deep_merge_hashes(old_val, new_val)
|
|
129
|
-
else
|
|
130
|
-
new_val.nil? ? old_val : new_val
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
coerce_types(
|
|
136
|
-
# Nested sections -> ConfigSection objects (with SCHEMA defaults merged)
|
|
137
|
-
database: config_section_with_defaults(:database),
|
|
138
|
-
service: config_section_with_defaults(:service),
|
|
139
|
-
embedding: config_section_with_defaults(:embedding),
|
|
140
|
-
tag: config_section_with_defaults(:tag),
|
|
141
|
-
proposition: config_section_with_defaults(:proposition),
|
|
142
|
-
chunking: config_section_with_defaults(:chunking),
|
|
143
|
-
circuit_breaker: config_section_with_defaults(:circuit_breaker),
|
|
144
|
-
relevance: config_section_with_defaults(:relevance),
|
|
145
|
-
job: config_section_with_defaults(:job),
|
|
146
|
-
providers: config_section_with_defaults(:providers),
|
|
147
|
-
|
|
148
|
-
# Top-level symbols
|
|
149
|
-
week_start: TO_SYMBOL,
|
|
150
|
-
log_level: TO_SYMBOL,
|
|
151
|
-
|
|
152
|
-
# Top-level integers
|
|
153
|
-
connection_timeout: :integer,
|
|
154
|
-
|
|
155
|
-
# Top-level booleans
|
|
156
|
-
telemetry_enabled: :boolean
|
|
157
|
-
)
|
|
158
|
-
|
|
159
79
|
# ==========================================================================
|
|
160
80
|
# Validation
|
|
161
81
|
# ==========================================================================
|
|
@@ -256,11 +176,11 @@ class HTM
|
|
|
256
176
|
|
|
257
177
|
# Chunking convenience accessors
|
|
258
178
|
def chunk_size
|
|
259
|
-
chunking.
|
|
179
|
+
chunking.chunk_size.to_i
|
|
260
180
|
end
|
|
261
181
|
|
|
262
182
|
def chunk_overlap
|
|
263
|
-
chunking.
|
|
183
|
+
chunking.chunk_overlap.to_i
|
|
264
184
|
end
|
|
265
185
|
|
|
266
186
|
# Circuit breaker convenience accessors
|
|
@@ -375,17 +295,8 @@ class HTM
|
|
|
375
295
|
# Environment Helpers
|
|
376
296
|
# ==========================================================================
|
|
377
297
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
def development?
|
|
383
|
-
self.class.env == 'development'
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
def production?
|
|
387
|
-
self.class.env == 'production'
|
|
388
|
-
end
|
|
298
|
+
# Note: test?, development?, production? are auto-generated by MywayConfig::Base
|
|
299
|
+
# based on environment keys in defaults.yml
|
|
389
300
|
|
|
390
301
|
def environment
|
|
391
302
|
self.class.env
|
|
@@ -396,17 +307,16 @@ class HTM
|
|
|
396
307
|
# ==========================================================================
|
|
397
308
|
|
|
398
309
|
# Returns list of valid environment names from bundled defaults
|
|
310
|
+
# Inherited from MywayConfig::Base - delegates to DefaultsLoader
|
|
399
311
|
#
|
|
400
312
|
# @return [Array<Symbol>] valid environment names (e.g., [:development, :production, :test])
|
|
401
|
-
|
|
402
|
-
HTM::Loaders::DefaultsLoader.valid_environments
|
|
403
|
-
end
|
|
313
|
+
# Note: valid_environments is inherited from MywayConfig::Base
|
|
404
314
|
|
|
405
315
|
# Check if current environment is valid (defined in config)
|
|
406
316
|
#
|
|
407
317
|
# @return [Boolean] true if environment has a config section
|
|
408
318
|
def self.valid_environment?
|
|
409
|
-
|
|
319
|
+
MywayConfig::Loaders::DefaultsLoader.valid_environment?(config_name, env)
|
|
410
320
|
end
|
|
411
321
|
|
|
412
322
|
# Validate that the current environment is configured
|
|
@@ -415,7 +325,7 @@ class HTM
|
|
|
415
325
|
# @return [true] if environment is valid
|
|
416
326
|
def self.validate_environment!
|
|
417
327
|
current = env
|
|
418
|
-
return true if
|
|
328
|
+
return true if valid_environment?
|
|
419
329
|
|
|
420
330
|
valid = valid_environments.map(&:to_s).join(', ')
|
|
421
331
|
raise HTM::ConfigurationError,
|
|
@@ -438,7 +348,7 @@ class HTM
|
|
|
438
348
|
# ==========================================================================
|
|
439
349
|
|
|
440
350
|
def self.xdg_config_paths
|
|
441
|
-
|
|
351
|
+
MywayConfig::Loaders::XdgConfigLoader.config_paths(config_name)
|
|
442
352
|
end
|
|
443
353
|
|
|
444
354
|
def self.xdg_config_file
|
|
@@ -452,7 +362,7 @@ class HTM
|
|
|
452
362
|
end
|
|
453
363
|
|
|
454
364
|
def self.active_xdg_config_file
|
|
455
|
-
|
|
365
|
+
MywayConfig::Loaders::XdgConfigLoader.find_config_file(config_name)
|
|
456
366
|
end
|
|
457
367
|
|
|
458
368
|
# ==========================================================================
|
|
@@ -544,10 +454,11 @@ class HTM
|
|
|
544
454
|
|
|
545
455
|
def coerce_nested_types
|
|
546
456
|
# Ensure nested provider sections are ConfigSections
|
|
547
|
-
|
|
457
|
+
# myway_config handles top-level sections, but we need to handle nested ones
|
|
458
|
+
if providers.is_a?(MywayConfig::ConfigSection)
|
|
548
459
|
%i[openai anthropic gemini azure ollama huggingface openrouter bedrock deepseek].each do |provider|
|
|
549
460
|
value = providers[provider]
|
|
550
|
-
providers[provider] = ConfigSection.new(value) if value.is_a?(Hash)
|
|
461
|
+
providers[provider] = MywayConfig::ConfigSection.new(value) if value.is_a?(Hash)
|
|
551
462
|
end
|
|
552
463
|
end
|
|
553
464
|
|
|
@@ -586,7 +497,5 @@ class HTM
|
|
|
586
497
|
end
|
|
587
498
|
end
|
|
588
499
|
|
|
589
|
-
#
|
|
590
|
-
#
|
|
591
|
-
require_relative 'loaders/defaults_loader'
|
|
592
|
-
require_relative 'loaders/xdg_config_loader'
|
|
500
|
+
# myway_config provides DefaultsLoader and XdgConfigLoader automatically
|
|
501
|
+
# Loaders are registered when MywayConfig.setup! is called (happens on require)
|
data/lib/htm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: htm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.31
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -150,19 +150,19 @@ dependencies:
|
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
151
|
version: '0'
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
|
-
name:
|
|
153
|
+
name: myway_config
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
|
156
156
|
- - ">="
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
|
-
version:
|
|
158
|
+
version: 0.1.2
|
|
159
159
|
type: :runtime
|
|
160
160
|
prerelease: false
|
|
161
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements:
|
|
163
163
|
- - ">="
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
|
-
version:
|
|
165
|
+
version: 0.1.2
|
|
166
166
|
- !ruby/object:Gem::Dependency
|
|
167
167
|
name: simple_flow
|
|
168
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -617,7 +617,6 @@ files:
|
|
|
617
617
|
- lib/htm/config/builder.rb
|
|
618
618
|
- lib/htm/config/database.rb
|
|
619
619
|
- lib/htm/config/defaults.yml
|
|
620
|
-
- lib/htm/config/section.rb
|
|
621
620
|
- lib/htm/config/validator.rb
|
|
622
621
|
- lib/htm/database.rb
|
|
623
622
|
- lib/htm/embedding_service.rb
|
|
@@ -627,10 +626,8 @@ files:
|
|
|
627
626
|
- lib/htm/jobs/generate_embedding_job.rb
|
|
628
627
|
- lib/htm/jobs/generate_propositions_job.rb
|
|
629
628
|
- lib/htm/jobs/generate_tags_job.rb
|
|
630
|
-
- lib/htm/loaders/defaults_loader.rb
|
|
631
629
|
- lib/htm/loaders/markdown_chunker.rb
|
|
632
630
|
- lib/htm/loaders/markdown_loader.rb
|
|
633
|
-
- lib/htm/loaders/xdg_config_loader.rb
|
|
634
631
|
- lib/htm/long_term_memory.rb
|
|
635
632
|
- lib/htm/long_term_memory/fulltext_search.rb
|
|
636
633
|
- lib/htm/long_term_memory/hybrid_search.rb
|
data/lib/htm/config/section.rb
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class HTM
|
|
4
|
-
# ConfigSection provides method access to nested configuration hashes
|
|
5
|
-
#
|
|
6
|
-
# @example
|
|
7
|
-
# section = ConfigSection.new(host: 'localhost', port: 5432)
|
|
8
|
-
# section.host # => 'localhost'
|
|
9
|
-
# section.port # => 5432
|
|
10
|
-
#
|
|
11
|
-
class ConfigSection
|
|
12
|
-
def initialize(hash = {})
|
|
13
|
-
@data = {}
|
|
14
|
-
(hash || {}).each do |key, value|
|
|
15
|
-
@data[key.to_sym] = value.is_a?(Hash) ? ConfigSection.new(value) : value
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def method_missing(method, *args, &block)
|
|
20
|
-
key = method.to_s
|
|
21
|
-
if key.end_with?('=')
|
|
22
|
-
@data[key.chomp('=').to_sym] = args.first
|
|
23
|
-
elsif @data.key?(method)
|
|
24
|
-
@data[method]
|
|
25
|
-
else
|
|
26
|
-
nil
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def respond_to_missing?(method, include_private = false)
|
|
31
|
-
key = method.to_s.chomp('=').to_sym
|
|
32
|
-
@data.key?(key) || super
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def to_h
|
|
36
|
-
@data.transform_values do |v|
|
|
37
|
-
v.is_a?(ConfigSection) ? v.to_h : v
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def [](key)
|
|
42
|
-
@data[key.to_sym]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def []=(key, value)
|
|
46
|
-
@data[key.to_sym] = value
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def merge(other)
|
|
50
|
-
other_hash = other.is_a?(ConfigSection) ? other.to_h : other
|
|
51
|
-
ConfigSection.new(deep_merge(to_h, other_hash || {}))
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def keys
|
|
55
|
-
@data.keys
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def each(&block)
|
|
59
|
-
@data.each(&block)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def deep_merge(base, overlay)
|
|
65
|
-
base.merge(overlay) do |_key, old_val, new_val|
|
|
66
|
-
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
67
|
-
deep_merge(old_val, new_val)
|
|
68
|
-
else
|
|
69
|
-
new_val
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'anyway_config'
|
|
4
|
-
require 'yaml'
|
|
5
|
-
|
|
6
|
-
class HTM
|
|
7
|
-
module Loaders
|
|
8
|
-
# Bundled Defaults Loader for Anyway Config
|
|
9
|
-
#
|
|
10
|
-
# Loads default configuration values from a YAML file bundled with the gem.
|
|
11
|
-
# This ensures defaults are always available regardless of where HTM is installed.
|
|
12
|
-
#
|
|
13
|
-
# The defaults.yml file has this structure:
|
|
14
|
-
# defaults: # Base values for all environments
|
|
15
|
-
# database:
|
|
16
|
-
# host: localhost
|
|
17
|
-
# port: 5432
|
|
18
|
-
# development: # Overrides for development
|
|
19
|
-
# database:
|
|
20
|
-
# name: htm_development
|
|
21
|
-
# test: # Overrides for test
|
|
22
|
-
# database:
|
|
23
|
-
# name: htm_test
|
|
24
|
-
# production: # Overrides for production
|
|
25
|
-
# database:
|
|
26
|
-
# sslmode: require
|
|
27
|
-
#
|
|
28
|
-
# This loader deep-merges `defaults` with the current environment's overrides.
|
|
29
|
-
#
|
|
30
|
-
# This loader runs at LOWEST priority (before XDG), so all other sources
|
|
31
|
-
# can override these bundled defaults:
|
|
32
|
-
# 1. Bundled defaults (this loader)
|
|
33
|
-
# 2. XDG user config (~/.config/htm/htm.yml)
|
|
34
|
-
# 3. Project config (./config/htm.yml)
|
|
35
|
-
# 4. Local overrides (./config/htm.local.yml)
|
|
36
|
-
# 5. Environment variables (HTM_*)
|
|
37
|
-
# 6. Programmatic (configure block)
|
|
38
|
-
#
|
|
39
|
-
class DefaultsLoader < Anyway::Loaders::Base
|
|
40
|
-
DEFAULTS_PATH = File.expand_path('../config/defaults.yml', __dir__).freeze
|
|
41
|
-
|
|
42
|
-
class << self
|
|
43
|
-
# Returns the path to the bundled defaults file
|
|
44
|
-
#
|
|
45
|
-
# @return [String] path to defaults.yml
|
|
46
|
-
def defaults_path
|
|
47
|
-
DEFAULTS_PATH
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Check if defaults file exists
|
|
51
|
-
#
|
|
52
|
-
# @return [Boolean]
|
|
53
|
-
def defaults_exist?
|
|
54
|
-
File.exist?(DEFAULTS_PATH)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Load and parse the raw YAML content
|
|
58
|
-
#
|
|
59
|
-
# @return [Hash] parsed YAML with symbolized keys
|
|
60
|
-
def load_raw_yaml
|
|
61
|
-
return {} unless defaults_exist?
|
|
62
|
-
|
|
63
|
-
content = File.read(defaults_path)
|
|
64
|
-
YAML.safe_load(
|
|
65
|
-
content,
|
|
66
|
-
permitted_classes: [Symbol],
|
|
67
|
-
symbolize_names: true,
|
|
68
|
-
aliases: true
|
|
69
|
-
) || {}
|
|
70
|
-
rescue Psych::SyntaxError => e
|
|
71
|
-
warn "HTM: Failed to parse bundled defaults #{defaults_path}: #{e.message}"
|
|
72
|
-
{}
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Extract the schema (attribute names) from the defaults section
|
|
76
|
-
#
|
|
77
|
-
# @return [Hash] the defaults section containing all attribute definitions
|
|
78
|
-
def schema
|
|
79
|
-
raw = load_raw_yaml
|
|
80
|
-
raw[:defaults] || {}
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Returns valid environment names from the config file
|
|
84
|
-
#
|
|
85
|
-
# Valid environments are top-level keys in defaults.yml excluding 'defaults'.
|
|
86
|
-
# For example, if defaults.yml has keys: defaults, development, test, production
|
|
87
|
-
# this returns [:development, :test, :production]
|
|
88
|
-
#
|
|
89
|
-
# @return [Array<Symbol>] list of valid environment names
|
|
90
|
-
def valid_environments
|
|
91
|
-
raw = load_raw_yaml
|
|
92
|
-
raw.keys.reject { |k| k == :defaults }.sort
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Check if a given environment name is valid
|
|
96
|
-
#
|
|
97
|
-
# @param env [String, Symbol] environment name to check
|
|
98
|
-
# @return [Boolean] true if environment is valid
|
|
99
|
-
def valid_environment?(env)
|
|
100
|
-
return false if env.nil? || env.to_s.empty?
|
|
101
|
-
return false if env.to_s == 'defaults'
|
|
102
|
-
|
|
103
|
-
valid_environments.include?(env.to_sym)
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def call(name:, **_options)
|
|
108
|
-
return {} unless self.class.defaults_exist?
|
|
109
|
-
|
|
110
|
-
trace!(:bundled_defaults, path: self.class.defaults_path) do
|
|
111
|
-
load_and_merge_for_environment
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
# Load defaults and deep merge with environment-specific overrides
|
|
118
|
-
#
|
|
119
|
-
# @return [Hash] merged configuration for current environment
|
|
120
|
-
def load_and_merge_for_environment
|
|
121
|
-
raw = self.class.load_raw_yaml
|
|
122
|
-
return {} if raw.empty?
|
|
123
|
-
|
|
124
|
-
# Start with the defaults section
|
|
125
|
-
defaults = raw[:defaults] || {}
|
|
126
|
-
|
|
127
|
-
# Deep merge with environment-specific overrides
|
|
128
|
-
env = current_environment
|
|
129
|
-
env_overrides = raw[env.to_sym] || {}
|
|
130
|
-
|
|
131
|
-
deep_merge(defaults, env_overrides)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Deep merge two hashes, with overlay taking precedence
|
|
135
|
-
#
|
|
136
|
-
# @param base [Hash] base configuration
|
|
137
|
-
# @param overlay [Hash] overlay configuration (takes precedence)
|
|
138
|
-
# @return [Hash] merged result
|
|
139
|
-
def deep_merge(base, overlay)
|
|
140
|
-
base.merge(overlay) do |_key, old_val, new_val|
|
|
141
|
-
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
142
|
-
deep_merge(old_val, new_val)
|
|
143
|
-
else
|
|
144
|
-
new_val
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Determine the current environment
|
|
150
|
-
#
|
|
151
|
-
# Priority: HTM_ENV > RAILS_ENV > RACK_ENV > 'development'
|
|
152
|
-
#
|
|
153
|
-
# @return [String] current environment name
|
|
154
|
-
def current_environment
|
|
155
|
-
ENV['HTM_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Register the defaults loader at LOWEST priority (before :yml loader)
|
|
162
|
-
# This ensures bundled defaults are overridden by all other sources:
|
|
163
|
-
# - XDG user config (registered after this, also before :yml)
|
|
164
|
-
# - Project config (:yml loader)
|
|
165
|
-
# - Environment variables (:env loader)
|
|
166
|
-
Anyway.loaders.insert_before :yml, :bundled_defaults, HTM::Loaders::DefaultsLoader
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'anyway_config'
|
|
4
|
-
require 'yaml'
|
|
5
|
-
|
|
6
|
-
class HTM
|
|
7
|
-
module Loaders
|
|
8
|
-
# XDG Base Directory Specification loader for Anyway Config
|
|
9
|
-
#
|
|
10
|
-
# Loads configuration from XDG-compliant paths:
|
|
11
|
-
# 1. $XDG_CONFIG_HOME/htm/htm.yml (if XDG_CONFIG_HOME is set)
|
|
12
|
-
# 2. ~/.config/htm/htm.yml (XDG default fallback)
|
|
13
|
-
#
|
|
14
|
-
# On macOS, also checks:
|
|
15
|
-
# 3. ~/Library/Application Support/htm/htm.yml
|
|
16
|
-
#
|
|
17
|
-
# This loader runs BEFORE the project-local config loader,
|
|
18
|
-
# so project configs take precedence over user-global configs.
|
|
19
|
-
#
|
|
20
|
-
# @example XDG config file location
|
|
21
|
-
# ~/.config/htm/htm.yml
|
|
22
|
-
#
|
|
23
|
-
# @example Custom XDG_CONFIG_HOME
|
|
24
|
-
# export XDG_CONFIG_HOME=/my/config
|
|
25
|
-
# # Looks for /my/config/htm/htm.yml
|
|
26
|
-
#
|
|
27
|
-
class XdgConfigLoader < Anyway::Loaders::Base
|
|
28
|
-
class << self
|
|
29
|
-
# Returns all XDG config paths to check, in order of priority (lowest first)
|
|
30
|
-
#
|
|
31
|
-
# Per XDG spec: If $XDG_CONFIG_HOME is set, use it; otherwise use ~/.config
|
|
32
|
-
#
|
|
33
|
-
# @return [Array<String>] list of potential config file paths
|
|
34
|
-
def config_paths
|
|
35
|
-
paths = []
|
|
36
|
-
|
|
37
|
-
# macOS Application Support (lowest priority, only when XDG_CONFIG_HOME is not set)
|
|
38
|
-
if macos? && (!ENV['XDG_CONFIG_HOME'] || ENV['XDG_CONFIG_HOME'].empty?)
|
|
39
|
-
macos_path = File.expand_path('~/Library/Application Support/htm')
|
|
40
|
-
paths << macos_path if Dir.exist?(File.dirname(macos_path))
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# XDG_CONFIG_HOME takes precedence over default
|
|
44
|
-
if ENV['XDG_CONFIG_HOME'] && !ENV['XDG_CONFIG_HOME'].empty?
|
|
45
|
-
paths << File.join(ENV['XDG_CONFIG_HOME'], 'htm')
|
|
46
|
-
else
|
|
47
|
-
# XDG default: ~/.config/htm (only when XDG_CONFIG_HOME is not set)
|
|
48
|
-
paths << File.expand_path('~/.config/htm')
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
paths
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Find the first existing config file
|
|
55
|
-
#
|
|
56
|
-
# @param name [String] config name (e.g., 'htm')
|
|
57
|
-
# @return [String, nil] path to config file or nil if not found
|
|
58
|
-
def find_config_file(name)
|
|
59
|
-
config_paths.reverse_each do |dir|
|
|
60
|
-
file = File.join(dir, "#{name}.yml")
|
|
61
|
-
return file if File.exist?(file)
|
|
62
|
-
end
|
|
63
|
-
nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def macos?
|
|
69
|
-
RUBY_PLATFORM.include?('darwin')
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def call(name:, **_options)
|
|
74
|
-
config_file = self.class.find_config_file(name)
|
|
75
|
-
return {} unless config_file
|
|
76
|
-
|
|
77
|
-
trace!(:xdg, path: config_file) do
|
|
78
|
-
load_yaml(config_file, name)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
def load_yaml(path, name)
|
|
85
|
-
return {} unless File.exist?(path)
|
|
86
|
-
|
|
87
|
-
content = File.read(path)
|
|
88
|
-
parsed = YAML.safe_load(content, permitted_classes: [Symbol], symbolize_names: true, aliases: true) || {}
|
|
89
|
-
|
|
90
|
-
# Support environment-specific configs
|
|
91
|
-
env = Anyway::Settings.current_environment ||
|
|
92
|
-
ENV['HTM_ENV'] ||
|
|
93
|
-
ENV['RAILS_ENV'] ||
|
|
94
|
-
ENV['RACK_ENV'] ||
|
|
95
|
-
'development'
|
|
96
|
-
|
|
97
|
-
# Check for environment key first, fall back to root level
|
|
98
|
-
if parsed.key?(env.to_sym)
|
|
99
|
-
parsed[env.to_sym] || {}
|
|
100
|
-
elsif parsed.key?(env.to_s)
|
|
101
|
-
parsed[env.to_s] || {}
|
|
102
|
-
else
|
|
103
|
-
# No environment key, treat as flat config
|
|
104
|
-
parsed
|
|
105
|
-
end
|
|
106
|
-
rescue Psych::SyntaxError => e
|
|
107
|
-
warn "HTM: Failed to parse XDG config #{path}: #{e.message}"
|
|
108
|
-
{}
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Register the XDG loader with Anyway Config
|
|
115
|
-
# Insert before :yml so project-local config takes precedence
|
|
116
|
-
Anyway.loaders.insert_before :yml, :xdg, HTM::Loaders::XdgConfigLoader
|