rbcli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73760c480505d19af6bf3bbf67df643902f079fe2b5c22cc2368111894ce2da5
4
- data.tar.gz: 4fbbd0ca18a2cf4b0dc79bb5b2582ddc2730d2d9944147e90cc8255f20aa92c8
3
+ metadata.gz: bad65e9d8cefa0a82adb9305a68294eeaed6cfefed1d06be9cce6edb5eb2a2eb
4
+ data.tar.gz: 6add98a08da1c55f18e87e60bb279a31adad0979b31d3b00745b9016ab5870d5
5
5
  SHA512:
6
- metadata.gz: d1e5a99c761cf3bb6ef81fd8877cfcb16d447a60a595189ecaeb8d41f054a6ea3d836d43a8e1776edc6e919f773f673aa1ec61c92b1829d4bdb4a741a20a87c5
7
- data.tar.gz: ead35dca33292b7c2cbd591267fe18807932e6d5f98a2f284b4699743c05aefe852af1d94a3507e41ca65aa36ef9b9bb453572a5eb66198b5d04c120907b3fe7
6
+ metadata.gz: af5e0383b0bf15283007c3e9e45470b5925037e5d73ae9c6ce4fb329ecf3ef983321e4e23bdcbc761676ce79e1adb48b151364067bbc5130e15adc789ed663ce
7
+ data.tar.gz: 01e6bba1b239653d59cb8e3b4c7c9e6c9ee33efe28ea50aa11cdafd55bcebdab433dc17082464f65ee38ee1290b2f40f39543272a5604a1f5e93d25f99e01c27
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.4.2
@@ -92,7 +92,7 @@ module Rbcli
92
92
  unless command.has_function?
93
93
  raise Rbcli::CommandError.new "Command #{command.name} does not have a function. An action, inline script, or external script must be defined."
94
94
  end
95
- if command.default? && Rbcli::Warehouse.get(:commands).any? { |cmd| cmd.default? }
95
+ if command.default? && Rbcli::Warehouse.get(:commands).any? { |_k, cmd| cmd.default? }
96
96
  raise Rbcli::CommandError.new "Only one command may be assigned as default"
97
97
  end
98
98
  Rbcli::Warehouse.get(:commands)[name] = command
@@ -46,10 +46,14 @@ Rbcli.command "<%= @command_name || 'example' %>" do
46
46
  Rbcli.log.unknown "Example Unknown Message", "MYAPP"
47
47
  Rbcli.log.info ""
48
48
 
49
+ config.add_default :foo, 'bar'
50
+ config.create!
51
+ config.load!
49
52
  config[:davey] = 'Davey Jones'.compress
50
53
  Rbcli.log.info "Compressed String: " + config[:davey]
51
54
  Rbcli.log.info "Decompressed String: " + config[:davey].decompress
52
55
  config.save!
56
+ Rbcli.log.info config
53
57
  <%- end -%>
54
58
  end
55
59
  end
@@ -27,7 +27,7 @@ class Rbcli::UserConf::Backend
27
27
  end
28
28
 
29
29
  def self.create filename, type: nil
30
- type ||= self.types.map { |slug, check| [slug, check.call(filename)] }.to_h.select { |slug, match| match }.first.first
30
+ type ||= self.types.map { |slug, check| [slug, check.call(filename)] }.select { |slug, match| match }.first.first
31
31
  type = type.to_s.downcase.to_sym
32
32
  require_relative "backends/#{type.to_s}"
33
33
  @registered_types[type].new(filename, type)
@@ -43,8 +43,8 @@ class Rbcli::UserConf::Backend
43
43
  attr_reader :type, :loaded
44
44
  alias_method :loaded?, :loaded
45
45
 
46
- # The defaults: parameter is used on some backends to know which fields to expect and parse
47
- def load defaults: nil
46
+ # The `defaults` parameter is used on some backends which override this method
47
+ def load _defaults = nil
48
48
  begin
49
49
  text = File.read(@path)
50
50
  rescue Errno::ENOENT => _
@@ -68,6 +68,19 @@ class Rbcli::UserConf::Backend
68
68
  end
69
69
  end
70
70
 
71
+ def save_raw text
72
+ Rbcli.log.debug "Saving #{@type} config file at '#{@path}' using raw text", "CONF"
73
+ begin
74
+ File.write(@path, text)
75
+ rescue Errno => err
76
+ Rbcli.log.error "Could not save config to file at '#{@path}'", "CONF"
77
+ Rbcli.log.error "Error: #{err.message}", "CONF"
78
+ false
79
+ else
80
+ true
81
+ end
82
+ end
83
+
71
84
  def savable?
72
85
  File.writable?(File.exist?(@path) ? @path : File.dirname(@path))
73
86
  end
