Class: Aspera::Cli::PresetManager

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/cli/preset_manager.rb

Overview

Manages the YAML config file and all preset-related operations. Extracted from Plugins::Config so it can be referenced independently via Context#presets without coupling to the plugin machinery.

Defined Under Namespace

Modules: Key

Constant Summary collapse

GLOBAL_DEFAULT_KEYWORD =
'GLOBAL'
CONF_GLOBAL_SYM =
:config

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_file:, use_plugin_defaults: true) ⇒ PresetManager

Returns a new instance of PresetManager.

Parameters:

  • config_file (String)

    absolute path to the YAML config file

  • use_plugin_defaults (Boolean) (defaults to: true)

    if false, skip default preset lookup



34
35
36
37
38
39
40
# File 'lib/aspera/cli/preset_manager.rb', line 34

def initialize(config_file:, use_plugin_defaults: true)
  @config_file         = config_file
  @use_plugin_defaults = use_plugin_defaults
  @config_presets      = {}
  @checksum_on_disk    = nil
  read_config_file
end

Instance Attribute Details

#config_presetsObject (readonly)

Returns the value of attribute config_presets.



42
43
44
# File 'lib/aspera/cli/preset_manager.rb', line 42

def config_presets
  @config_presets
end

#use_plugin_defaultsObject

Returns the value of attribute use_plugin_defaults.



42
43
44
# File 'lib/aspera/cli/preset_manager.rb', line 42

def use_plugin_defaults
  @use_plugin_defaults
end

Class Method Details

.deep_clone(val) ⇒ Object



208
209
210
# File 'lib/aspera/cli/preset_manager.rb', line 208

def deep_clone(val)
  Marshal.load(Marshal.dump(val))
end

Instance Method Details

#by_name(config_name, include_path = []) ⇒ Object

Resolve and return a deep-cloned, extended copy of a named preset. Supports dot-notation (e.g. "outer.inner").

Parameters:

  • config_name (String)
  • include_path (Array) (defaults to: [])

    guard against include loops

Raises:



125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/aspera/cli/preset_manager.rb', line 125

def by_name(config_name, include_path = [])
  raise Cli::Error, 'loop in include' if include_path.include?(config_name)
  include_path = include_path.clone
  current = @config_presets
  config_name.split(PRESET_DIG_SEPARATOR).each do |name|
    Aspera.assert_type(current, Hash, type: Cli::Error){"sub key: #{include_path}"}
    include_path.push(name)
    current = current[name]
    raise Cli::Error, "Unknown config preset: #{include_path}" if current.nil?
  end
  current = self.class.deep_clone(current) unless current.is_a?(String)
  ExtendedValue.instance.evaluate(current, context: 'preset')
end

#checksumString

Returns SHA1 of current in-memory presets.

Returns:

  • (String)

    SHA1 of current in-memory presets



50
51
52
# File 'lib/aspera/cli/preset_manager.rb', line 50

def checksum
  Digest::SHA1.hexdigest(JSON.generate(@config_presets))
end

#defaults_set(plugin_name, preset_name, preset_values, option_default, option_override) ⇒ Object

Create / overwrite a preset and optionally set it as plugin default.

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/aspera/cli/preset_manager.rb', line 177

def defaults_set(plugin_name, preset_name, preset_values, option_default, option_override)
  @config_presets[Key::DEFAULTS] ||= {}
  raise Cli::Error, "A default configuration already exists for plugin '#{plugin_name}' (use --override=yes or --default=no)" \
    if !option_override && option_default && @config_presets[Key::DEFAULTS].key?(plugin_name)
  raise Cli::Error, "Preset already exists: #{preset_name}  (use --override=yes or provide alternate name on command line)" \
    if !option_override && @config_presets.key?(preset_name)
  if option_default
    Log.log.info("Setting config preset as default for #{plugin_name}")
    @config_presets[Key::DEFAULTS][plugin_name.to_s] = preset_name
  end
  @config_presets[preset_name] = preset_values
end

#global_default_presetString

Returns name of the global default preset, creating it if needed.

Returns:

  • (String)

    name of the global default preset, creating it if needed



162
163
164
165
166
167
168
169
# File 'lib/aspera/cli/preset_manager.rb', line 162

def global_default_preset
  result = plugin_default_name(CONF_GLOBAL_SYM)
  if result.nil?
    result = Key::GLOBAL
    set_key(Key::DEFAULTS, CONF_GLOBAL_SYM, result)
  end
  result
end

#lookup_preset(url:, username:) ⇒ Hash?

Find the first preset whose url+username match

Returns:



192
193
194
195
196
197
198
199
200
201
# File 'lib/aspera/cli/preset_manager.rb', line 192

