Class: Ace::LLM::Atoms::XDGDirectoryResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/llm/atoms/xdg_directory_resolver.rb

Overview

XDGDirectoryResolver provides XDG Base Directory Specification compliant directory resolution for cache and data storage. This atom has no dependencies on other parts of this gem.

Constant Summary collapse

APP_NAME =

Application name used in directory paths

"ace-llm"
XDG_CACHE_HOME =

Environment variable names

"XDG_CACHE_HOME"
XDG_CONFIG_HOME =
"XDG_CONFIG_HOME"
XDG_DATA_HOME =
"XDG_DATA_HOME"
HOME =
"HOME"
DEFAULT_DIR_PERMISSIONS =

Default directory permissions

0o700

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env_reader = ENV) ⇒ XDGDirectoryResolver

Instance methods for non-static usage



142
143
144
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 142

def initialize(env_reader = ENV)
  @env_reader = env_reader
end

Class Method Details

.cache_directory(env_reader = ENV) ⇒ String

Resolve XDG-compliant cache directory path

Parameters:

  • env_reader (Hash, #[]) (defaults to: ENV)

    Environment variable source (defaults to ENV)

Returns:

  • (String)

    Absolute path to cache directory



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 28

def self.cache_directory(env_reader = ENV)
  xdg_cache_home = env_reader[XDG_CACHE_HOME]
  home_dir = env_reader[HOME]

  # Use XDG_CACHE_HOME if set and non-empty
  cache_base = if xdg_cache_home && !xdg_cache_home.strip.empty?
    File.expand_path(xdg_cache_home.strip)
  elsif home_dir && !home_dir.strip.empty?
    # Fall back to ~/.cache if HOME is available
    File.expand_path(".cache", home_dir.strip)
  else
    # Last resort: use current directory
    File.expand_path(".cache")
  end

  File.join(cache_base, APP_NAME)
end

.cache_subdirectory(cache_type, env_reader = ENV) ⇒ String

Get cache subdirectory path for specific cache type

Parameters:

  • cache_type (String)

    Type of cache (e.g., ‘models’, ‘http’, ‘pricing’)

  • env_reader (Hash, #[]) (defaults to: ENV)

    Environment variable source (defaults to ENV)

Returns:

  • (String)

    Path to cache subdirectory



102
103
104
105
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 102

def self.cache_subdirectory(cache_type, env_reader = ENV)
  base_cache_dir = cache_directory(env_reader)
  File.join(base_cache_dir, cache_type.to_s)
end

.config_directory(env_reader = ENV) ⇒ String

Resolve XDG-compliant config directory path

Parameters:

  • env_reader (Hash, #[]) (defaults to: ENV)

    Environment variable source (defaults to ENV)

Returns:

  • (String)

    Absolute path to config directory



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 49

def self.config_directory(env_reader = ENV)
  xdg_config_home = env_reader[XDG_CONFIG_HOME]
  home_dir = env_reader[HOME]

  # Use XDG_CONFIG_HOME if set and non-empty
  config_base = if xdg_config_home && !xdg_config_home.strip.empty?
    File.expand_path(xdg_config_home.strip)
  elsif home_dir && !home_dir.strip.empty?
    # Fall back to ~/.config if HOME is available
    File.expand_path(".config", home_dir.strip)
  else
    # Last resort: use current directory
    File.expand_path(".config")
  end

  File.join(config_base, APP_NAME)
end

.data_directory(env_reader = ENV) ⇒ String

Resolve XDG-compliant data directory path

Parameters:

  • env_reader (Hash, #[]) (defaults to: ENV)

    Environment variable source (defaults to ENV)

Returns:

  • (String)

    Absolute path to data directory



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 70

def self.data_directory(env_reader = ENV)
  xdg_data_home = env_reader[XDG_DATA_HOME]
  home_dir = env_reader[HOME]

  # Use XDG_DATA_HOME if set and non-empty
  data_base = if xdg_data_home && !xdg_data_home.strip.empty?
    File.expand_path(xdg_data_home.strip)
  elsif home_dir && !home_dir.strip.empty?
    # Fall back to ~/.local/share if HOME is available
    File.expand_path(".local/share", home_dir.strip)
  else
    # Last resort: use current directory
    File.expand_path(".local/share")
  end

  File.join(data_base, APP_NAME)
end

.ensure_cache_subdirectory(cache_type, env_reader = ENV, permissions = DEFAULT_DIR_PERMISSIONS) ⇒ String

Resolve and ensure cache subdirectory exists

Parameters:

  • cache_type (String)

    Type of cache

  • env_reader (Hash, #[]) (defaults to: ENV)

    Environment variable source (defaults to ENV)

  • permissions (Integer) (defaults to: DEFAULT_DIR_PERMISSIONS)

    Directory permissions (default: 0700)

Returns:

  • (String)

    Path to existing cache subdirectory



112
113
114
115
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 112

def self.ensure_cache_subdirectory(cache_type, env_reader = ENV, permissions = DEFAULT_DIR_PERMISSIONS)
  subdir_path = cache_subdirectory(cache_type, env_reader)
  ensure_directory(subdir_path, permissions)
end

.ensure_directory(directory, permissions = DEFAULT_DIR_PERMISSIONS) ⇒ String

Ensure directory exists with proper permissions

Parameters:

  • directory (String)

    Directory path

  • permissions (Integer) (defaults to: DEFAULT_DIR_PERMISSIONS)

    Directory permissions (default: 0700)

Returns:

  • (String)

    The created directory path

Raises:

  • (SystemCallError)

    If directory cannot be created



93
94
95
96
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 93

def self.ensure_directory(directory, permissions = DEFAULT_DIR_PERMISSIONS)
  FileUtils.mkdir_p(directory, mode: permissions)
  directory
end

.safe_directory_path?(path) ⇒ Boolean

Validate directory path for security

Parameters:

  • path (String)

    Directory path to validate

Returns:

  • (Boolean)

    True if path is safe to use



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 120

def self.safe_directory_path?(path)
  return false if path.nil? || path.empty?

  # Reject paths with null bytes (security concern)
  return false if path.include?("\0")

  # Reject paths with parent directory traversal attempts
  return false if path.include?("..")

  # Must be absolute path after expansion
  begin
    expanded = File.expand_path(path)
    pathname = Pathname.new(expanded)
    return false unless pathname.absolute?
  rescue
    return false
  end

  true
end

Instance Method Details

#cache_directoryString

Instance method to get cache directory

Returns:

  • (String)

    Cache directory path



148
149
150
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 148

def cache_directory
  self.class.cache_directory(@env_reader)
end

#cache_subdirectory(cache_type) ⇒ String

Instance method to get cache subdirectory

Parameters:

  • cache_type (String)

    Type of cache

Returns:

  • (String)

    Path to cache subdirectory



175
176
177
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 175

def cache_subdirectory(cache_type)
  self.class.cache_subdirectory(cache_type, @env_reader)
end

#config_directoryString

Instance method to get config directory

Returns:

  • (String)

    Config directory path



154
155
156
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 154

def config_directory
  self.class.config_directory(@env_reader)
end

#data_directoryString

Instance method to get data directory

Returns:

  • (String)

    Data directory path



160
161
162
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 160

def data_directory
  self.class.data_directory(@env_reader)
end

#ensure_cache_subdirectory(cache_type, permissions = DEFAULT_DIR_PERMISSIONS) ⇒ String

Instance method to ensure cache subdirectory exists

Parameters:

  • cache_type (String)

    Type of cache

  • permissions (Integer) (defaults to: DEFAULT_DIR_PERMISSIONS)

    Directory permissions (default: 0700)

Returns:

  • (String)

    Path to existing cache subdirectory



183
184
185
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 183

def ensure_cache_subdirectory(cache_type, permissions = DEFAULT_DIR_PERMISSIONS)
  self.class.ensure_cache_subdirectory(cache_type, @env_reader, permissions)
end

#ensure_directory(directory, permissions = DEFAULT_DIR_PERMISSIONS) ⇒ String

Instance method to ensure directory exists

Parameters:

  • directory (String)

    Directory path

  • permissions (Integer) (defaults to: DEFAULT_DIR_PERMISSIONS)

    Directory permissions (default: 0700)

Returns:

  • (String)

    The created directory path



168
169
170
# File 'lib/ace/llm/atoms/xdg_directory_resolver.rb', line 168

def ensure_directory(directory, permissions = DEFAULT_DIR_PERMISSIONS)
  self.class.ensure_directory(directory, permissions)
end