Class: Incline::CliHelpers::Yaml::YamlContents
- Inherits:
-
Object
- Object
- Incline::CliHelpers::Yaml::YamlContents
- Defined in:
- lib/incline/cli/helpers/yaml.rb
Overview
Helper class to process the YAML file contents easily.
Instance Method Summary collapse
-
#=~(regexp) ⇒ Object
Allows comparing the contents against a regular expression.
-
#add_key(key, value, make_safe_value = true) ⇒ Object
Adds a key to the YAML contents if it is missing.
-
#add_key_with_comment(key, value, comment) ⇒ Object
Adds a key to the YAML contents if it is missing.
-
#append_comment(text) ⇒ Object
Appends a comment to the end of the contents.
-
#initialize(content) ⇒ YamlContents
constructor
Creates a new YAML contents.
-
#insert_comment(text) ⇒ Object
Inserts a comment to the beginning of the contents.
-
#level_comment_offset(level) ⇒ Object
Gets the comment offset for the specified level.
-
#level_value_offset(level) ⇒ Object
Gets the value offset for the specified level.
-
#realign! ⇒ Object
Realigns the file.
-
#remove_key(key) ⇒ Object
Removes the specified key from the contents.
-
#set_key(key, value, make_safe_value = true) ⇒ Object
Sets a key in the YAML contents.
-
#set_key_with_comment(key, value, comment) ⇒ Object
Sets a key in the YAML contents.
-
#set_level_comment_offset(level, offset) ⇒ Object
Sets the comment offset for the specified level.
-
#set_level_value_offset(level, offset) ⇒ Object
Sets the value offset for the specified level.
-
#to_s ⇒ Object
Returns the YAML contents.
Constructor Details
#initialize(content) ⇒ YamlContents
Creates a new YAML contents.
18 19 20 |
# File 'lib/incline/cli/helpers/yaml.rb', line 18 def initialize(content) @content = content.to_s.gsub("\r\n", "\n").strip + "\n" end |
Instance Method Details
#=~(regexp) ⇒ Object
Allows comparing the contents against a regular expression.
518 519 520 |
# File 'lib/incline/cli/helpers/yaml.rb', line 518 def =~(regexp) @content =~ regexp end |
#add_key(key, value, make_safe_value = true) ⇒ Object
Adds a key to the YAML contents if it is missing. Does nothing to the key if it exists.
add_key [ "default", "name" ], "george"
The ‘key’ should be an array defining the path.
Value can be nil, a string, a symbol, a number, or a boolean.
The ‘make_safe_value’ option can be used to provide an explicit text value. This can be useful if you want to add a specific value, like an ERB command.
add_key [ "default", "name" ], "<%= ENV[\"DEFAULT_USER\"] %>", false
You can also use a hash for the value to specify advanced options. Currently only three advanced options are recognized.
The first option, :value, simply sets the value. If this is the only hash key provided, then the value supplied is treated as if it was the original value. In other words, only setting :value is the same as not using a hash and just passing in the value, so the value must be nil, a string, a symbol, a number, or a boolean.
The second option, :safe, works the opposite of the ‘make_safe_value’ parameter. If :safe is a non-false value, then it is like ‘make_safe_value’ is set to false. If :safe is a false value, then it is like ‘make_safe_value’ is set to true. The :safe value can be set to true and the :value option can set the value, or the :safe value can be set to the value directly since all strings are non-false.
The third option, :before_section, tells add_key to insert the section before the named section (if the new section doesn’t exist). This can be useful if the named section is going to be referencing the key you are adding. Otherwise, when a section needs to be added, it gets added to the end of the file.
Returns the contents object.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/incline/cli/helpers/yaml.rb', line 64 def add_key(key, value, make_safe_value = true) # ensure the parent structure exists! if key.count > 1 add_key(key[0...-1], nil) else # If the base key already exists, no need to look further. return self if @content =~ /^#{key.first}:/ unless @content[0] == '#' @content = "# File modified by Incline v#{Incline::VERSION}.\n" + @content end end val_name = key.last # construct a regular expression to find the parent group and value. rex_str = '^(' rex_prefix = /\A./ key.each_with_index do |attr,level| lev = (level < 1 ? '' : ('\\s\\s' * (level))) if lev != '' rex_str += '(?:' + lev + '[^\\n]*\\n)*' end if level == key.count - 1 if level == 0 # At level 0 we cheat and use a very simple regular expression to confirm the section exists. rex_str = "^#{attr}:" # If it doesn't exists, the prefix regex will usually want to put the new section at the end of the # file. However if the :before_section option is set, and the other section exists, then we # want to ensure that we are putting the new section before it. # # Down below we take care to reverse the replacement string when key.count == 1. rex_prefix = if value.is_a?(::Hash) && value[:before_section] && @content =~ /^#{value[:before_section]}:/ /(^#{value[:before_section]}:)/ else /(\z)/ # match the end of the contents. end else rex_str += ')' rex_prefix = Regexp.new(rex_str) rex_str += '(' + lev + attr + ':.*\\n)' end else rex_str += lev + attr + ':.*\\n' end end rex = Regexp.new(rex_str) if @content =~ rex # all good. elsif @content =~ rex_prefix if make_safe_value value = safe_value(value) value = add_value_offset(key, value) elsif value.is_a?(::Hash) value = value[:value] end value = '' if value =~ /\A\s*\z/ # Should be true thanks to first step in this method. # Capture 1 would be the parent group. # When key.count == 1 then we want to put our new value before capture 1. # Otherwise we put our new value after capture 1. rep = if key.count == 1 "\n#{val_name}:#{value}\n\\1" else "\\1#{' ' * (key.count - 1)}#{val_name}:#{value}\n" end @content.gsub! rex_prefix, rep else raise ::Incline::CliHelpers::Yaml::YamlError, "Failed to create parent group for '#{key.join('/')}'." end self end |
#add_key_with_comment(key, value, comment) ⇒ Object
Adds a key to the YAML contents if it is missing. Does nothing to the key if it exists.
add_key_with_comment [ "default", "name" ], "george", "this is the name of the default user"
The ‘key’ should be an array defining the path. If the ‘comment’ is blank (nil or ”), then it will not modify the comment. Use a whitespace string (‘ ’) to indicate that you want a blank comment added.
Value can be nil, a string, a symbol, a number, or a boolean. Value can also be a hash according to #add_key.
Returns the contents object.
158 159 160 161 162 163 164 |
# File 'lib/incline/cli/helpers/yaml.rb', line 158 def add_key_with_comment(key, value, comment) if comment.to_s == '' add_key key, value else add_key key, value_with_comment(key, value, comment), false end end |
#append_comment(text) ⇒ Object
Appends a comment to the end of the contents.
531 532 533 534 535 536 537 |
# File 'lib/incline/cli/helpers/yaml.rb', line 531 def append_comment(text) text = '# ' + text.gsub("\r\n", "\n").gsub("\n", "\n# ") + "\n" unless @content[-1] == "\n" @content += "\n" end @content += text end |
#insert_comment(text) ⇒ Object
Inserts a comment to the beginning of the contents.
524 525 526 527 |
# File 'lib/incline/cli/helpers/yaml.rb', line 524 def insert_comment(text) text = '# ' + text.gsub("\r\n", "\n").gsub("\n", "\n# ") + "\n" @content = @content.insert(0, text) end |
#level_comment_offset(level) ⇒ Object
Gets the comment offset for the specified level.
The offset is based on the beginning of the level in question. There will always be at least one whitespace before the value and at least one whitespace between the value and a comment. For instance an offset of 15 for level 2 would be like this:
one:
two: value # comment
some_long_name: value # comment
# 012345678901234^
495 496 497 |
# File 'lib/incline/cli/helpers/yaml.rb', line 495 def level_comment_offset(level) comment_offsets[level] || 0 end |
#level_value_offset(level) ⇒ Object
Gets the value offset for the specified level.
The offset is based on the beginning of the level in question. There will always be at least one whitespace before the value. For instance an offset of 10 for level 2 would be like this:
one:
two: value
some_long_name: value
# 0123456789^
462 463 464 |
# File 'lib/incline/cli/helpers/yaml.rb', line 462 def level_value_offset(level) value_offsets[level] || 0 end |
#realign! ⇒ Object
Realigns the file.
All values and comments will line up at each level when complete.
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/incline/cli/helpers/yaml.rb', line 393 def realign! lines = extract_to_array(@content) # reset the offsets. value_offsets.clear comment_offsets.clear # get value offsets. lines.each do |line| level = line[:level] if level > 0 && line[:key] key_len = line[:key].length + 2 # include the colon and a space if key_len > level_value_offset(level) set_level_value_offset level, key_len end end end # get comment offsets. lines.each do |line| level = line[:level] if level > 0 && line[:value] voff = level_value_offset(level) val_len = line[:value] ? line[:value].length : 0 coff = voff + val_len + 1 # add a space after the value. if coff > level_comment_offset(level) set_level_comment_offset level, coff end end end # convert the lines back into strings with proper spacing. lines = lines.map do |line| level = line[:level] if level > 0 if line[:key] # a key: value line. key = line[:key] + ':' key = key.ljust(level_value_offset(level), ' ') unless line[:value].to_s == '' && line[:comment].to_s == '' val = line[:value].to_s val = val.ljust(level_comment_offset(level) - level_value_offset(level), ' ') unless line[:comment].to_s == '' comment = line[:comment] ? "# #{line[:comment]}" : '' (' ' * (level - 1)) + key + val + comment else # just a comment line. (' ' * (level - 1)) + (' ' * level_comment_offset(level)) + "# #{line[:comment]}" end else line[:value] # return the original value end end @content = lines.join("\n") + "\n" end |
#remove_key(key) ⇒ Object
Removes the specified key from the contents.
Returns an array containing the contents of the key. The first element will be for the key itself. If the key had child keys, then they will also be included in the array.
The returned array will contain hashes for each removed key.
data = remove_key %w(pet dog)
[
{
:key => [ "pet", "dog" ],
:value => "",
:safe => true,
:comment => "This list has the family dogs."
},
{
:key => [ "pet", "dog", "sadie" ],
:value => "",
:safe => true,
:comment => ""
},
{
:key => [ "pet", "dog", "sadie", "breed" ],
:value => "boxer",
:safe => true,
:comment => ""
},
{
:key => [ "pet", "dog", "sadie", "dob" ],
:value => "\"2016-06-01\"",
:safe => true,
:comment => "Estimated date of birth since she was a rescue."
}
]
The returned hashes can be fired right back into #add_key.
data.each do |item|
add_key_with_comment item[:key], item, item[:comment]
end
This method can be used to move a section within the file.
# remove the 'familes' section from the file.
section = remove_key [ "families" ]
item = section.delete(section.first)
# add the 'familes' section back in before the 'pets' section.
add_key_with_comment item[:key], { before_section: "pets" }.merge(item), item[:comment]
# add the data back into the 'familes' section.
section.each do |item|
add_key_with_comment item[:key], item, item[:comment]
end
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/incline/cli/helpers/yaml.rb', line 305 def remove_key(key) rex_str = '(^' key.each_with_index do |attr,level| lev = (level < 1 ? '' : ('\\s\\s' * level)) if lev != '' rex_str += '(?:' + lev + '.*\\n)*' end if level == key.count - 1 if level == 0 rex_str = '(^)(' + attr + ':[^\\n]*\\n(?:\\s\\s[^\\n]*\\n)*)' else rex_str += ')(' + lev + attr + ':[^\\n]*\\n(?:' + lev + '\\s\\s[^\\n]*\\n)*)' end else rex_str += lev + attr + ':[^\\n]*\\n' end end # match result 1 is the parent key structure leading up to the key to be extracted. # match result 2 is the key with all child elements to be extracted. rex = Regexp.new(rex_str) if @content =~ rex # cache the key contents key_content = $2 # remove the key from the main contents. @content.gsub!(rex, "\\1") # and separate into lines. lines = extract_to_array(key_content) ret = [] base_key = key.length == 1 ? [] : key[0...-1] last_line = nil lines.each do |line| level = line[:level] if level > 0 && line[:key] # go from base 1 to base 0 level -= 1 # make sure the base key is the right length for the current key. while level > base_key.length base_key.push '?' # hopefully this never occurs. end while level < base_key.length base_key.pop end # add our key to the base key. # if the next key is below it, this ensures the parent structure is correct. # if the next key is higher or at the same level the above loops should make it correct. base_key << line[:key] last_line = { key: base_key.dup, value: line[:value].to_s, comment: line[:comment], safe: true } ret << last_line elsif level > 0 && line[:comment] if last_line && last_line[:key].length == level if last_line[:comment] last_line[:comment] += "\n" + line[:comment] else last_line[:comment] = "\n" + line[:comment] end end end end ret else [] end end |
#set_key(key, value, make_safe_value = true) ⇒ Object
Sets a key in the YAML contents. Adds the key if it is missing, replaces it if it already exists.
set_key [ "default", "name" ], "george"
The ‘key’ should be an array defining the path.
Value can be nil, a string, a symbol, a number, or a boolean. Value can also be a hash according to #add_key.
The ‘make_safe_value’ option can be used to provide an explicit text value. This can be useful if you want to add a specific value, like an ERB command.
set_key [ "default", "name" ], "<%= ENV[\"DEFAULT_USER\"] %>", false
Returns the contents object.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/incline/cli/helpers/yaml.rb', line 183 def set_key(key, value, make_safe_value = true) # construct a regular expression to find the value and not confuse it with any other value in the file. rex_str = '^(' key.each_with_index do |attr,level| lev = (level < 1 ? '' : ('\\s\\s' * (level))) if lev != '' rex_str += '(?:' + lev + '.*\\n)*' end if level == key.count - 1 rex_str += lev + attr + ':)\\s*([^#\\n]*)?(#[^\\n]*)?\\n' else rex_str += lev + attr + ':.*\\n' end end rex = Regexp.new(rex_str) if @content =~ rex if make_safe_value value = safe_value(value) value = add_value_offset(key, value) elsif value.is_a?(::Hash) value = value[:value] end value = '' if value =~ /\A\s*\z/ # Capture 1 is everything before the value. # Capture 2 is going to be just the value. # Capture 3 is the comment (if any). This allows us to propagate comments if we change a value. if $2 != value rep = "\\1#{value}\\3\n" @content.gsub! rex, rep end self else add_key(key, value, make_safe_value) end end |
#set_key_with_comment(key, value, comment) ⇒ Object
Sets a key in the YAML contents. Adds the key if it is missing, replaces it if it already exists.
set_key_with_comment [ "default", "name" ], "george", "this is the name of the default user"
The ‘key’ should be an array defining the path. If the ‘comment’ is blank (nil or ”), then it will not modify the comment. Use a whitespace string (‘ ’) to indicate that you want a blank comment added.
Value can be nil, a string, a symbol, a number, or a boolean. Value can also be a hash according to #add_key.
Returns the contents object.
237 238 239 240 241 242 243 |
# File 'lib/incline/cli/helpers/yaml.rb', line 237 def set_key_with_comment(key, value, comment) if comment.to_s == '' set_key key, value else set_key key, value_with_comment(key, value, comment), false end end |
#set_level_comment_offset(level, offset) ⇒ Object
Sets the comment offset for the specified level.
The offset is based on the beginning of the level in question. There will always be at least one whitespace before the value and at least one whitespace between the value and a comment. For instance an offset of 15 for level 2 would be like this:
one:
two: value # comment
some_long_name: value # comment
# 012345678901234^
512 513 514 |
# File 'lib/incline/cli/helpers/yaml.rb', line 512 def set_level_comment_offset(level, offset) comment_offsets[level] = offset end |
#set_level_value_offset(level, offset) ⇒ Object
Sets the value offset for the specified level.
The offset is based on the beginning of the level in question. There will always be at least one whitespace before the value. For instance an offset of 10 for level 2 would be like this:
one:
two: value
some_long_name: value
# 0123456789^
478 479 480 |
# File 'lib/incline/cli/helpers/yaml.rb', line 478 def set_level_value_offset(level, offset) value_offsets[level] = offset end |
#to_s ⇒ Object
Returns the YAML contents.
24 25 26 |
# File 'lib/incline/cli/helpers/yaml.rb', line 24 def to_s @content end |