class Doing::Configuration
Configuration
object
Constants
- DEFAULTS
- MissingConfigFile
Attributes
Public Class Methods
# File lib/doing/configuration.rb, line 121 def initialize(file = nil, options: {}) @config_file = file.nil? ? default_config_file : File.expand_path(file) @settings = configure(options) end
Public Instance Methods
# File lib/doing/configuration.rb, line 160 def additional_configs @additional_configs ||= find_local_config end
Present a menu if there are multiple configs found
@return [String] file path
# File lib/doing/configuration.rb, line 169 def choose_config(create: false, local: false) if local && create res = File.expand_path('.doingrc') FileUtils.touch(res) return res end return @config_file if @force_answer if @additional_configs&.count&.positive? || create choices = [@config_file].concat(@additional_configs) choices.push('Create a new .doingrc in the current directory') if create && !File.exist?('.doingrc') res = Doing::Prompt.choose_from(choices.uniq.sort.reverse, sorted: false, prompt: 'Local configs found, select which to update > ') raise UserCancelled, 'Cancelled' unless res if res =~ /^Create a new/ res = File.expand_path('.doingrc') FileUtils.touch(res) end res.strip || @config_file else @config_file end end
# File lib/doing/configuration.rb, line 131 def config_dir @config_dir ||= File.join(Util.user_home, '.config', 'doing') end
# File lib/doing/configuration.rb, line 127 def config_file @config_file ||= default_config_file end
Read user configuration and merge with defaults
@param opt [Hash] Additional Options
# File lib/doing/configuration.rb, line 338 def configure(opt = {}) update_deprecated_config if config_file == default_config_file @ignore_local = opt[:ignore_local] if opt[:ignore_local] config = read_config.clone plugin_config = Util.deep_merge_hashes(DEFAULTS['plugins'], config['plugins'] || {}) load_plugins(plugin_config['plugin_path']) Plugins.plugins.each do |_type, plugins| plugins.each do |title, plugin| plugin_config[title] = plugin[:config] if plugin[:config].good? config['export_templates'][title] ||= nil if plugin[:templates] && !plugin[:templates].empty? end end config = Util.deep_merge_hashes({ 'plugins' => plugin_config }, config) config = find_deprecations(config) if !File.exist?(config_file) || opt[:rewrite] Util.write_to_file(config_file, YAML.dump(config), backup: true) Doing.logger.warn('Config:', "Config file written to #{config_file}") end Hooks.trigger :post_config, self config = local_config.deep_merge(config, { extend_existing_arrays: true, sort_merged_arrays: true }) unless @ignore_local # config = Util.deep_merge_hashes(config, local_config) unless @ignore_local Hooks.trigger :post_local_config, self config end
# File lib/doing/configuration.rb, line 146 def default_config_file if File.exist?(config_dir) && !File.directory?(config_dir) raise DoingRuntimeError, "#{config_dir} exists but is not a directory" end unless File.exist?(config_dir) FileUtils.mkdir_p(config_dir) Doing.logger.log_now(:warn, "Config directory created at #{config_dir}") end File.join(config_dir, 'config.yml') end
Check if configuration enforces exact string matching
@return [Boolean] exact matching enabled
# File lib/doing/configuration.rb, line 140 def exact_match? search_settings = @settings['search'] matching = search_settings.fetch('matching', 'pattern').normalize_matching matching == :exact end
# File lib/doing/configuration.rb, line 198 def fetch(*path, default) @settings.dig(*path) || default end
# File lib/doing/configuration.rb, line 12 def force_answer @force_answer ||= false end
It takes the input, fills in the defaults where values do not exist.
@param user_config a Hash
or Configuration
of overrides.
@return [Hash] a Configuration
filled with defaults.
# File lib/doing/configuration.rb, line 296 def from(user_config) # Util.deep_merge_hashes(DEFAULTS, Configuration[user_config].stringify_keys) Configuration[user_config].stringify_keys.deep_merge(DEFAULTS, { extend_existing_arrays: true, sort_merged_arrays: true }) end
@private
# File lib/doing/configuration.rb, line 399 def inspect %(<Doing::Configuration #{@settings.hash}>) end
Resolve a fuzzy-matched key path
@param keypath [String] A dot-separated key path, e.g. “plugins.plugin_path”. Will also work with “plug.path” (fuzzy matched, first match wins) @return [Array] ordered array of resolved keys
# File lib/doing/configuration.rb, line 212 def resolve_key_path(keypath, create: false, distance: 2, exact: false) cfg = @settings real_path = [] unless keypath =~ /^[.*]?$/ paths = keypath.split(/[:.]/) element_count = paths.count while paths.length.positive? && !cfg.nil? path = paths.shift new_cfg = nil if cfg.is_a?(Hash) matches = if exact cfg.select { |key, _| key == path } else cfg.select { |key, _| key =~ path.to_rx(distance: distance) } end if matches.count.positive? shortest = matches.keys.group_by(&:length).min.last[0] real_path << shortest new_cfg = matches[shortest] end else new_cfg = cfg end if new_cfg.nil? return real_path if real_path[-1] == path && real_path.count == element_count if distance < 5 && !create return resolve_key_path(keypath, create: false, distance: distance + 1) else return nil unless create end resolved = real_path.count.positive? ? "Resolved #{real_path.join('.')}, but " : '' Doing.logger.log_now(:warn, "#{resolved}#{path} is unknown") new_path = [*real_path, path, *paths].compact.join('.') Doing.logger.log_now(:warn, "Continuing will create the path #{new_path}") res = Prompt.yn('Key path not found, create it?', default_response: true) raise InvalidArgument, 'Invalid key path' unless res real_path.push(path).concat(paths).compact! Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}") unless keypath == real_path.join('.') return real_path end cfg = new_cfg end end Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}") unless keypath == real_path.join('.') real_path end
Save a set of options to the views
configuration
@param view [Hash] view options @param title [String] view title
# File lib/doing/configuration.rb, line 383 def save_view(view, title) title.downcase! default_template = Doing.setting('templates.default') user_config = Util.safe_load_file(config_file) user_config['views'] = {} unless user_config.key?('views') view.delete_if { |k, v| v == default_template[k] } user_config['views'][title] = view Util.write_to_file(config_file, YAML.dump(user_config), backup: true) Doing.logger.warn('Config:', %(View "#{title}" saved to #{config_file})) Doing.logger.info('Config:', %(to use, run `doing view #{title}`)) Hooks.trigger :post_config, self end
@private
# File lib/doing/configuration.rb, line 404 def to_s YAML.dump(@settings) end
Method for transitioning from ~/.doingrc to ~/.config/doing/config.yml
# File lib/doing/configuration.rb, line 304 def update_deprecated_config # return # Until further notice return if File.exist?(default_config_file) old_file = File.join(Util.user_home, '.doingrc') return unless File.exist?(old_file) Doing.logger.log_now(:warn, 'Deprecated:', "main config file location has changed to #{config_file}") res = Prompt.yn("Move #{old_file} to new location, preserving settings?", default_response: true) return unless res if File.exist?(default_config_file) res = Prompt.yn("#{default_config_file} already exists, overwrite it?", default_response: false) unless res @config_file = old_file return end end FileUtils.mv old_file, default_config_file, force: true Doing.logger.log_now(:warn, 'Config:', "Config file moved to #{default_config_file}") Doing.logger.log_now(:warn, 'Config:', %(If ~/.doingrc exists in the future, it will be considered a local config and its values will override the default configuration.)) Process.exit 0 end
Get the value for a fuzzy-matched key path
@param keypath [String] A dot-separated key path, e.g. “plugins.plugin_path”. Will also work with “plug.path” (fuzzy matched, first match wins) @return [Hash] Config value
# File lib/doing/configuration.rb, line 274 def value_for_key(keypath = '') cfg = @settings real_path = ['config'] unless keypath =~ /^[.*]?$/ real_path = resolve_key_path(keypath, create: false) return nil unless real_path&.count&.positive? cfg = cfg.dig(*real_path) end cfg.nil? ? nil : { real_path[-1] => cfg } end
Private Instance Methods
Test for deprecated config keys
@param config The configuration
# File lib/doing/configuration.rb, line 415 def find_deprecations(config) deprecated = false if config.key?('editor') deprecated = true config['editors']['default'] ||= config['editor'] config.delete('editor') Doing.logger.debug('Deprecated:', "config key 'editor' is now 'editors.default', please update your config.") end if config.key?('config_editor_app') && !config['editors']['config'] deprecated = true config['editors']['config'] = config['config_editor_app'] config.delete('config_editor_app') Doing.logger.debug('Deprecated:', "config key 'config_editor_app' is now 'editors.config', please update your config.") end if config.key?('editor_app') && !config['editors']['doing_file'] deprecated = true config['editors']['doing_file'] = config['editor_app'] config.delete('editor_app') Doing.logger.debug('Deprecated:', "config key 'editor_app' is now 'editors.doing_file', please update your config.") end Doing.logger.warn('Deprecated:', 'outdated keys found, please run `doing config --update`.') if deprecated config end
Finds a project-specific configuration file
@return [String] A file path
# File lib/doing/configuration.rb, line 512 def find_local_config dir = Dir.pwd local_config_files = [] while dir != '/' && (dir =~ %r{[A-Z]:/}).nil? local_config_files.push(File.join(dir, '.doingrc')) if File.exist? File.join(dir, '.doingrc') dir = File.dirname(dir) end local_config_files.delete(config_file) local_config_files end
# File lib/doing/configuration.rb, line 528 def load_plugins(add_dir = nil) FileUtils.mkdir_p(add_dir) if add_dir && !File.exist?(add_dir) Plugins.load_plugins(add_dir) end
Read local configurations
@return Hash
of config options
# File lib/doing/configuration.rb, line 449 def local_config return {} if @ignore_local local_configs = read_local_configs || {} if additional_configs&.count file_list = additional_configs.map { |p| p.sub(/^#{Util.user_home}/, '~') }.join(', ') Doing.logger.debug('Config:', "Local config files found: #{file_list}") end local_configs end
Reads a configuration.
# File lib/doing/configuration.rb, line 479 def read_config unless File.exist?(config_file) Doing.logger.info('Config:', 'Config file doesn\'t exist, using default configuration') return {}.deep_merge(DEFAULTS) end begin user_config = Util.safe_load_file(config_file) raise StandardError, 'Invalid config file format' unless user_config.is_a?(Hash) if user_config.key?('html_template') user_config['export_templates'] ||= {} user_config['export_templates'].deep_merge(user_config.delete('html_template'), { extend_existing_arrays: true, sort_merged_arrays: true }) end user_config['include_notes'] = user_config.delete(':include_notes') if user_config.key?(':include_notes') user_config.deep_merge(DEFAULTS, { extend_existing_arrays: true, sort_merged_arrays: true }) rescue StandardError => e Doing.logger.error('Config:', 'Error reading default configuration') Doing.logger.error('Error:', e.message) user_config = DEFAULTS end user_config end
# File lib/doing/configuration.rb, line 462 def read_local_configs local_configs = {} begin additional_configs.each do |cfg| local_configs.deep_merge(Util.safe_load_file(cfg), { extend_existing_arrays: true, sort_merged_arrays: true }) end rescue StandardError Doing.logger.error('Config:', 'Error reading local configuration(s)') end local_configs end