Module: Ace::Support::Config

Defined in:
lib/ace/support/config.rb,
lib/ace/support/config/errors.rb,
lib/ace/support/config/version.rb,
lib/ace/support/config/models/config.rb,
lib/ace/support/config/atoms/deep_merger.rb,
lib/ace/support/config/atoms/yaml_parser.rb,
lib/ace/support/config/models/cascade_path.rb,
lib/ace/support/config/models/config_group.rb,
lib/ace/support/config/atoms/path_validator.rb,
lib/ace/support/config/molecules/yaml_loader.rb,
lib/ace/support/config/atoms/path_rule_matcher.rb,
lib/ace/support/config/molecules/config_finder.rb,
lib/ace/support/config/organisms/config_resolver.rb,
lib/ace/support/config/molecules/file_config_resolver.rb,
lib/ace/support/config/molecules/project_config_scanner.rb,
lib/ace/support/config/organisms/virtual_config_resolver.rb

Defined Under Namespace

Modules: Atoms, Models, Molecules, Organisms Classes: ConfigNotFoundError, Error, MergeStrategyError, PathError, YamlParseError

Constant Summary collapse

DEFAULT_CONFIG_DIR =

Default folder name for user configuration

".ace"
DEFAULT_DEFAULTS_DIR =

Default folder name for gem defaults

".ace-defaults"
DEFAULT_PROJECT_MARKERS =

Default project root markers

%w[
  .git
  Gemfile
  package.json
  Cargo.toml
  pyproject.toml
  go.mod
  .hg
  .svn
  Rakefile
  Makefile
].freeze
VERSION =
"0.9.0"

Class Method Summary collapse

Class Method Details

.create(config_dir: DEFAULT_CONFIG_DIR, defaults_dir: DEFAULT_DEFAULTS_DIR, gem_path: nil, merge_strategy: :replace, cache_namespaces: false, test_mode: nil, mock_config: nil) ⇒ Organisms::ConfigResolver

Create a new configuration resolver with customizable options

Examples:

Create with defaults

config = Ace::Support::Config.create
value = config.get("some", "key")

Create with custom folders

config = Ace::Support::Config.create(
  config_dir: ".my-app",
  defaults_dir: ".my-app-defaults",
  gem_path: File.expand_path("..", __dir__)
)

Create with namespace caching for performance

config = Ace::Support::Config.create(cache_namespaces: true)
config.resolve_namespace("my_gem")  # reads from disk
config.resolve_namespace("my_gem")  # returns cached result

Test mode with mock config

config = Ace::Support::Config.create(test_mode: true, mock_config: { "key" => "value" })
config.resolve.get("key")  # => "value"

Parameters:

  • config_dir (String) (defaults to: DEFAULT_CONFIG_DIR)

    User config folder name (default: “.ace”)

  • defaults_dir (String) (defaults to: DEFAULT_DEFAULTS_DIR)

    Gem defaults folder name (default: “.ace-defaults”)

  • gem_path (String, nil) (defaults to: nil)

    Optional gem root for defaults

  • merge_strategy (Symbol) (defaults to: :replace)

    Array merge strategy (:replace, :concat, :union)

  • cache_namespaces (Boolean) (defaults to: false)

    Whether to cache resolve_namespace results (default: false)

  • test_mode (Boolean, nil) (defaults to: nil)

    Force test mode on/off (default: nil = auto-detect)

  • mock_config (Hash, nil) (defaults to: nil)

    Mock config data for test mode (default: nil = use default_mock)

Returns:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ace/support/config.rb', line 167

def create(
  config_dir: DEFAULT_CONFIG_DIR,
  defaults_dir: DEFAULT_DEFAULTS_DIR,
  gem_path: nil,
  merge_strategy: :replace,
  cache_namespaces: false,
  test_mode: nil,
  mock_config: nil
)
  # Determine effective test mode
  effective_test_mode = test_mode.nil? ? test_mode? : test_mode

  Organisms::ConfigResolver.new(
    config_dir: config_dir,
    defaults_dir: defaults_dir,
    gem_path: gem_path,
    merge_strategy: merge_strategy,
    cache_namespaces: cache_namespaces,
    test_mode: effective_test_mode,
    mock_config: mock_config || default_mock
  )
end

.default_mockHash?

Thread-local mock configuration data to return in test mode

Returns:

  • (Hash, nil)

    Mock configuration data



99
100
101
# File 'lib/ace/support/config.rb', line 99

def default_mock
  Thread.current[:ace_config_default_mock]
end

.default_mock=(value) ⇒ Object

Set thread-local mock configuration data

Parameters:

  • value (Hash, nil)

    Mock configuration data



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

def default_mock=(value)
  Thread.current[:ace_config_default_mock] = value
end

.find_project_root(start_path: nil, markers: DEFAULT_PROJECT_MARKERS) ⇒ String?

Find project root from a starting path

Parameters:

  • start_path (String, nil) (defaults to: nil)

    Path to start searching from

  • markers (Array<String>) (defaults to: DEFAULT_PROJECT_MARKERS)

    Project root markers

Returns:

  • (String, nil)

    Project root path or nil



218
219
220
# File 'lib/ace/support/config.rb', line 218

def find_project_root(start_path: nil, markers: DEFAULT_PROJECT_MARKERS)
  Ace::Support::Fs::Molecules::ProjectRootFinder.find(start_path: start_path, markers: markers)
end

