1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
7 class YAMLTree < Psych::Visitors::Visitor
8 def visit_ActiveSupport_Duration o
12 outstr += "#{seconds / 3600}h"
13 seconds = seconds % 3600
16 outstr += "#{seconds / 60}m"
17 seconds = seconds % 60
20 outstr += "#{seconds}s"
25 @emitter.scalar outstr, nil, nil, true, false, Nodes::Scalar::ANY
28 def visit_URI_Generic o
29 @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
33 @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
37 @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
45 class TrueClass; include Boolean; end
46 class FalseClass; include Boolean; end
48 class NonemptyString < String
53 @config_migrate_map = {}
57 def declare_config(assign_to, configtype, migrate_from=nil, migrate_fn=nil)
59 @config_migrate_map[migrate_from] = migrate_fn || ->(cfg, k, v) {
60 ConfigLoader.set_cfg cfg, assign_to, v
63 @config_types[assign_to] = configtype
67 def migrate_config from_config, to_config
69 from_config.each do |k, v|
70 if @config_migrate_map[k.to_sym]
71 @config_migrate_map[k.to_sym].call to_config, k, v
79 def coercion_and_check check_cfg, check_nonempty: true
80 @config_types.each do |cfgkey, cfgtype|
93 raise "missing #{cfgkey}"
96 if cfgtype == String and !cfg[k]
100 if cfgtype == String and cfg[k].is_a? Symbol
104 if cfgtype == Pathname and cfg[k].is_a? String
107 cfg[k] = Pathname.new("")
109 cfg[k] = Pathname.new(cfg[k])
111 raise "#{cfgkey} path #{cfg[k]} does not exist"
116 if cfgtype == NonemptyString
117 if (!cfg[k] || cfg[k] == "") && check_nonempty
118 raise "#{cfgkey} cannot be empty"
120 if cfg[k].is_a? String
125 if cfgtype == ActiveSupport::Duration
126 if cfg[k].is_a? Integer
127 cfg[k] = cfg[k].seconds
128 elsif cfg[k].is_a? String
129 cfg[k] = ConfigLoader.parse_duration(cfg[k], cfgkey: cfgkey)
137 if cfgtype == Integer && cfg[k].is_a?(String)
138 v = cfg[k].sub(/B\s*$/, '')
139 if mt = /(-?\d*\.?\d+)\s*([KMGTPE]i?)$/.match(v)
152 "T" => 1000000000000,
154 "P" => 1000000000000000,
156 "E" => 1000000000000000000,
162 if !cfg[k].is_a? cfgtype
163 raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
168 def self.set_cfg cfg, k, v
169 # "foo.bar = baz" --> { cfg["foo"]["bar"] = baz }
183 def self.parse_duration(durstr, cfgkey:)
186 durstr = durstr[1..-1]
189 duration_re = /(\d+(\.\d+)?)(s|m|h)/
192 mt = duration_re.match durstr
194 raise "#{cfgkey} not a valid duration: '#{durstr}', accepted suffixes are s, m, h"
196 multiplier = {s: 1, m: 60, h: 3600}
197 dursec += (Float(mt[1]) * multiplier[mt[3].to_sym] * sign)
198 durstr = durstr[mt[0].length..-1]
200 return dursec.seconds
203 def self.copy_into_config src, dst
205 dst.send "#{k}=", self.to_OrderedOptions(v)
209 def self.to_OrderedOptions confs
211 opts = ActiveSupport::OrderedOptions.new
213 opts[k] = self.to_OrderedOptions(v)
216 elsif confs.is_a? Array
217 confs.map { |v| self.to_OrderedOptions v }
223 def self.load path, erb: false
227 yaml = ERB.new(yaml).result(binding)
229 YAML.load(yaml, deserialize_symbols: false)