using_yaml 0.3.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/VERSION +1 -1
- data/lib/using_yaml/array.rb +29 -0
- data/lib/using_yaml/hash.rb +57 -0
- data/lib/using_yaml/nilclass.rb +33 -0
- data/lib/using_yaml.rb +128 -25
- data/spec/spec_helper.rb +2 -2
- data/spec/using_yaml/path_spec.rb +2 -2
- data/spec/using_yaml/using_yaml_spec.rb +41 -11
- data/using_yaml.gemspec +5 -5
- metadata +5 -5
- data/lib/using_yaml/open_hash.rb +0 -46
- data/lib/using_yaml/patches/array.rb +0 -5
- data/lib/using_yaml/patches/hash.rb +0 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module UsingYAML
|
2
|
+
# Calls add_extensions on all children. Also defines a +save+ method
|
3
|
+
# iff this is a top-level UsingYAML object (see below for details).
|
4
|
+
def self.add_array_extensions(array, pathname)
|
5
|
+
# Here we define a Module to extend the array
|
6
|
+
extensions = Module.new do
|
7
|
+
# Iterate over the items
|
8
|
+
array.each do |item|
|
9
|
+
# Recursively continue to extend.
|
10
|
+
UsingYAML.add_extensions(item)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Define a save method if a pathname was supplied (only happens
|
14
|
+
# on the top-level object - that is, example.foo will have a
|
15
|
+
# +save+, but example.foo.bar will not).
|
16
|
+
if pathname
|
17
|
+
define_method(:save) do
|
18
|
+
# Serialise using +to_yaml+
|
19
|
+
File.open(pathname, 'w') do |f|
|
20
|
+
f.puts self.to_yaml
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Load in the extensions for this instance
|
27
|
+
array.extend(extensions)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module UsingYAML
|
2
|
+
# Calls add_extensions on all children. Also defines a +save+ method
|
3
|
+
# iff this is a top-level UsingYAML object (see below for details).
|
4
|
+
def self.add_hash_extensions(hash, pathname)
|
5
|
+
# Here we define a Module to extend the hash
|
6
|
+
extensions = Module.new do
|
7
|
+
# Recursively continue to extend.
|
8
|
+
hash.each_pair do |key, value|
|
9
|
+
UsingYAML.add_extensions(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
define_method(:method_missing) do |*args|
|
13
|
+
name = args.shift.to_s
|
14
|
+
|
15
|
+
if args.empty?
|
16
|
+
send(:[], name) || UsingYAML.add_extensions(nil)
|
17
|
+
elsif args.size == 1 && name =~ /(.+)=/
|
18
|
+
# This is an "alias" turning self.key= into self[key]=
|
19
|
+
# Also extends the incoming value so that it behaves
|
20
|
+
# consistently with the other key-value pairs.
|
21
|
+
key = $1
|
22
|
+
value = args.first
|
23
|
+
|
24
|
+
# Save the value in the hashtable
|
25
|
+
send(:[]=, key, UsingYAML.add_extensions(value))
|
26
|
+
|
27
|
+
# Define the new reader (as above)
|
28
|
+
new_reader_extension = Module.new do
|
29
|
+
define_method(key) do
|
30
|
+
send(:[], key) || UsingYAML.add_extensions(nil)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
extend(new_reader_extension)
|
34
|
+
|
35
|
+
value
|
36
|
+
else
|
37
|
+
super(name, args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Define a save method if a pathname was supplied (only happens
|
42
|
+
# on the top-level object - that is, example.foo will have a
|
43
|
+
# +save+, but example.foo.bar will not).
|
44
|
+
if pathname
|
45
|
+
define_method(:save) do
|
46
|
+
# Serialise using +to_yaml+
|
47
|
+
File.open(pathname, 'w') do |f|
|
48
|
+
f.puts self.to_yaml
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Load in the extensions for this instance
|
55
|
+
hash.extend(extensions)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module UsingYAML
|
2
|
+
def self.add_nilclass_extensions(instance, pathname)
|
3
|
+
extensions = Module.new do
|
4
|
+
define_method(:method_missing) do
|
5
|
+
# Child objects should not have #save
|
6
|
+
if respond_to? :save
|
7
|
+
add_extensions(nil)
|
8
|
+
else
|
9
|
+
# One nil is the same as the next :)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Define a save method if a pathname was supplied (only happens
|
15
|
+
# on the top-level object - that is, example.foo will have a
|
16
|
+
# +save+, but example.foo.bar will not).
|
17
|
+
if pathname
|
18
|
+
# Being nil translates to "no file", not to "empty file", so we
|
19
|
+
# want to actually delete any existing file. This is a semantic
|
20
|
+
# difference, but important: there is a huge different between
|
21
|
+
# an _empty_ file and a _non-existant_ file. YAML does not
|
22
|
+
# reflect this difference, so we do.
|
23
|
+
define_method(:save) do
|
24
|
+
# If we can't delete it (permissions, ENOENT..), then there
|
25
|
+
# ain't much we can do, so just squelch the error.
|
26
|
+
FileUtils.rm(pathname) rescue nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
instance.extend(extensions)
|
32
|
+
end
|
33
|
+
end
|
data/lib/using_yaml.rb
CHANGED
@@ -1,23 +1,69 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'pathname'
|
3
|
-
require 'using_yaml/
|
4
|
-
require 'using_yaml/
|
5
|
-
require 'using_yaml/
|
3
|
+
require 'using_yaml/array'
|
4
|
+
require 'using_yaml/hash'
|
5
|
+
require 'using_yaml/nilclass'
|
6
6
|
|
7
|
+
# UsingYAML provides convenient class extensions which make it easy to
|
8
|
+
# interact with YAML-storage of settings files by defining accessors
|
9
|
+
# which allow one to talk to the files as if they were fully
|
10
|
+
# implemented classes.
|
11
|
+
#
|
12
|
+
# Each of these files will be automagically enhanced to provide save
|
13
|
+
# and clean methods of talking to your YAML files without needing to
|
14
|
+
# worry about checking for nil's, re-serializing or long-winded hash
|
15
|
+
# access.
|
16
|
+
#
|
17
|
+
# This module also provides some static access to make it easy to
|
18
|
+
# enable/disable options such as squelching error messages (in
|
19
|
+
# production) and tracking where instances should load their files
|
20
|
+
# from.
|
21
|
+
#
|
22
|
+
# See +using_yaml+ for usage information.
|
7
23
|
module UsingYAML
|
8
24
|
class << self
|
25
|
+
# Extends the incoming Array/Hash with magic which makes it
|
26
|
+
# possible to +save+ and (in the case of Hashes) use methods
|
27
|
+
# instead of []-access.
|
28
|
+
def add_extensions(object, pathname = nil)
|
29
|
+
case object
|
30
|
+
when Array
|
31
|
+
add_array_extensions(object, pathname)
|
32
|
+
when Hash
|
33
|
+
add_hash_extensions(object, pathname)
|
34
|
+
when NilClass
|
35
|
+
add_nilclass_extensions(object, pathname)
|
36
|
+
end
|
37
|
+
|
38
|
+
object
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the path where the given class will load from. See
|
42
|
+
# +using_yaml_path+ for information on how to override this on a
|
43
|
+
# per-instance basis.
|
9
44
|
def path(klass)
|
10
45
|
return @@path[klass] if defined?(@@path)
|
11
46
|
end
|
12
47
|
|
48
|
+
# Sets the the path where the given class will load from. See
|
49
|
+
# +using_yaml_path+ for information on how to override this on a
|
50
|
+
# per-instance basis.
|
13
51
|
def path=(args)
|
14
52
|
(@@path ||= {})[args.first] = args.last
|
15
53
|
end
|
16
54
|
|
55
|
+
# Because of the "safety" UsingYAML provides, it is easy for typos
|
56
|
+
# to creep in, which might result in unexpected nil's. UsingYAML
|
57
|
+
# therefore is verbose in warning about these potential errors.
|
58
|
+
#
|
59
|
+
# This method disables those warnings, which is useful in a
|
60
|
+
# production environment or if you are sure you aren't making mistakes.
|
17
61
|
def squelch!
|
18
62
|
@@squelched = true
|
19
63
|
end
|
20
64
|
|
65
|
+
# Returns true if UsingYAML will warn about potential typos (or
|
66
|
+
# similar) which might otherwise be masked.
|
21
67
|
def squelched?
|
22
68
|
defined?(@@squelched) && @@squelched
|
23
69
|
end
|
@@ -28,56 +74,113 @@ module UsingYAML
|
|
28
74
|
end
|
29
75
|
|
30
76
|
module ClassMethods
|
77
|
+
# Used to configure UsingYAML for a class by defining what files
|
78
|
+
# should be loaded and from where.
|
79
|
+
#
|
80
|
+
# include UsingYAML
|
81
|
+
# using_yaml :foo, :bar, :path => "/some/where"
|
82
|
+
#
|
83
|
+
# +args+ can contain either filenames or a hash which specifices a
|
84
|
+
# path which contains the corresponding files.
|
85
|
+
#
|
86
|
+
# The value of :path must either be a string or Proc (see
|
87
|
+
# +using_yaml_path+ for more information on overriding paths).
|
31
88
|
def using_yaml(*args)
|
89
|
+
# Include the instance methods which provide accessors and
|
90
|
+
# mutators for reading/writing from/to the YAML objects.
|
32
91
|
include InstanceMethods
|
33
|
-
|
92
|
+
|
93
|
+
# Each argument is either a filename or a :path option
|
34
94
|
args.each do |arg|
|
35
95
|
case arg
|
36
96
|
when Symbol, String
|
37
|
-
|
38
|
-
using_yaml_file(
|
97
|
+
# Define accessors for this file
|
98
|
+
using_yaml_file(arg.to_s)
|
39
99
|
when Hash
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
filename = key
|
46
|
-
options = value
|
47
|
-
using_yaml_file(filename)
|
48
|
-
end
|
49
|
-
end
|
100
|
+
# Currently only accepts { :path => ... }
|
101
|
+
next unless arg.size == 1 && arg.keys.first == :path
|
102
|
+
|
103
|
+
# Take note of the path
|
104
|
+
UsingYAML.path = [self.inspect, arg.values.first]
|
50
105
|
end
|
51
106
|
end
|
52
107
|
end
|
53
108
|
|
109
|
+
# Special attr_accessor for the suppiled +filename+ such that
|
110
|
+
# files are intelligently loaded/written to disk.
|
111
|
+
#
|
112
|
+
# using_yaml_file(:foo) # => attr_accessor(:foo) + some magic
|
113
|
+
#
|
114
|
+
# If class Example is setup with the above, then:
|
115
|
+
#
|
116
|
+
# example = Example.new
|
117
|
+
# example.foo # => loads from foo.yml
|
118
|
+
# example.foo.bar # => equivalent to example.foo['bar']
|
119
|
+
# example.foo.save # => serialises to foo.yml
|
120
|
+
#
|
54
121
|
def using_yaml_file(filename)
|
122
|
+
# Define an reader for filename such that the corresponding
|
123
|
+
# YAML file is loaded. Example: using_yaml_file(:foo) will look
|
124
|
+
# for foo.yml in the specified path.
|
55
125
|
define_method(filename) do
|
126
|
+
# Work out the absolute path for the filename and get a handle
|
127
|
+
# on the cachestore for that file.
|
56
128
|
pathname = using_yaml_path.join("#{filename}.yml").expand_path
|
57
|
-
|
58
|
-
data = @using_yaml_cache[pathname]
|
59
|
-
return data if data
|
129
|
+
yaml = (@using_yaml_cache ||= {})[pathname]
|
60
130
|
|
131
|
+
# If the yaml exists in our cache, then we don't need to hit
|
132
|
+
# the disk.
|
133
|
+
return yaml if yaml
|
134
|
+
|
135
|
+
# Safe disk read which either reads and parses a YAML object
|
136
|
+
# (and caches it against future reads) or graciously ignores
|
137
|
+
# the file's existence. Note that an error will appear on
|
138
|
+
# stderr to avoid typos (or similar) from causing unexpected
|
139
|
+
# behavior. See +UsingYAML.squelch!+ if you wish to hide the
|
140
|
+
# error.
|
61
141
|
begin
|
62
|
-
|
63
|
-
@using_yaml_cache[pathname] = data
|
142
|
+
@using_yaml_cache[pathname] = UsingYAML.add_extensions(YAML.load_file(pathname), pathname)
|
64
143
|
rescue Exception => e
|
65
144
|
$stderr.puts "(UsingYAML) Could not load #{filename}: #{e.message}" unless UsingYAML.squelched?
|
66
|
-
nil
|
145
|
+
UsingYAML.add_extensions(nil)
|
67
146
|
end
|
68
147
|
end
|
148
|
+
|
149
|
+
# Define a writer for filename such that the incoming object is
|
150
|
+
# treated as a UsingYAML-ized Hash (magical properties). Be
|
151
|
+
# aware that the incoming object will not be saved to disk
|
152
|
+
# unless you explicitly do so.
|
153
|
+
define_method("#{filename}=".to_sym) do |object|
|
154
|
+
# Work out the absolute path for the filename and get a handle
|
155
|
+
# on the cachestore for that file.
|
156
|
+
pathname = using_yaml_path.join("#{filename}.yml").expand_path
|
157
|
+
(@using_yaml_cache ||= {})[pathname] = UsingYAML.add_extensions(object, pathname)
|
158
|
+
end
|
69
159
|
end
|
70
160
|
end
|
71
161
|
|
162
|
+
# Instance methods which allow us to define where YAML files are
|
163
|
+
# read/written from/to.
|
72
164
|
module InstanceMethods
|
73
|
-
|
74
|
-
|
165
|
+
# Reader which determines where to find files according to the
|
166
|
+
# following recipe:
|
167
|
+
#
|
168
|
+
# (1) Load the :path option from +using_yaml+, if set
|
169
|
+
# (2) Possibly invoke a Proc (if supplied to step 1)
|
170
|
+
# (3) Default to the current location if 1 & 2 failed
|
171
|
+
#
|
172
|
+
# You can, of course, overrite this method if you wish to supply
|
173
|
+
# your own logic.
|
75
174
|
def using_yaml_path
|
175
|
+
return @using_yaml_path unless @using_yaml_path.nil?
|
176
|
+
|
76
177
|
path = UsingYAML.path(self.class.name)
|
77
178
|
path = path.call(self) if path.is_a? Proc
|
78
|
-
@using_yaml_path
|
179
|
+
@using_yaml_path = Pathname.new(path || '.')
|
79
180
|
end
|
80
181
|
|
182
|
+
# Sets the YAML load path to the given argument by invoking
|
183
|
+
# +Pathname.new+ on +path+.
|
81
184
|
def using_yaml_path=(path)
|
82
185
|
@using_yaml_path = path && Pathname.new(path)
|
83
186
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,8 +5,8 @@ describe "UsingYAML#paths" do
|
|
5
5
|
@person = Person.new
|
6
6
|
end
|
7
7
|
|
8
|
-
it "should return
|
9
|
-
@person.using_yaml_path.expand_path.to_s.should ==
|
8
|
+
it "should return pwd for non-existant pathnames" do
|
9
|
+
@person.using_yaml_path.expand_path.to_s.should == `pwd`.strip
|
10
10
|
end
|
11
11
|
|
12
12
|
it "should return path/to/defined for globally set pathnames" do
|
@@ -1,23 +1,53 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
2
|
|
3
|
-
describe 'UsingYAML
|
3
|
+
describe 'UsingYAML' do
|
4
4
|
before(:each) do
|
5
5
|
@person = Person.new
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
describe 'robustness' do
|
9
|
+
it "should return nil for invalid settings files" do
|
10
|
+
@person.children.should be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should gracefully handle nil.nil..." do
|
14
|
+
@person.children.invalid.call.should be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'hashes' do
|
19
|
+
it "should work" do
|
20
|
+
YAML.stubs(:load_file).with(anything).returns({ 'foo' => 'bar' })
|
21
|
+
@person.children.should == { 'foo' => 'bar' }
|
22
|
+
@person.children.foo.should == 'bar'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should define methods when a hash gets a new key" do
|
26
|
+
YAML.stubs(:load_file).with(anything).returns({})
|
27
|
+
@person.children.foo = 'bar'
|
28
|
+
@person.children.foo.should == 'bar'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should catchup when a hash gets a new key via []=" do
|
32
|
+
YAML.stubs(:load_file).with(anything).returns({})
|
33
|
+
@person.children['foo'] = 'bar'
|
34
|
+
@person.children.foo.should == 'bar'
|
35
|
+
end
|
10
36
|
end
|
11
37
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
38
|
+
describe 'arrays' do
|
39
|
+
it "should work" do
|
40
|
+
YAML.stubs(:load_file).with(anything).returns([ { 'foo' => 'bar' } ])
|
41
|
+
@person.children.class.name.should == 'Array'
|
42
|
+
@person.children.first.should == { 'foo' => 'bar' }
|
43
|
+
end
|
16
44
|
end
|
17
45
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
46
|
+
describe 'assignments' do
|
47
|
+
it "should work" do
|
48
|
+
@person.children = [ :example ]
|
49
|
+
@person.children.should == [ :example ]
|
50
|
+
@person.children.respond_to?(:save).should == true
|
51
|
+
end
|
22
52
|
end
|
23
53
|
end
|
data/using_yaml.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{using_yaml}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Marc Bowes"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-03-06}
|
13
13
|
s.description = %q{"Load, save and use YAML files as if they were objects"}
|
14
14
|
s.email = %q{marcbowes@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -24,9 +24,9 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/using_yaml.rb",
|
27
|
-
"lib/using_yaml/
|
28
|
-
"lib/using_yaml/
|
29
|
-
"lib/using_yaml/
|
27
|
+
"lib/using_yaml/array.rb",
|
28
|
+
"lib/using_yaml/hash.rb",
|
29
|
+
"lib/using_yaml/nilclass.rb",
|
30
30
|
"spec/spec_helper.rb",
|
31
31
|
"spec/using_yaml/path_spec.rb",
|
32
32
|
"spec/using_yaml/using_yaml_spec.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: using_yaml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Bowes
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-03-06 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,9 +39,9 @@ files:
|
|
39
39
|
- Rakefile
|
40
40
|
- VERSION
|
41
41
|
- lib/using_yaml.rb
|
42
|
-
- lib/using_yaml/
|
43
|
-
- lib/using_yaml/
|
44
|
-
- lib/using_yaml/
|
42
|
+
- lib/using_yaml/array.rb
|
43
|
+
- lib/using_yaml/hash.rb
|
44
|
+
- lib/using_yaml/nilclass.rb
|
45
45
|
- spec/spec_helper.rb
|
46
46
|
- spec/using_yaml/path_spec.rb
|
47
47
|
- spec/using_yaml/using_yaml_spec.rb
|
data/lib/using_yaml/open_hash.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
module OpenHash
|
2
|
-
class << self
|
3
|
-
def from_hash(hash, pathname = nil)
|
4
|
-
Module.new do
|
5
|
-
hash.each_pair do |key, value|
|
6
|
-
define_method(key) do
|
7
|
-
value.respond_to?(:to_ohash) ? value.to_ohash(value) : value
|
8
|
-
end
|
9
|
-
|
10
|
-
define_method("#{key}=") do |value|
|
11
|
-
case value
|
12
|
-
when Hash
|
13
|
-
send(:[]=, key, value.to_ohash)
|
14
|
-
else
|
15
|
-
send(:[]=, key, value)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
if pathname
|
21
|
-
define_method(:save) do
|
22
|
-
File.open(pathname, 'w') do |f|
|
23
|
-
f.puts self.to_yaml
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def from_array(array, pathname = nil)
|
31
|
-
Module.new do
|
32
|
-
array.each do |e|
|
33
|
-
e.to_ohash(e)
|
34
|
-
end
|
35
|
-
|
36
|
-
if pathname
|
37
|
-
define_method(:save) do
|
38
|
-
File.open(pathname, 'w') do |f|
|
39
|
-
f.puts self.to_yaml
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|