Created
December 7, 2008 21:23
-
-
Save thinkerbot/33250 to your computer and use it in GitHub Desktop.
YAML dump/load for simple hashes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# = 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:: [email protected] | |
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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