.finder(config_dir: DEFAULT_CONFIG_DIR, defaults_dir: DEFAULT_DEFAULTS_DIR, gem_path: nil) ⇒ Molecules::ConfigFinder

Create a configuration finder for lower-level access

Parameters:

  • config_dir (String) (defaults to: DEFAULT_CONFIG_DIR)

    Config folder name

  • defaults_dir (String) (defaults to: DEFAULT_DEFAULTS_DIR)

    Defaults folder name

  • gem_path (String, nil) (defaults to: nil)

    Gem root path

Returns:



196
197
198
199
200
201
202
# File 'lib/ace/support/config.rb', line 196

def finder(config_dir: DEFAULT_CONFIG_DIR, defaults_dir: DEFAULT_DEFAULTS_DIR, gem_path: nil)
  Molecules::ConfigFinder.new(
    config_dir: config_dir,
    defaults_dir: defaults_dir,
    gem_path: gem_path
  )
end

.path_expander(source_dir:, project_root:) ⇒ Ace::Support::Fs::Atoms::PathExpander

Create a path expander with explicit context

Parameters:

  • source_dir (String)

    Source document directory

  • project_root (String)

    Project root directory

Returns:

  • (Ace::Support::Fs::Atoms::PathExpander)

    Path expander instance



209
210
211
# File 'lib/ace/support/config.rb', line 209

def path_expander(source_dir:, project_root:)
  Ace::Support::Fs::Atoms::PathExpander.new(source_dir: source_dir, project_root: project_root)
end

.reset_config!void

This method returns an undefined value.

Reset all cached configuration state

Per ADR-022, this method allows test isolation by clearing all cached state in the configuration system.



269
270
271
272
273
# File 'lib/ace/support/config.rb', line 269

def reset_config!
  Ace::Support::Fs::Molecules::ProjectRootFinder.clear_cache!
  Thread.current[:ace_config_test_mode] = nil
  Thread.current[:ace_config_default_mock] = nil
end

.test_modeBoolean?

Thread-local test mode state Uses Thread.current for true thread isolation in parallel test environments

Returns:

  • (Boolean, nil)

    Whether test mode is enabled



87
88
89
# File 'lib/ace/support/config.rb', line 87

def test_mode
  Thread.current[:ace_config_test_mode]
end

.test_mode=(value) ⇒ Object

Set thread-local test mode state

Parameters:

  • value (Boolean, nil)

    Whether test mode is enabled



93
94
95
# File 'lib/ace/support/config.rb', line 93

def test_mode=(value)
  Thread.current[:ace_config_test_mode] = value
end

.test_mode?Boolean

Check if test mode is active

Test mode is active when:

  1. Ace::Support::Config.test_mode is explicitly set to true

  2. ACE_CONFIG_TEST_MODE environment variable is set to “1” or “true” (case-insensitive)

Note: We intentionally do NOT auto-detect based on Minitest being loaded, as that would break tests that need to test real filesystem access (like ace-config’s own tests). Use explicit opt-in instead.

Note: ENV lookup is intentionally NOT memoized to allow dynamic control of test_mode via environment variable changes at runtime.

Returns:

  • (Boolean)

    True if test mode is active



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ace/support/config.rb', line 123

def test_mode?
  # Note: test_mode (without ?) is the thread-local getter defined above,
  # not a recursive call - it reads from Thread.current[:ace_config_test_mode]
  return true if test_mode == true

  env_value = ENV["ACE_CONFIG_TEST_MODE"]
  return false if env_value.nil?
  return true if env_value == "1"
  return true if env_value.casecmp("true").zero?

  false
end

.virtual_resolver(config_dir: DEFAULT_CONFIG_DIR, defaults_dir: DEFAULT_DEFAULTS_DIR, start_path: nil, gem_path: nil) ⇒ Organisms::VirtualConfigResolver

Create a virtual config resolver for path-based lookups

VirtualConfigResolver provides a “virtual filesystem” view where nearest config file wins. Useful for finding presets and resources across the configuration cascade without loading/merging YAML content.

Examples:

Find preset files across cascade

resolver = Ace::Support::Config.virtual_resolver
resolver.glob("presets/*.yml").each do |relative, absolute|
  puts "Found: #{relative} at #{absolute}"
end

Check if resource exists with gem defaults

resolver = Ace::Support::Config.virtual_resolver(
  config_dir: ".my-app",
  gem_path: File.expand_path("..", __dir__)
)
if resolver.exists?("templates/default.md")
  path = resolver.resolve_path("templates/default.md")
end

Parameters:

  • config_dir (String) (defaults to: DEFAULT_CONFIG_DIR)

    Config folder name (default: “.ace”)

  • defaults_dir (String) (defaults to: DEFAULT_DEFAULTS_DIR)

    Defaults folder name (default: “.ace-defaults”)

  • start_path (String, nil) (defaults to: nil)

    Starting path for traversal (default: Dir.pwd)

  • gem_path (String, nil) (defaults to: nil)

    Gem root path for defaults (lowest priority)

Returns:



249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/ace/support/config.rb', line 249

def virtual_resolver(
  config_dir: DEFAULT_CONFIG_DIR,
  defaults_dir: DEFAULT_DEFAULTS_DIR,
  start_path: nil,
  gem_path: nil
)
  Organisms::VirtualConfigResolver.new(
    config_dir: config_dir,
    defaults_dir: defaults_dir,
    start_path: start_path,
    gem_path: gem_path
  )
end