@@ -76,17 +89,18 @@ class Rbcli::UserConf::Backend
76
89
  File.exist?(@path)
77
90
  end
78
91
 
79
- def annotate! defaults
92
+ def annotate! banner
93
+ return true unless self.respond_to?(:inject_banner) || self.private_methods.include?(:inject_banner)
94
+
80
95
  begin
81
96
  text = File.read(@path)
82
97
  rescue Errno::ENOENT => _
83
98
  Rbcli.log.debug "Attempted to annotate #{@type} config file but did not find it at '#{@path}'", "CONF"
84
99
  return nil
85
100
  end
86
- Rbcli.log.debug "Annotating #{@type} config file at '#{@path}'", "CONF"
87
101
 
88
- text = self.inject_banner(text, defaults[:helptext])
89
- File.write(@path, text)
102
+ Rbcli.log.debug "Annotating #{@type} config file at '#{@path}'", "CONF"
103
+ text = self.inject_banner(text, banner)
90
104
 
91
105
  begin
92
106
  File.write(@path, text)
@@ -14,7 +14,7 @@ class Rbcli::UserConf::Env < Rbcli::UserConf::Backend
14
14
  @loaded = false
15
15
  end
16
16
 
17
- def load defaults: nil
17
+ def load defaults = nil
18
18
  vars = ENV.select { |k, v| k.match(/^#{@prefix}_.+/i) && !k.match(/^_/) }
19
19
  if @prefix.nil? && !defaults.nil? && !defaults.empty?
20
20
  lowercase_default_keys = defaults.keys.map { |k| k.downcase.to_sym }
@@ -25,10 +25,6 @@ class Rbcli::UserConf::Env < Rbcli::UserConf::Backend
25
25
  vars.each do |key, value|
26
26
  deep_assign(final_vars, key.split('_').map { |k| k.to_sym }, translate_value(value))
27
27
  end
28
- if @prefix.nil? && !defaults.nil? && !defaults.empty?
29
- lowercase_default_keys = defaults.keys.map { |k| k.downcase.to_sym }
30
- vars = ENV.select { |k, v| lowercase_default_keys.include?(k.downcase.to_sym) }.map { |k, v| [k.downcase.to_sym, v] }.to_h
31
- end
32
28
  @loaded = true
33
29
  final_vars
34
30
  end
@@ -43,6 +39,10 @@ class Rbcli::UserConf::Env < Rbcli::UserConf::Backend
43
39
  end
44
40
  end
45
41
 
42
+ def save_raw text
43
+ true
44
+ end
45
+
46
46
  def parse_defaults defaults
47
47
  final_vars = Hash.new
48
48
  defaults.each do |key, value|
@@ -60,8 +60,4 @@ class Rbcli::UserConf::Env < Rbcli::UserConf::Backend
60
60
  def exist?
61
61
  true
62
62
  end
63
-
64
- def annotate! _defaults = nil
65
- true
66
- end
67
63
  end
@@ -22,8 +22,7 @@ class Rbcli::UserConf::Ini < Rbcli::UserConf::Backend
22
22
  key, value = line.split('=').map { |part| part.strip }
23
23
  deep_assign(ini, nest + [key], translate_value(value))
24
24
  else
25
- Rbcli.log.warn "Invalid #{@type} syntax found on line #{i} at '#{@path}'. Offending syntax: #{line}", "CONF"
26
- return Hash.new
25
+ raise Rbcli::ParseError.new "Invalid #{@type} syntax found on line #{i} at '#{@path}'. Offending syntax: #{line}"
27
26
  end
28
27
  end
29
28
  @loaded = true
@@ -12,9 +12,7 @@ class Rbcli::UserConf::Json < Rbcli::UserConf::Backend
12
12
  begin
13
13
  parsed_str = JSON.parse(str)
14
14
  rescue JSON::JSONError => e
15
- Rbcli.log.warn "Invalid #{@type} syntax found at '#{@path}'", "CONF"
16
- Rbcli.log.warn e.message, "CONF"
17
- Hash.new
15
+ raise Rbcli::ParseError.new "Invalid #{@type} syntax found at '#{@path}': #{e.message}"
18
16
  else
19
17
  @loaded = true
20
18
  parsed_str
@@ -28,5 +26,4 @@ class Rbcli::UserConf::Json < Rbcli::UserConf::Backend
28
26
  def inject_banner text, _banner
29
27
  text
30
28
  end
31
-
32
29
  end
@@ -9,7 +9,7 @@ class Rbcli::UserConf::Null < Rbcli::UserConf::Backend
9
9
  @loaded = false
10
10
  end
11
11
 
12
- def load defaults: nil
12
+ def load _defaults = nil
13
13
  @loaded = true
14
14
  {}
15
15
  end
@@ -18,15 +18,15 @@ class Rbcli::UserConf::Null < Rbcli::UserConf::Backend
18
18
  hash
19
19
  end
20
20
 
21
- def savable?
21
+ def save_raw text
22
22
  true
23
23
  end
24
24
 
25
- def exist?
26
- true
25
+ def savable?
26
+ false
27
27
  end
28
28
 
29
- def annotate! _defaults = nil
29
+ def exist?
30
30
  true
31
31
  end
32
32
  end
@@ -10,12 +10,9 @@ class Rbcli::UserConf::Toml < Rbcli::UserConf::Backend
10
10
 
11
11
  def parse str
12
12
  begin
13
-
14
13
  parsed_str = TOML.load(str).deep_symbolize!
15
14
  rescue => e
16
- Rbcli.log.warn "Error when parsing TOML file", "CONF"
17
- Rbcli.log.warn e.message, "CONF"
18
- Hash.new
15
+ raise Rbcli::ParseError.new "Invalid #{@type} syntax found at '#{@path}': #{e.message}"
19
16
  else
20
17
  @loaded = true
21
18
  parsed_str
@@ -12,9 +12,7 @@ class Rbcli::UserConf::Yaml < Rbcli::UserConf::Backend
12
12
  begin
13
13
  parsed_str = YAML.safe_load(str, symbolize_names: true, aliases: true, permitted_classes: [Symbol])
14
14
  rescue Psych::SyntaxError, Psych::DisallowedClass => e
15
- Rbcli.log.warn "Invalid #{@type} syntax found at '#{@path}'", "CONF"
16
- Rbcli.log.warn e.message, "CONF"
17
- Hash.new
15
+ raise Rbcli::ParseError.new "Invalid #{@type} syntax found at '#{@path}': #{e.message}"
18
16
  else
19
17
  @loaded = true
20
18
  parsed_str
@@ -6,28 +6,63 @@
6
6
  module Rbcli::Configurate::Config
7
7
  include Rbcli::Configurable
8
8
 
9
- def self.file location: nil, type: nil, schema_location: nil, save_on_exit: false, create_if_not_exists: false, suppress_errors: false
9
+ @params = {}
10
+ @on_declare = Proc.new do
11
+ Rbcli::Engine.register_operation Proc.new {
12
+ config = Rbcli::Config.new(**@params)
13
+ Rbcli::Warehouse.set(:config, config, :parsedopts)
14
+ Rbcli::Warehouse.get(:config, :parsedopts).load!; Rbcli::Warehouse.get(:config, :parsedopts).validate!
15
+ }, name: :load_config, priority: 40
16
+ end
17
+
18
+ def self.file location
10
19
  raise Rbcli::ConfigurateError.new "Config file location must either be a path or an array of paths" unless location.nil? || location.is_a?(String) || (location.is_a?(Array) && location.all? { |loc| loc.is_a?(String) })
11
- raise Rbcli::ConfigurateError.new "Config schema file location must be a path" unless schema_location.nil? || schema_location.is_a?(String)
20
+ @params[:location] = location
21
+ end
22
+
23
+ def self.type type
12
24
  raise Rbcli::ConfigurateError.new "Config type must be one of the following: #{Rbcli::UserConf::Backend.types.keys.join(', ')}" unless (type.is_a?(String) || type.is_a?(Symbol)) && Rbcli::UserConf::Backend.types.key?(type.downcase.to_sym)
13
- config = Rbcli::Config.new location: location, type: type, schema_location: schema_location, create_if_not_exists: create_if_not_exists, suppress_errors: suppress_errors
14
- Rbcli::Warehouse.set(:config, config, :parsedopts)
15
- Rbcli::Engine.register_operation Proc.new { Rbcli::Warehouse.get(:config, :parsedopts).load!; Rbcli::Warehouse.get(:config, :parsedopts).validate! }, name: :load_config, priority: 40
16
- Rbcli::Engine.register_operation Proc.new { Rbcli::Warehouse.get(:config, :parsedopts).save! }, name: :save_config, priority: 160 if save_on_exit
25
+ @params[:type] = type
26
+ end
27
+
28
+ def self.schema_file path
29
+ raise Rbcli::ConfigurateError.new "Config schema file location must be a path" unless path.nil? || path.is_a?(String)
30
+ raise Rbcli::ConfigurateError.new "May not define both a schema_hash and schema_file together." if @params[:schema_hash] && !path.nil?
31
+ @params[:schema_file] = path
32
+ end
33
+
34
+ def self.schema_hash hash
35
+ raise Rbcli::ConfigurateError.new "Config schema hash must be a hash" unless hash.nil? || hash.is_a?(Hash)
36
+ raise Rbcli::ConfigurateError.new "May not define both a schema_hash and schema_file together." if @params[:schema_file] && !hash.nil?
37
+ @params[:schema_hash] = hash
38
+ end
39
+
40
+ def self.save_on_exit soe
41
+ Rbcli::Engine.register_operation Proc.new { Rbcli::Warehouse.get(:config, :parsedopts).save! }, name: :save_config, priority: 160 if soe
42
+ end
43
+
44
+ def self.create_if_not_exists cne
45
+ raise Rbcli::ConfigurateError.new "Config 'create_if_not_exists' must be true or false" unless cne.is_a?(TrueClass) || cne.is_a?(FalseClass)
46
+ @params[:create_if_not_exists] = cne
47
+ end
48
+
49
+ def self.suppress_errors suppress
50
+ raise Rbcli::ConfigurateError.new "Config 'suppress_errors' must be true or false" unless suppress.is_a?(TrueClass) || suppress.is_a?(FalseClass)
51
+ @params[:suppress_errors] = suppress
17
52
  end
18
53
 
19
54
  def self.banner text
20
55
  raise Rbcli::ConfigurateError.new "The banner must be set to a string." unless text.is_a?(String)
21
- Rbcli::Warehouse.get(:config, :parsedopts).set_banner text
56
+ @params[:banner] = text
22
57
  end
23
58
 
24
- def self.group slug #, helptext: nil
25
- @lastgroup = slug.to_sym
26
- Rbcli::Warehouse.get(:config, :parsedopts).add_group(slug.to_sym, helptext: nil)
59
+ def self.defaults hash
60
+ raise Rbcli::ConfigurateError.new "The default configuration must be a hash." unless hash.is_a?(Hash)
61
+ @params[:defaults] = hash
27
62
  end
28
63
 
29
- def self.setting slug, default: nil #, helptext: nil
30
- # raise Rbcli::ConfigurateError.new "A config group must be defined before declaring any config options" if @lastgroup.nil?
31
- Rbcli::Warehouse.get(:config, :parsedopts).add_default(slug.to_sym, helptext: nil, group_path: @lastgroup, default: default)
64
+ def self.skeleton text
65
+ raise Rbcli::ConfigurateError.new "The skeleton data must be set to a string." unless text.is_a?(String)
66
+ @params[:skeleton] = text
32
67
  end
33
68
  end
@@ -8,93 +8,85 @@ require 'json-schema'
8
8
  require_relative 'backend'
9
9
 
10
10
  class Rbcli::Config < Hash
11
- def initialize location: nil, type: nil, schema_location: nil, create_if_not_exists: false, suppress_errors: false
11
+ def initialize location: nil, type: nil, schema_file: nil, schema_hash: nil, create_if_not_exists: false, suppress_errors: false, banner: nil, defaults: {}, skeleton: nil
12
12
  location = [location] unless location.is_a?(Array)
13
- find_location = Proc.new do |method|
14
- location.each do |loc|
15
- storage = Rbcli::UserConf::Backend.create(loc, type: type)
16
- if storage.send(method)
17
- Rbcli.log.debug("Found config storage at '#{loc}'", "CONF") if method == :exist? && !loc.nil?
18
- Rbcli.log.debug("Ready to create config at '#{loc}'", "CONF") if method == :savable? && !loc.nil?
19
- @location = loc
20
- @storage = storage
21
- @should_create = true if method == :savable?
22
- break true
23
- end
24
- end
25
- end
26
- find_location.call(:exist?)
27
- find_location.call(:savable?) if !defined?(@location) && create_if_not_exists
28
-
29
- if (defined?(@location) && !@location.nil? && @location != :null) || type == :env
30
- Rbcli.log.debug "Instantiated config of type '#{@storage.type}' at '#{@location}'", "CONF"
31
- elsif location.nil? || location == :null || location == [nil]
32
- Rbcli.log.debug "Instantiated null config; data will not be stored", "CONF"
33
- @location = :null
34
- @storage = Rbcli::UserConf::Backend.create(:null)
13
+ locations = location.map { |path| [path, Rbcli::UserConf::Backend.create(path, type: type)] }.reject { |path, storage| !(path.nil? || path == :null) && storage.type == 'NULL' }
14
+ existing_location = locations.select { |_path, storage| storage.exist? }.first
15
+ savable_location = locations.select { |_path, storage| storage.savable? }.first
16
+ if !existing_location.nil?
17
+ @location, @storage = existing_location
18
+ Rbcli.log.debug @location.nil? ? "Instantiated null config; data will not be stored" : "Found config of type '#{@storage.type}' at '#{@location}'", "CONF"
19
+ elsif !savable_location.nil? && create_if_not_exists
20
+ @location, @storage = savable_location
21
+ @should_create = true
22
+ Rbcli.log.debug "Ready to create new config of type '#{@storage.type}' at '#{@location}'", "CONF"
35
23
  elsif suppress_errors
24
+ @location, @storage = :null, Rbcli::UserConf::Backend.create(:null)
36
25
  Rbcli.log.debug "Location(s) could not be found and/or are not writeable. Instantiating null config and failing silently.", "CONF"
37
- @location = :null
38
- @storage = Rbcli::UserConf::Backend.create(:null)
39
26
  else
40
27
  Rbcli.log.fatal "Config file could not be loaded. Please verify that it exists at one of the following locations: #{location.join(", ")}", "CONF"
41
28
  Rbcli::exit 2
42
29
  end
43
-
44
- @original_hash = {}
45
- @declared_defaults = { groups: {}, options: {}, helptext: nil }
46
30
  @suppress_errors = suppress_errors
47
- @type = @storage.nil? ? nil : @storage.type.downcase.to_sym
48
- if schema_location
49
- @schema = self.class.new(location: schema_location)
31
+ @original_hash = {}
32
+ @defaults = defaults
33
+ @skeleton = skeleton
34
+ @banner = banner
35
+ if schema_hash
36
+ @schema = self.class.new
37
+ schema_hash.each_key { |key| @schema[key] = schema_hash[key] }
38
+ @schema.is_schema = true
39
+ elsif schema_file
40
+ @schema = self.class.new(location: schema_file)
50
41
  @schema.is_schema = true
42
+ else
43
+ @schema = nil
51
44
  end
52
45
  end
53
46
 
54
- attr_accessor :is_schema, :type
47
+ attr_accessor :is_schema, :defaults
48
+ attr_reader :location, :schema, :skeleton, :banner, :suppress_errors
55
49
 
56
- def set_banner text
57
- @declared_defaults[:helptext] = text
50
+ def add_default slug, default = nil
51
+ @defaults[slug] = default
58
52
  end
59
53
 
60
- def add_group path_arr, helptext: nil
61
- make_group path_arr, helptext: helptext, nested: @declared_defaults
62
- end
63
-
64
- def add_default slug, helptext: nil, group_path: nil, default: nil, permitted: nil
65
- make_group group_path, nested: @declared_defaults
66
- make_default slug, helptext: helptext, group_path: group_path, default: default, permitted: permitted, nested: @declared_defaults
54
+ def type
55
+ @storage.type.downcase.to_sym
67
56
  end
68
57
 
69
58
  def load!
70
59
  if @should_create
71
60
  Rbcli.log.add (@suppress_errors ? :debug : :info), "Config file #{@location} does not exist. Creating with default values.", "CONF"
72
- self.deep_merge!(@storage.respond_to?(:parse_defaults) ? @storage.parse_defaults(defaults) : defaults)
73
- self.save!
74
- else
75
- Rbcli.log.debug "Loading #{@is_schema ? 'schema' : 'config'} file", "CONF"
76
- @original_hash = @storage.load(defaults: self.defaults)
77
- if !@storage.loaded?
78
- Rbcli.log.add (@suppress_errors ? :debug : :warn), "Could not load #{@is_schema ? 'schema' : 'config'} file", "CONF"
79
- Rbcli.log.add (@suppress_errors ? :debug : :warn), "Using defaults", "CONF" unless self.defaults.empty?
80
- return false
81
- else
82
- self.deep_merge!(@storage.respond_to?(:parse_defaults) ? @storage.parse_defaults(defaults) : defaults)
83
- self.deep_merge!(@original_hash.deep_symbolize!) if @original_hash.is_a?(Hash)
84
- end
61
+ return self.create!(force: true)
62
+ end
63
+ Rbcli.log.debug "Loading #{@is_schema ? 'schema' : 'config'} file", "CONF"
64
+ begin
65
+ @original_hash = @storage.load(@defaults)
66
+ rescue Rbcli::ParseError => e
67
+ Rbcli.log.add (@suppress_errors ? :debug : :fatal), e.message, "CONF"
68
+ Rbcli.exit 3 unless @suppress_errors
69
+ @original_hash = {}
70
+ end
71
+ unless @storage.loaded?
72
+ Rbcli.log.add (@suppress_errors ? :debug : :warn), "Could not load #{@is_schema ? 'schema' : 'config'} file", "CONF"
73
+ Rbcli.log.add (@suppress_errors ? :debug : :warn), "Using defaults", "CONF" unless @defaults.empty?
85
74
  end
75
+ self.clear
76
+ self.deep_merge!(@storage.respond_to?(:parse_defaults) ? @storage.parse_defaults(@defaults) : @defaults)
77
+ self.deep_merge!(@original_hash.deep_symbolize!) if @original_hash.is_a?(Hash)
86
78
  end
87
79
 
88
80
  def validate!
89
81
  return true if @schema.nil?
90
82
  Rbcli.log.debug "Validating config against schema", "CONF"
91
- @schema.load!
83
+ @schema.load! unless @schema.location.nil?
92
84
  begin
93
85
  JSON::Validator.validate!(@schema, self)
94
- rescue JSON::Schema::ValidationError => e
86
+ rescue JSON::Schema::ValidationError => _e
95
87
  Rbcli.log.send (@suppress_errors ? :debug : :error), "There are errors in the config. Please fix these errors and try again."
96
88
  Rbcli.log.send (@suppress_errors ? :debug : :error), JSON::Validator.fully_validate(@schema, self).join("\n")
97
- Rbcli::exit 3 unless @suppress_errors
89
+ Rbcli::exit 4 unless @suppress_errors
98
90
  return false
99
91
  end
100
92
  Rbcli.log.debug "Validated config against schema successfully", "CONF"
@@ -118,47 +110,24 @@ class Rbcli::Config < Hash
118
110
  self
119
111
  end
120
112
 
121
- def annotate!
122
- @storage.annotate! @declared_defaults
123
- end
124
-
125
- def inspect
126
- @declared_defaults.inspect
127
- end
128
-
129
- def defaults
130
- data = {}
131
- traverse = Proc.new do |dataloc, defaultsloc|
132
- dataloc.merge!(defaultsloc[:options].map { |k, v| [k, v[:default]] }.to_h)
133
- defaultsloc[:groups].keys.each do |k|
134
- dataloc[k] = {}
135
- traverse.call dataloc[k], defaultsloc[:groups][k]
136
- end
113
+ def create! path: nil, force: false
114
+ return false unless @location.is_a?(String)
115
+ if File.exist?(path || @location) && !force
116
+ Rbcli.log.add (@suppress_errors ? :debug : :error), "Config file already exists; can not overwrite.", "CONF"
117
+ Rbcli::exit 4 unless @suppress_errors
118
+ return false
137
119
  end
138
- traverse.call(data, @declared_defaults)
139
- data
140
- end
141
-
142
- private
143
-
144
- def make_group path_arr, helptext: nil, nested: nil
145
- return true if path_arr.nil? || path_arr.respond_to?(:empty?) && path_arr.empty?
146
- path_arr = [path_arr] unless path_arr.is_a?(Array)
147
- nested[:groups][path_arr.first.to_sym] ||= { groups: {}, options: {}, helptext: nil }
148
- if path_arr.length == 1
149
- nested[:groups][path_arr.first.to_sym][:helptext] = helptext
120
+ if @skeleton
121
+ (path.nil? ? @storage : Rbcli::UserConf::Backend.create(path, type: self.type)).save_raw @skeleton
150
122
  else
151
- make_group path_arr[1..-1], helptext: helptext, nested: nested[:groups][path_arr.first.to_sym]
123
+ self.deep_merge!(@storage.respond_to?(:parse_defaults) ? @storage.parse_defaults(@defaults) : @defaults)
124
+ self.save!
152
125
  end
126
+ @storage.annotate!(@banner) if @banner
153
127
  end
154
128
 
155
- def make_default slug, helptext: nil, group_path: nil, default: nil, permitted: nil, nested: nil
156
- group_path = [group_path] unless group_path.is_a?(Array)
157
- if group_path.empty? || group_path.first.nil?
158
- nested[:options][slug.to_sym] = { helptext: helptext, default: default, permitted: permitted }
159
- else
160
- make_default(slug, helptext: helptext, group_path: group_path[1..-1], default: default, permitted: permitted, nested: nested[:groups][group_path.first.to_sym])
161
- end
129
+ def annotate!
130
+ @storage.annotate! @banner
162
131
  end
163
132
  end
164
133
 
@@ -4,33 +4,30 @@ Rbcli::Configurate.config do
4
4
  # The built-in config will automatically pull in a config file to a hash and vice versa
5
5
  # It can either be used immutably (as user-defined configuration) and/or to store application state
6
6
  #
7
- # file location: <string> or <array> (Required) Provide either a specific config file location or a hierarchy to search
8
- # type: (:yaml|:json|:ini|:toml|:null) (Optional) Select which backend/file format to use. If the file has an associated extension (i.e. '.yaml') then this can be omitted.
9
- # schema_location: <string> (Optional) The file location of a JSON Schema (https://json-schema.org). If provided, the config will automatically be validated. (default: nil)
10
- # save_on_exit: (true|false) (Optional) Save changes to the config file on exit (default: false)
11
- # create_if_not_exists: (true|false) (Optional) Create the file using default values if it is not found on the system (default: false)
12
- # suppress_errors: (true|false) (Optional) If set to false, the application will halt on any errors in loading the config. If set to true, defaults will be provided (default: true)
13
- <%- end -%>
14
- file location: ['./<%= @appname.downcase %>.yaml', '~/.<%= @appname.downcase %>.yaml', '/etc/<%= @appname.downcase %>.yaml'],
15
- type: :yaml, schema_location: nil, save_on_exit: false,
16
- create_if_not_exists: false, suppress_errors: true
17
- <%- if @showdocs -%>
18
- ##### Banner (Optional) #####
19
- # Define a banner to be placed at the top of the config file.
20
- # Note that the banner will only be written to backends that support comments (:yaml, :ini, and :toml).
7
+ # file: <string> or <array> (Required) Provide either a specific config file location or a hierarchy to search. If creating a :null type of config this can be omitted.
8
+ # type: (:yaml|:json|:ini|:toml|:null) (Optional) Select which backend/file format to use. If the file has an associated extension (i.e. '.yaml') then this can be omitted.
9
+ # schema_file: <string> (Optional) The file location of a JSON Schema (https://json-schema.org). If provided, the config will automatically be validated. (default: nil)
10
+ # schema_hash: <string> (Optional) If you'd like to provide a JSON schema hash directly instead, do it here. May not use `schema_hash` and `schema_file` together. (default: nil)
11
+ # save_on_exit: (true|false) (Optional) Save changes to the config file on exit (default: false)
12
+ # create_if_not_exists: (true|false) (Optional) Create the file using default values if it is not found on the system (default: false)
13
+ # suppress_errors: (true|false) (Optional) If set to false, the application will halt on any errors in loading the config. If set to true, defaults will be provided (default: true)
14
+ # banner: <string> (Optional) Define a banner to be placed at the top of the config file on disk. Note that it will only be written to backends that support comments
15
+ # defaults: <hash> (Optional) Defaults set here will be provided to your application if any values are missing in the config.
16
+ # They will also be used to create new config files when the flag `create_if_not_exists` is set to true.
17
+ # skeleton: <string> (Optional) If a skeleton is provided, it will be used as the data source when creating new config files on disk instead of the defaults. (default: nil)`
18
+ # Please provide the text exactly as you want the user to see it in the config file (plus the banner if provided).
21
19
  <%- end -%>
20
+ file ['./<%= @appname.downcase %>.yaml', '~/.<%= @appname.downcase %>.yaml', '/etc/<%= @appname.downcase %>.yaml']
21
+ type :yaml
22
+ schema_file nil
23
+ schema_hash nil
24
+ save_on_exit false
25
+ create_if_not_exists false
26
+ suppress_errors true
22
27
  banner <<~BANNER
23
28
  This text appears at the top of the config file when using a backend that supports comments.
24
29
  Tell the user a bit about what they're doing here.
25
30
  BANNER
26
- <%- if @showdocs -%>
27
- ## Config Structure & Defaults (Optional)
28
- # Set defaults for your config values.
29
- #
30
- # Defaults set here will be provided to your application if the values are missing in the config.
31
- # They will also be used to create new config files when the flag `create_if_not_exists` is set above.
32
- # will be written to a config file, along with the helptext and/or short descriptions.
33
- <%- end -%>
34
- group :group1
35
- setting :enable_logins, default: true
31
+ defaults({ setting_one: true, setting_two: false })
32
+ skeleton "eJzT1dXlSsvPt1JISiziAhFWSYlVAD1hBjs=".decompress
36
33
  end
@@ -32,12 +32,15 @@ module Rbcli::Configurable
32
32
  end
33
33
  end
34
34
 
35
+
35
36
  # This will dynamically create the configurate block based on the class name.
36
37
  # For example, if the class name is 'Me', then the resulting block is `Configurate.me`
37
38
  name = klass.name.split('::')[-1]
38
39
  Rbcli::Configurate.singleton_class.class_eval do
39
40
  define_method name.downcase.to_sym do |&block|
40
41
  mod = self.const_get name
42
+ on_declare = mod.instance_variable_get('@on_declare')
43
+ on_declare.call if on_declare.is_a?(Proc)
41
44
  begin
42
45
  mod.rbcli_private_running_method &block
43
46
  rescue Rbcli::ConfigurateError => e
@@ -15,6 +15,6 @@ module Rbcli::Configurate::Envvars
15
15
 
16
16
  def self.envvar envvar, default
17
17
  raise Rbcli::ConfigurateError.new "Environment variables must be a string" unless envvar.is_a?(String)
18
- Rbcli::Warehouse.get(:env, :parsedopts).add_default(envvar, default: default)
18
+ Rbcli::Warehouse.get(:env, :parsedopts).add_default(envvar, default)
19
19
  end
20
20
  end
@@ -13,6 +13,7 @@ module Rbcli::Configurate::Cli
13
13
  author: nil,
14
14
  email: nil,
15
15
  copyright_year: nil,
16
+ compatibility: nil,
16
17
  license: nil,
17
18
  helptext: nil
18
19
  }
@@ -23,6 +23,14 @@ module Rbcli::Parser
23
23
  bannerstr += " <#{appinfo[:email]}>" unless appinfo[:author].nil? || appinfo[:email].nil?
24
24
  bannerstr += "\n"
25
25
  end
26
+ unless appinfo[:compatibility].nil? || appinfo[:compatibility].empty?
27
+ bannerstr += "Compatiblity: "
28
+ if appinfo[:compatibility].length == 2
29
+ bannerstr += appinfo[:compatibility].join(' and ') + "\n"
30
+ else
31
+ bannerstr += appinfo[:compatibility][0..-2].join(', ') + ', and ' + appinfo[:compatibility][-1] + "\n"
32
+ end
33
+ end
26
34
  bannerstr += "License: #{appinfo[:license]}\n" unless appinfo[:license].nil?
27
35
  bannerstr += "\n"
28
36
  bannerstr += appinfo[:helptext].chomp + "\n\n" unless appinfo[:helptext].nil?
@@ -6,6 +6,7 @@ Rbcli::Configurate.cli do
6
6
  # email (Optional) - An email for users to contact
7
7
  # version (Optional) - major.minor.patch notation, required if using update checks
8
8
  # copyright_year (Optional) - Self explanatory
9
+ # compatibility (Optional) - Array of Operating Systems, devices, or other targets (For example: %w[MacOS Linux Ubuntu Windows Raspberry\ Pi]
9
10
  # license (Optional) - Convention is to use an identifier from here: https://spdx.org/licenses/
10
11
  # helptext (Optional) - Text that gets shown with --help or -h
11
12
  <%- end -%>
@@ -14,6 +15,7 @@ Rbcli::Configurate.cli do
14
15
  email nil
15
16
  version <%= @appname.capitalize %>::VERSION
16
17
  copyright_year <%= Time.now.year %>
18
+ compatibility nil
17
19
  license nil
18
20
  helptext "This text shows up in `<%= @appname.downcase %> --help`"
19
21
  <%- if @showdocs -%>
@@ -9,4 +9,6 @@ module Rbcli
9
9
  class ConfigurateError < Rbcli::Error; end
10
10
 
11
11
  class CommandError < Rbcli::Error; end
12
+
13
+ class ParseError < Rbcli::Error; end
12
14
  end
@@ -8,6 +8,18 @@
8
8
  # Functions to convert hash keys to all symbols or all strings
9
9
  ##
10
10
  class Hash
11
+ def deep_symbolize hsh = nil
12
+ hsh ||= Marshal.load(Marshal.dump(self))
13
+ hsh.keys.each do |k|
14
+ if k.is_a? String
15
+ hsh[k.to_sym] = hsh[k]
16
+ hsh.delete k
17
+ end
18
+ deep_symbolize! hsh[k.to_sym] if hsh[k.to_sym].is_a? Hash
19
+ end
20
+ hsh
21
+ end
22
+
11
23
  def deep_symbolize! hsh = nil
12
24
  hsh ||= self
13
25
  hsh.keys.each do |k|
@@ -20,6 +32,18 @@ class Hash
20
32
  hsh
21
33
  end
22
34
 
35
+ def deep_stringify hsh = nil
36
+ hsh ||= Marshal.load(Marshal.dump(self))
37
+ hsh.keys.each do |k|
38
+ if k.is_a? Symbol
39
+ hsh[k.to_s] = hsh[k]
40
+ hsh.delete k
41
+ end
42
+ deep_stringify! hsh[k.to_s] if hsh[k.to_s].is_a? Hash
43
+ end
44
+ hsh
45
+ end
46
+
23
47
  def deep_stringify! hsh = nil
24
48
  hsh ||= self
25
49
  hsh.keys.each do |k|
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbcli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Khoury
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-05-07 00:00:00.000000000 Z
10
+ date: 2025-05-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -259,12 +258,10 @@ homepage: https://akhoury6.github.io/rbcli/
259
258
  licenses:
260
259
  - MIT
261
260
  metadata:
262
- allowed_push_host: https://rubygems.org
263
261
  homepage_uri: https://akhoury6.github.io/rbcli/
264
262
  documentation_uri: https://akhoury6.github.io/rbcli/
265
263
  source_code_uri: https://github.com/akhoury6/rbcli
266
264
  changelog_uri: https://github.com/akhoury6/rbcli/blob/master/CHANGELOG.md
267
- post_install_message:
268
265
  rdoc_options: []
269
266
  require_paths:
270
267
  - lib
@@ -279,8 +276,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
279
276
  - !ruby/object:Gem::Version
280
277
  version: '0'
281
278
  requirements: []
282
- rubygems_version: 3.5.9
283
- signing_key:
279
+ rubygems_version: 3.6.2
284
280
  specification_version: 4
285
281
  summary: A CLI Application/Tooling Framework for Ruby
286
282
  test_files: []