Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
YAML dump/load for simple hashes
# = Description
#
# SimpleYaml provides methods to dump and load simple configurations.
# Support is limited strings, symbols, booleans, nil, and numbers.
# Values may include non-nested arrays. Nested hashes are not
# supported anywhere.
#
# include SimpleYaml
# hash = {
# :sym => 'str',
# 'true' => true,
# 'false' => false,
# 'nil' => nil,
# 'number' => 100,
# 'array' => ['str', :sym, true, false, 100]
# }
#
# # dump format is identical to YAML
# dump(hash) == YAML.dump(hash) # => true
#
# # loading reproduces the hash
# load(dump(hash)) # => hash
#
# Unsupported objects like hashes, ranges, times, arrays with nested
# arrays/hashes, etc. all raise errors.
#
# dump(:hash_value => {}) # !> ArgumentError
# dump(:range => 1..10) # !> ArgumentError
# dump(:time => Time.now) # !> ArgumentError
# dump(:nested_array => [[]]) # !> ArgumentError
#
# Load overlooks comments and empty lines, but similarly can only
# parse simple key-value pairs.
#
# str = %Q{
# # comments are allowed
# key: value
# array:
# - 1
# - true
# - :sym
# }
#
# load(str) # => {'key' => 'value', 'array' => [1, true, :sym]}
#
# == Info
#
# Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com]
# Contact:: simon.a.chiang@gmail.com
# Licence:: MIT-Style
#
module SimpleYaml
module_function
# Reads and load a hash from path.
def load_file(path)
load File.read(path)
end
# Loads a limited hash from a YAML-formatted string.
def load(str)
hash = {}
array = nil
str.split(/\r?\n/).reverse_each do |line|
case line
when /^\s*-\s+(.*?)\s*$/
# array value
(array ||= []).unshift(parse($1))
when /^(.+?):\s*(.*?)\s*$/
# key-value pair
key = parse($1)
if $2.empty? && array
hash[key] = array
array = nil
else
hash[key] = parse($2)
end
when /^\s*(#.*?)?$/, /^---\s*$/
# comment, document, or empty line
next
else raise "unparseable line: #{line.inspect}"
end
end
hash
end
# helper to parse and object from a string
def parse(str) # :nodoc:
case str
when "true" then true
when "false" then false
when "" then nil
when /^\d+(\.\d+)?$/ then $1 ? str.to_f : str.to_i
when /^:(.*)$/ then $1.to_sym
when /^"(.*)"$/ then $1
else str
end
end
# Dumps a limited hash as YAML to the target.
def dump(hash, target="")
target << "--- \n"
hash.each_pair do |key, value|
target << format(key)
target << ": "
case value
when Array
target << "\n"
value.each do |value|
target << "- #{format(value)}\n"
end
else
target << format(value)
target << "\n"
end
end
target
end
# helper method to format objects for dump
def format(obj) # :nodoc:
case obj
when String
case obj
when "true", "false" then obj.inspect
when /(\A\s)|\n\t|(\s\Z)/ then obj.inspect
else obj
end
when Symbol then obj.inspect
when true, false, nil, Numeric then obj.to_s
else raise ArgumentError, "unsupported object: #{obj}"
end
end
end
require 'test/unit'
require 'simple_yaml'
require 'yaml'
require 'tempfile'
class TaskTest < Test::Unit::TestCase
include SimpleYaml
def test_documentation
hash = {
:sym => 'str',
'true' => true,
'false' => false,
'nil' => nil,
'number' => 100,
'array' => ['str', :sym, true, false, 100]
}
# dump format is identical to YAML
assert_equal YAML.dump(hash), dump(hash)
# loading reproduces the hash
assert_equal hash, load(dump(hash))
assert_raise(ArgumentError) { dump(:hash_value => {}) }
assert_raise(ArgumentError) { dump(:range => 1..10) }
assert_raise(ArgumentError) { dump(:time => Time.now) }
assert_raise(ArgumentError) { dump(:nested_array => [[]]) }
str = %Q{
# comments are allowed
key: value
array:
- 1
- true
- :sym
}
assert_equal({'key' => 'value', 'array' => [1, true, :sym]}, load(str))
end
def test_dump_load_empty_hash
assert_equal({}, load(dump({})))
end
def test_require_and_load_time
tempfile = Tempfile.new("load_time")
tempfile << {
:one => 'one',
:two => 'two',
:array => [1,2,3]
}.to_yaml
tempfile.close
cmd = %Q{ruby -e 'start = Time.now; require "%s"; %s.load_file("#{tempfile.path}"); puts "%s: \#{Time.now-start}"'}
puts
puts "require and load:"
system(cmd % ['yaml', 'YAML', 'yaml '])
system(cmd % ['simple_yaml', 'SimpleYaml', 'simple'])
end
#
# specific cases
#
def test_load_gemrc
gemrc = %Q{---
:benchmark: false
:bulk_threshold: 1000
:verbose: true
:update_sources: true
:sources:
- http://gems.rubyforge.org/
- http://gems.github.com
:backtrace: false
}
expected = {
:benchmark => false,
:bulk_threshold => 1000,
:verbose => true,
:update_sources => true,
:sources => ['http://gems.rubyforge.org/', 'http://gems.github.com'],
:backtrace => false
}
assert_equal expected, load(gemrc)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.