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 +4 -4
- data/VERSION +1 -1
- data/lib/rbcli/components/commands/command.rb +1 -1
- data/lib/rbcli/components/commands/command.rb.erb +4 -0
- data/lib/rbcli/components/config/backend.rb +21 -7
- data/lib/rbcli/components/config/backends/env.rb +5 -9
- data/lib/rbcli/components/config/backends/ini.rb +1 -2
- data/lib/rbcli/components/config/backends/json.rb +1 -4
- data/lib/rbcli/components/config/backends/null.rb +5 -5
- data/lib/rbcli/components/config/backends/toml.rb +1 -4
- data/lib/rbcli/components/config/backends/yaml.rb +1 -3
- data/lib/rbcli/components/config/component.rb +48 -13
- data/lib/rbcli/components/config/config.rb +62 -93
- data/lib/rbcli/components/config/template.rb.erb +21 -24
- data/lib/rbcli/components/core/configurate.rb +3 -0
- data/lib/rbcli/components/envvars/component.rb +1 -1
- data/lib/rbcli/components/parser/component.rb +1 -0
- data/lib/rbcli/components/parser/parser.rb +8 -0
- data/lib/rbcli/components/parser/template.rb.erb +2 -0
- data/lib/rbcli/util/errors.rb +2 -0
- data/lib/rbcli/util/hash_deep_symbolize.rb +24 -0
- metadata +3 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bad65e9d8cefa0a82adb9305a68294eeaed6cfefed1d06be9cce6edb5eb2a2eb
|
4
|
+
data.tar.gz: 6add98a08da1c55f18e87e60bb279a31adad0979b31d3b00745b9016ab5870d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af5e0383b0bf15283007c3e9e45470b5925037e5d73ae9c6ce4fb329ecf3ef983321e4e23bdcbc761676ce79e1adb48b151364067bbc5130e15adc789ed663ce
|
7
|
+
data.tar.gz: 01e6bba1b239653d59cb8e3b4c7c9e6c9ee33efe28ea50aa11cdafd55bcebdab433dc17082464f65ee38ee1290b2f40f39543272a5604a1f5e93d25f99e01c27
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
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)] }.
|
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
|
47
|
-
def load
|
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!
|
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
|
-
|
89
|
-
|
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
|
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.
|
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.
|
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
|
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
|
21
|
+
def save_raw text
|
22
22
|
true
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
25
|
+
def savable?
|
26
|
+
false
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
56
|
+
@params[:banner] = text
|
22
57
|
end
|
23
58
|
|
24
|
-
def self.
|
25
|
-
|
26
|
-
|
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.
|
30
|
-
|
31
|
-
|
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,
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
@
|
48
|
-
|
49
|
-
|
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, :
|
47
|
+
attr_accessor :is_schema, :defaults
|
48
|
+
attr_reader :location, :schema, :skeleton, :banner, :suppress_errors
|
55
49
|
|
56
|
-
def
|
57
|
-
@
|
50
|
+
def add_default slug, default = nil
|
51
|
+
@defaults[slug] = default
|
58
52
|
end
|
59
53
|
|
60
|
-
def
|
61
|
-
|
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.
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@original_hash = @storage.load(defaults
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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 =>
|
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
|
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
|
122
|
-
@
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
139
|
-
|
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
|
-
|
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
|
156
|
-
|
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
27
|
-
|
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
|
18
|
+
Rbcli::Warehouse.get(:env, :parsedopts).add_default(envvar, default)
|
19
19
|
end
|
20
20
|
end
|
@@ -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 -%>
|
data/lib/rbcli/util/errors.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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: []
|