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]
72 @config_migrate_map[k.to_sym].call to_config, k, v
74 raise "Error migrating '#{k}: #{v}' got error #{e}"
83 def coercion_and_check check_cfg, check_nonempty: true
84 @config_types.each do |cfgkey, cfgtype|
98 raise "missing #{cfgkey}"
101 if cfgtype == String and !cfg[k]
105 if cfgtype == String and cfg[k].is_a? Symbol
109 if cfgtype == Pathname and cfg[k].is_a? String
112 cfg[k] = Pathname.new("")
114 cfg[k] = Pathname.new(cfg[k])
116 raise "#{cfgkey} path #{cfg[k]} does not exist"
121 if cfgtype == NonemptyString
122 if (!cfg[k] || cfg[k] == "") && check_nonempty
123 raise "#{cfgkey} cannot be empty"
125 if cfg[k].is_a? String
130 if cfgtype == ActiveSupport::Duration
131 if cfg[k].is_a? Integer
132 cfg[k] = cfg[k].seconds
133 elsif cfg[k].is_a? String
134 cfg[k] = ConfigLoader.parse_duration(cfg[k], cfgkey: cfgkey)
146 if cfgtype == Integer && cfg[k].is_a?(String)
147 v = cfg[k].sub(/B\s*$/, '')
148 if mt = /(-?\d*\.?\d+)\s*([KMGTPE]i?)$/.match(v)
161 "T" => 1000000000000,
163 "P" => 1000000000000000,
165 "E" => 1000000000000000000,
172 raise "#{cfgkey} expected #{cfgtype} but '#{cfg[k]}' got error #{e}"
175 if !cfg[k].is_a? cfgtype
176 raise "#{cfgkey} expected #{cfgtype} but was #{cfg[k].class}"
181 def self.set_cfg cfg, k, v
182 # "foo.bar = baz" --> { cfg["foo"]["bar"] = baz }
196 def self.parse_duration durstr, cfgkey:
197 duration_re = /-?(\d+(\.\d+)?)(s|m|h)/
200 mt = duration_re.match durstr
202 raise "#{cfgkey} not a valid duration: '#{durstr}', accepted suffixes are s, m, h"
204 multiplier = {s: 1, m: 60, h: 3600}
205 dursec += (Float(mt[1]) * multiplier[mt[3].to_sym])
206 durstr = durstr[mt[0].length..-1]
208 return dursec.seconds
211 def self.copy_into_config src, dst
213 dst.send "#{k}=", self.to_OrderedOptions(v)
217 def self.to_OrderedOptions confs
219 opts = ActiveSupport::OrderedOptions.new
221 opts[k] = self.to_OrderedOptions(v)
224 elsif confs.is_a? Array
225 confs.map { |v| self.to_OrderedOptions v }
231 def self.load path, erb: false
235 yaml = ERB.new(yaml).result(binding)
237 YAML.load(yaml, deserialize_symbols: false)