Class: SmilyCli::Config

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

Overview

On-disk configuration: a YAML file holding one or more named profiles, each a bag of credentials/settings (token, base_url, refresh_token, client_id, client_secret, ...). Precedence between file, environment and flags lives in Context; this class only owns reading and writing the file.

File shape:

current_profile: default
profiles:
default:
  token: "..."
  base_url: "https://www.bookingsync.com"

Constant Summary collapse

KNOWN_KEYS =

Keys that are meaningful inside a profile. Unknown keys are preserved on save but this list drives validation and config set completion.

%w[
  token base_url refresh_token client_id client_secret redirect_uri
  scope account_id mcp_token mcp_url
].freeze
DEFAULT_PROFILE =
"default"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = self.class.default_path) ⇒ Config

Returns a new instance of Config.

Parameters:

  • path (String) (defaults to: self.class.default_path)


52
53
54
55
# File 'lib/smily_cli/config.rb', line 52

def initialize(path = self.class.default_path)
  @path = path
  @data = read
end

Instance Attribute Details

#pathString (readonly)

Returns absolute path to the config file backing this instance.

Returns:

  • (String)

    absolute path to the config file backing this instance



29
30
31
# File 'lib/smily_cli/config.rb', line 29

def path
  @path
end

Class Method Details

.default_pathString

Resolve the default config path, honoring SMILY_CONFIG (full path), then XDG_CONFIG_HOME, then ~/.config.

Returns:

  • (String)


35
36
37
38
39
40
41
# File 'lib/smily_cli/config.rb', line 35

def self.default_path
  return ENV["SMILY_CONFIG"] if ENV["SMILY_CONFIG"] && !ENV["SMILY_CONFIG"].empty?

  base = ENV.fetch("XDG_CONFIG_HOME", nil)
  base = File.join(Dir.home, ".config") if base.nil? || base.empty?
  File.join(base, "smily", "config.yml")
end

.load(path = default_path) ⇒ Config

Load configuration from path (missing file yields an empty config).

Parameters:

  • path (String) (defaults to: default_path)

Returns:



47
48
49
# File 'lib/smily_cli/config.rb', line 47

def self.load(path = default_path)
  new(path)
end

Instance Method Details

#current_profile_nameString

Name of the active profile: explicit current_profile or DEFAULT_PROFILE.

Returns:

  • (String)


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

def current_profile_name
  @data["current_profile"] || DEFAULT_PROFILE
end

#current_profile_name=(name) ⇒ Object

Parameters:

  • name (String)


69
70
71
# File 'lib/smily_cli/config.rb', line 69

def current_profile_name=(name)
  @data["current_profile"] = name.to_s
end

#delete_profile(name) ⇒ Hash?

Delete an entire profile.

Parameters:

  • name (String)

Returns:

  • (Hash, nil)

    the removed profile



114
115
116
# File 'lib/smily_cli/config.rb', line 114

def delete_profile(name)
  profiles.delete(name.to_s)
end

#exist?Boolean

Returns whether the backing file exists on disk.

Returns:

  • (Boolean)

    whether the backing file exists on disk



58
59
60
# File 'lib/smily_cli/config.rb', line 58

def exist?
  File.exist?(path)
end

#profile(name = current_profile_name) ⇒ Hash{String=>Object}

Fetch a profile's settings hash (never nil).

Parameters:

  • name (String, nil) (defaults to: current_profile_name)

    defaults to the current profile

Returns:

  • (Hash{String=>Object})


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

def profile(name = current_profile_name)
  profiles[name.to_s] || {}
end

#profile?(name = current_profile_name) ⇒ Boolean

Parameters:

  • name (String, nil) (defaults to: current_profile_name)

Returns:

  • (Boolean)


88
89
90
# File 'lib/smily_cli/config.rb', line 88

def profile?(name = current_profile_name)
  profiles.key?(name.to_s)
end

#profile_namesArray<String>

Returns sorted profile names present in the file.

Returns:

  • (Array<String>)

    sorted profile names present in the file



74
75
76
# File 'lib/smily_cli/config.rb', line 74

def profile_names
  profiles.keys.sort
end

#profilesHash{String=>Hash}

Returns the profiles map (mutable).

Returns:

  • (Hash{String=>Hash})

    the profiles map (mutable)



139
140
141
# File 'lib/smily_cli/config.rb', line 139

def profiles
  @data["profiles"] ||= {}
end

#saveString

Persist the config to #path, creating the directory and locking down permissions (the file holds secrets).

Returns:

  • (String)

    the path written



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/smily_cli/config.rb', line 122

def save
  dir = File.dirname(path)
  created_dir = !File.directory?(dir)
  FileUtils.mkdir_p(dir)
  # Only tighten a directory we created, so we never clobber the perms of a
  # pre-existing (possibly shared) parent.
  File.chmod(0o700, dir) if created_dir
  # Create with 0600 from the start so there is no world-readable window;
  # re-chmod covers a pre-existing file whose perms may have drifted.
  File.open(path, File::WRONLY | File::CREAT | File::TRUNC, 0o600) do |file|
    file.write(YAML.dump(prune(@data)))
  end
  File.chmod(0o600, path)
  path
end

#set(key, value, profile: current_profile_name) ⇒ void

This method returns an undefined value.

Set a single key on a profile (creating the profile if needed).

Parameters:

  • key (String, Symbol)
  • value (Object)
  • profile (String) (defaults to: current_profile_name)


98
99
100
101
# File 'lib/smily_cli/config.rb', line 98

def set(key, value, profile: current_profile_name)
  profiles[profile.to_s] ||= {}
  profiles[profile.to_s][key.to_s] = value
end

#unset(key, profile: current_profile_name) ⇒ Object?

Remove a key from a profile.

Returns:

  • (Object, nil)

    the removed value



106
107
108
# File 'lib/smily_cli/config.rb', line 106

def unset(key, profile: current_profile_name)
  (profiles[profile.to_s] || {}).delete(key.to_s)
end