def lookup_preset(url:, username:)
  url = canonical_url(url)
  Log.log.debug{"Lookup preset for #{username}@#{url}"}
  @config_presets.each_value do |v|
    next unless v.is_a?(Hash)
    conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
    return self.class.deep_clone(v) if conf_url.eql?(url) && v['username'].eql?(username)
  end
  nil
end

#plugin_default_name(plugin_name_sym) ⇒ String?

Returns name of the default preset for a plugin, or nil.

Returns:

  • (String, nil)

    name of the default preset for a plugin, or nil



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/aspera/cli/preset_manager.rb', line 101

def plugin_default_name(plugin_name_sym)
  Aspera.assert(!@config_presets.nil?, 'config_presets shall be defined')
  return nil unless @use_plugin_defaults
  return nil unless @config_presets.key?(Key::DEFAULTS)
  Aspera.assert_type(@config_presets[Key::DEFAULTS], Hash){'default section'}
  return nil unless @config_presets[Key::DEFAULTS].key?(plugin_name_sym.to_s)
  default_name = @config_presets[Key::DEFAULTS][plugin_name_sym.to_s]
  unless @config_presets.key?(default_name)
    Log.log.error do
      "Default config name [#{default_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
        "Please fix: either create preset:\n" \
        "#{Info::CMD_NAME} config id #{default_name} init @json:'{}'\n" \
        "or remove default:\n#{Info::CMD_NAME} config id default remove #{plugin_name_sym}"
    end
    raise Cli::Error, "No such preset: #{default_name}"
  end
  Aspera.assert_type(@config_presets[default_name], Hash, type: Cli::Error){'preset type'}
  default_name
end

#read_config_fileObject

Read and validate the YAML config file. Sets @config_presets and @checksum_on_disk.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/aspera/cli/preset_manager.rb', line 56

def read_config_file
  Log.log.debug{"config file is: #{@config_file}".red}
  if File.exist?(@config_file)
    Log.log.debug{"loading #{@config_file}"}
    @config_presets   = YAML.load_file(@config_file)
    @checksum_on_disk = checksum
  else
    Log.log.warn{"No config file found. New configuration file: #{@config_file}"}
    @config_presets = {Key::CONFIG => {Key::VERSION => 'new file'}}
    # @checksum_on_disk remains nil → will be saved on first write
  end
  validate_config_presets!
rescue Psych::SyntaxError => e
  Log.log.error('YAML error in config file')
  raise e
rescue StandardError => e
  Log.log.debug{"-> #{e.class.name} : #{e}"}
  if File.exist?(@config_file)
    new_name = "#{@config_file}.pre#{Cli::VERSION}.manual_conversion_needed"
    File.rename(@config_file, new_name)
    Log.log.warn{"Renamed config file to #{new_name}."}
    Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
  end
  raise Cli::Error, e.to_s
end

#save_if_neededBoolean

Save to disk only if content changed since last load/save.

Returns:

  • (Boolean)

    true if actually written

Raises:



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/aspera/cli/preset_manager.rb', line 84

def save_if_needed
  raise Cli::Error, 'no configuration loaded' if @config_presets.nil?
  current = checksum
  return false if @checksum_on_disk.eql?(current)
  FileUtils.mkdir_p(File.dirname(@config_file))
  Environment.restrict_file_access(File.dirname(@config_file))
  Log.log.info{"Saving config file: #{@config_file}"}
  Environment.write_file_restricted(@config_file, force: true){ @config_presets.to_yaml }
  @checksum_on_disk = current
  true
end

#set_global_default(key, value) ⇒ Object

Set param in the global defaults preset



172
173
174
# File 'lib/aspera/cli/preset_manager.rb', line 172

def set_global_default(key, value)
  set_key(global_default_preset, key, value)
end

#set_key(preset, param_name, param_value) ⇒ Object

Set a single key in a preset hash (creates the preset if absent).



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/aspera/cli/preset_manager.rb', line 140

def set_key(preset, param_name, param_value)
  Aspera.assert_type(param_name, String, Symbol){'parameter'}
  param_name = param_name.to_s
  selected = @config_presets[preset]
  if selected.nil?
    Log.log.debug{"Unknown preset name: #{preset}, initializing"}
    selected = @config_presets[preset] = {}
  end
  Aspera.assert_type(selected, Hash){"#{preset}.#{param_name}"}
  if selected.key?(param_name)
    if selected[param_name].eql?(param_value)
      Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
      return
    end
    Log.log.warn{"overwriting value for #{param_name}: #{selected[param_name]}"}
  end
  selected[param_name] = param_value
  Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
  nil
end