1module ConfigWriters 2 class Base 3 class Writer 4 def category(name) 5 raise NotImplementedError.new() 6 end 7 end 8 9 class TOMLWriter < Writer 10 PATH_DELIMITER = ".".freeze 11 12 def initialize 13 @indent = 0 14 @string = "" 15 end 16 17 def category(name) 18 last_line = @string.split("\n").last 19 20 if last_line && (last_line[0] != "[" || last_line[-1] != "]") 21 puts() 22 end 23 24 puts("# #{name}") 25 end 26 27 def hash(hash, path: [], tags: []) 28 if hash.length > 1 29 raise ArgumentError.new("A hash must contain only a single key and value") 30 end 31 32 key = nil 33 value = nil 34 35 if hash.values.first.is_a?(Hash) 36 hash = hash.flatten 37 key = hash.keys.first 38 value = hash.values.first 39 elsif hash.keys.first.include?(".") 40 key = hash.keys.first.inspect 41 value = hash.values.first 42 else 43 key = hash.keys.first 44 value = hash.values.first 45 end 46 47 kv(key, value, path: path, tags: tags) 48 end 49 50 def indent(spaces) 51 @indent += spaces 52 end 53 54 def kv(key, value, path: [], tags: []) 55 quoted_key = key.include?(" ") ? key.to_toml : key 56 full_key = (path + [quoted_key]).join(PATH_DELIMITER) 57 line = "#{full_key} = #{value.to_toml(hash_style: :inline)}" 58 59 if !line.include?("\n") && tags.any? 60 line << " # #{tags.join(", ")}" 61 end 62 63 puts(line) 64 end 65 66 def print(string) 67 @string << string 68 end 69 70 def puts(string = nil) 71 if string == nil 72 @string << "\n" 73 else 74 @string << ("#{string}".indent(@indent) + "\n") 75 end 76 end 77 78 def table(name, array: false, path: []) 79 full_name = (path + [name]).join(PATH_DELIMITER) 80 81 if array 82 puts("[[#{full_name}]]") 83 else 84 puts("[#{full_name}]") 85 end 86 87 indent(2) 88 end 89 90 def to_s 91 @string.rstrip 92 end 93 end 94 95 attr_reader :array, :block, :fields, :group, :key_path, :table_path, :values 96 97 def initialize(fields, array: false, group: nil, key_path: [], table_path: [], values: nil, &block) 98 if !fields.is_a?(Array) 99 raise ArgumentError.new("fields must be an array") 100 end 101 102 if block_given? 103 fields = fields.select(&block) 104 end 105 106 @array = array 107 @fields = fields 108 @group = group 109 @key_path = key_path 110 @table_path = table_path 111 @block = block 112 @values = values || {} 113 end 114 115 def categories 116 @categories ||= fields.collect(&:category).uniq 117 end 118 119 def to_toml(table_style: :normal) 120 raise NotImplementedError.new() 121 end 122 123 private 124 def build_child_writer(fields, array: false, group: nil, key_path: [], table_path: [], values: nil) 125 self.class.new(fields, array: array, group: group, key_path: key_path, table_path: table_path, values: values, &block) 126 end 127 128 def field_tags(field, default: true, enum: true, example: false, optionality: true, relevant_when: true, type: true, short: false, unit: true) 129 tags = [] 130 131 if optionality 132 if field.required? 133 tags << "required" 134 else 135 tags << "optional" 136 end 137 end 138 139 if example 140 if field.default.nil? && (!field.enum || field.enum.keys.length > 1) 141 tags << "example" 142 end 143 end 144 145 if default 146 if !field.default.nil? 147 if short 148 tags << "default" 149 else 150 tags << "default: #{field.default.inspect}" 151 end 152 elsif field.optional? 153 tags << "no default" 154 end 155 end 156 157 if type 158 if short 159 tags << field.type 160 else 161 tags << "type: #{field.type}" 162 end 163 end 164 165 if unit && !field.unit.nil? 166 if short 167 tags << field.unit 168 else 169 tags << "unit: #{field.unit}" 170 end 171 end 172 173 if enum && field.enum 174 if short && field.enum.keys.length > 1 175 tags << "enum" 176 else 177 escaped_values = field.enum.keys.collect { |enum| enum.to_toml } 178 if escaped_values.length > 1 179 tags << "enum: #{escaped_values.to_sentence(two_words_connector: " or ")}" 180 else 181 tag = "must be: #{escaped_values.first}" 182 if field.optional? 183 tag << " (if supplied)" 184 end 185 tags << tag 186 end 187 end 188 end 189 190 if relevant_when && field.relevant_when 191 word = field.required? ? "required" : "relevant" 192 tag = "#{word} when #{field.relevant_when_kvs.to_sentence(two_words_connector: " or ")}" 193 tags << tag 194 end 195 196 tags 197 end 198 199 def full_path 200 table_path + key_path 201 end 202 end 203end 204