Skip to content

Instantly share code, notes, and snippets.

@thinkerbot
Created December 7, 2008 21:23
Show Gist options
  • Save thinkerbot/33250 to your computer and use it in GitHub Desktop.
Save thinkerbot/33250 to your computer and use it in GitHub Desktop.
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