delano-storable 0.5.1 → 0.5.2
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/CHANGES.txt +12 -0
- data/README.rdoc +30 -0
- data/lib/storable/orderedhash.rb +199 -0
- data/lib/storable.rb +28 -26
- data/storable.gemspec +3 -3
- metadata +2 -11
data/CHANGES.txt
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
STORABLE, CHANGES
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
#### 0.5.2 (2009-05-12) #############################
|
|
5
|
+
|
|
6
|
+
* CHANGE: Put OrderedHash into Storable namespace and imported merge fix from Caesars
|
|
7
|
+
* FIXED: Circular dependency with Sysinfo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
#### 0.5.1 (2009-05-07) #############################
|
|
11
|
+
|
|
12
|
+
* FIXED: Bug in from_hash which was incorrectly parsing some data types (incl. Integer)
|
|
13
|
+
* ADDED: bin/example
|
|
14
|
+
* ADDED: OrderedHash for Ruby 1.8.x (still unordered in JRuby. Need to investigate.)
|
|
15
|
+
|
|
4
16
|
#### 0.5 (2009-05-07) ###############################
|
|
5
17
|
|
|
6
18
|
* First public release. See commit history for solutious-stella, solutious-rudy,
|
data/README.rdoc
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)
|
|
4
4
|
|
|
5
|
+
== Example
|
|
6
|
+
|
|
7
|
+
require 'storable'
|
|
8
|
+
|
|
9
|
+
class Machine < Storable
|
|
10
|
+
field :environment # Define field names for Machine. The
|
|
11
|
+
field :role # default type is String, but you can
|
|
12
|
+
field :position => Integer # specify a type using a hash syntax.
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
mac1 = Machine.new # Instances of Machine have accessors
|
|
16
|
+
mac1.environment = "stage" # just like regular attributes.
|
|
17
|
+
mac1.role = "app"
|
|
18
|
+
mac1.position = 1
|
|
19
|
+
|
|
20
|
+
puts "# YAML", mac1.to_yaml # Note: the field order is maintained
|
|
21
|
+
puts "# CSV", mac1.to_csv # => stage,app,1
|
|
22
|
+
puts "# JSON", mac1.to_json # Note: field order not maintained.
|
|
23
|
+
|
|
24
|
+
mac2 = Machine.from_yaml(mac1.to_yaml)
|
|
25
|
+
puts mac2.environment # => "stage"
|
|
26
|
+
puts mac2.position.class # => Fixnum
|
|
27
|
+
|
|
28
|
+
|
|
5
29
|
== Installation
|
|
6
30
|
|
|
7
31
|
Via Rubygems, one of:
|
|
@@ -14,9 +38,15 @@ or via download:
|
|
|
14
38
|
* storable-latest.zip[http://github.com/delano/storable/zipball/latest]
|
|
15
39
|
|
|
16
40
|
|
|
41
|
+
== Prerequisites
|
|
42
|
+
|
|
43
|
+
* Ruby 1.8, Ruby 1.9, or JRuby 1.2
|
|
44
|
+
|
|
45
|
+
|
|
17
46
|
== Credits
|
|
18
47
|
|
|
19
48
|
* Delano Mandelbaum (delano@solutious.com)
|
|
49
|
+
* OrderedHash implementation by Jan Molic
|
|
20
50
|
|
|
21
51
|
== License
|
|
22
52
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# AUTHOR
|
|
2
|
+
# jan molic /mig/at/1984/dot/cz/
|
|
3
|
+
#
|
|
4
|
+
# DESCRIPTION
|
|
5
|
+
# Hash with preserved order and some array-like extensions
|
|
6
|
+
# Public domain.
|
|
7
|
+
#
|
|
8
|
+
# THANKS
|
|
9
|
+
# Andrew Johnson for his suggestions and fixes of Hash[],
|
|
10
|
+
# merge, to_a, inspect and shift
|
|
11
|
+
class Storable::OrderedHash < ::Hash
|
|
12
|
+
attr_accessor :order
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def [] *args
|
|
16
|
+
hsh = Storable::OrderedHash.new
|
|
17
|
+
if Hash === args[0]
|
|
18
|
+
hsh.replace args[0]
|
|
19
|
+
elsif (args.size % 2) != 0
|
|
20
|
+
raise ArgumentError, "odd number of elements for Hash"
|
|
21
|
+
else
|
|
22
|
+
0.step(args.size - 1, 2) do |a|
|
|
23
|
+
b = a + 1
|
|
24
|
+
hsh[args[a]] = args[b]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
hsh
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
def initialize(*a, &b)
|
|
31
|
+
super
|
|
32
|
+
@order = []
|
|
33
|
+
end
|
|
34
|
+
def store_only a,b
|
|
35
|
+
store a,b
|
|
36
|
+
end
|
|
37
|
+
alias orig_store store
|
|
38
|
+
def store a,b
|
|
39
|
+
@order.push a unless has_key? a
|
|
40
|
+
super a,b
|
|
41
|
+
end
|
|
42
|
+
alias []= store
|
|
43
|
+
def == hsh2
|
|
44
|
+
return false if @order != hsh2.order
|
|
45
|
+
super hsh2
|
|
46
|
+
end
|
|
47
|
+
def clear
|
|
48
|
+
@order = []
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
def delete key
|
|
52
|
+
@order.delete key
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
def each_key
|
|
56
|
+
@order.each { |k| yield k }
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
def each_value
|
|
60
|
+
@order.each { |k| yield self[k] }
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
def each
|
|
64
|
+
@order.each { |k| yield k,self[k] }
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
alias each_pair each
|
|
68
|
+
def delete_if
|
|
69
|
+
@order.clone.each { |k|
|
|
70
|
+
delete k if yield(k)
|
|
71
|
+
}
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
def values
|
|
75
|
+
ary = []
|
|
76
|
+
@order.each { |k| ary.push self[k] }
|
|
77
|
+
ary
|
|
78
|
+
end
|
|
79
|
+
def keys
|
|
80
|
+
@order
|
|
81
|
+
end
|
|
82
|
+
def first
|
|
83
|
+
{@order.first => self[@order.first]}
|
|
84
|
+
end
|
|
85
|
+
def last
|
|
86
|
+
{@order.last => self[@order.last]}
|
|
87
|
+
end
|
|
88
|
+
def invert
|
|
89
|
+
hsh2 = Hash.new
|
|
90
|
+
@order.each { |k| hsh2[self[k]] = k }
|
|
91
|
+
hsh2
|
|
92
|
+
end
|
|
93
|
+
def reject &block
|
|
94
|
+
self.dup.delete_if &block
|
|
95
|
+
end
|
|
96
|
+
def reject! &block
|
|
97
|
+
hsh2 = reject &block
|
|
98
|
+
self == hsh2 ? nil : hsh2
|
|
99
|
+
end
|
|
100
|
+
def replace hsh2
|
|
101
|
+
@order = hsh2.keys
|
|
102
|
+
super hsh2
|
|
103
|
+
end
|
|
104
|
+
def shift
|
|
105
|
+
key = @order.first
|
|
106
|
+
key ? [key,delete(key)] : super
|
|
107
|
+
end
|
|
108
|
+
def unshift k,v
|
|
109
|
+
unless self.include? k
|
|
110
|
+
@order.unshift k
|
|
111
|
+
orig_store(k,v)
|
|
112
|
+
true
|
|
113
|
+
else
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
def push k,v
|
|
118
|
+
unless self.include? k
|
|
119
|
+
@order.push k
|
|
120
|
+
orig_store(k,v)
|
|
121
|
+
true
|
|
122
|
+
else
|
|
123
|
+
false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
def pop
|
|
127
|
+
key = @order.last
|
|
128
|
+
key ? [key,delete(key)] : nil
|
|
129
|
+
end
|
|
130
|
+
def to_a
|
|
131
|
+
ary = []
|
|
132
|
+
each { |k,v| ary << [k,v] }
|
|
133
|
+
ary
|
|
134
|
+
end
|
|
135
|
+
def to_s
|
|
136
|
+
self.to_a.to_s
|
|
137
|
+
end
|
|
138
|
+
def inspect
|
|
139
|
+
ary = []
|
|
140
|
+
each {|k,v| ary << k.inspect + "=>" + v.inspect}
|
|
141
|
+
'{' + ary.join(", ") + '}'
|
|
142
|
+
end
|
|
143
|
+
def update hsh2
|
|
144
|
+
hsh2.each { |k,v| self[k] = v }
|
|
145
|
+
self
|
|
146
|
+
end
|
|
147
|
+
alias :merge! update
|
|
148
|
+
def merge hsh2
|
|
149
|
+
##self.dup update(hsh2) ## 2009-05-12 -- delano
|
|
150
|
+
update hsh2 ## dup doesn't take an argument
|
|
151
|
+
## and there's no need for it here
|
|
152
|
+
end
|
|
153
|
+
def select
|
|
154
|
+
ary = []
|
|
155
|
+
each { |k,v| ary << [k,v] if yield k,v }
|
|
156
|
+
ary
|
|
157
|
+
end
|
|
158
|
+
def class
|
|
159
|
+
Hash
|
|
160
|
+
end
|
|
161
|
+
def __class__
|
|
162
|
+
Storable::OrderedHash
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
attr_accessor "to_yaml_style"
|
|
166
|
+
def yaml_inline= bool
|
|
167
|
+
if respond_to?("to_yaml_style")
|
|
168
|
+
self.to_yaml_style = :inline
|
|
169
|
+
else
|
|
170
|
+
unless defined? @__yaml_inline_meth
|
|
171
|
+
@__yaml_inline_meth =
|
|
172
|
+
lambda {|opts|
|
|
173
|
+
YAML::quick_emit(object_id, opts) {|emitter|
|
|
174
|
+
emitter << '{ ' << map{|kv| kv.join ': '}.join(', ') << ' }'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
class << self
|
|
178
|
+
def to_yaml opts = {}
|
|
179
|
+
begin
|
|
180
|
+
@__yaml_inline ? @__yaml_inline_meth[ opts ] : super
|
|
181
|
+
rescue
|
|
182
|
+
@to_yaml_style = :inline
|
|
183
|
+
super
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
@__yaml_inline = bool
|
|
190
|
+
end
|
|
191
|
+
def yaml_inline!() self.yaml_inline = true end
|
|
192
|
+
|
|
193
|
+
def each_with_index
|
|
194
|
+
@order.each_with_index { |k, index| yield k, self[k], index }
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
end # class Storable::OrderedHash
|
|
198
|
+
|
|
199
|
+
|
data/lib/storable.rb
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#--
|
|
2
2
|
# TODO: Handle nested hashes and arrays.
|
|
3
3
|
# TODO: to_xml, see: http://codeforpeople.com/lib/ruby/xx/xx-2.0.0/README
|
|
4
|
-
# TODO: Rename to Stuffany
|
|
5
4
|
#++
|
|
6
5
|
|
|
7
|
-
require 'yaml'
|
|
8
|
-
require 'fileutils'
|
|
9
6
|
|
|
7
|
+
USE_ORDERED_HASH = (RUBY_VERSION =~ /1.9/).nil?
|
|
8
|
+
|
|
9
|
+
require 'json' rescue nil
|
|
10
10
|
|
|
11
|
+
require 'yaml'
|
|
12
|
+
require 'fileutils'
|
|
13
|
+
|
|
11
14
|
# Storable makes data available in multiple formats and can
|
|
12
15
|
# re-create objects from files. Fields are defined using the
|
|
13
16
|
# Storable.field method which tells Storable the order and
|
|
14
17
|
# name.
|
|
15
18
|
class Storable
|
|
19
|
+
require 'storable/orderedhash' if USE_ORDERED_HASH
|
|
16
20
|
unless defined?(SUPPORTED_FORMATS) # We can assume all are defined
|
|
17
|
-
VERSION = 5
|
|
21
|
+
VERSION = 0.5
|
|
18
22
|
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze
|
|
19
23
|
SUPPORTED_FORMATS = [:tsv, :csv, :yaml, :json, :s, :string].freeze
|
|
20
24
|
end
|
|
@@ -46,7 +50,7 @@ class Storable
|
|
|
46
50
|
# The value is not touched when the type is not provided.
|
|
47
51
|
def self.field(args={})
|
|
48
52
|
# TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
|
|
49
|
-
args = {args => nil} unless args.
|
|
53
|
+
args = {args => nil} unless args.kind_of?(Hash)
|
|
50
54
|
|
|
51
55
|
args.each_pair do |m,t|
|
|
52
56
|
|
|
@@ -127,14 +131,15 @@ class Storable
|
|
|
127
131
|
|
|
128
132
|
if field_types[index] == Array
|
|
129
133
|
((value ||= []) << stored_value).flatten
|
|
130
|
-
elsif field_types[index]
|
|
134
|
+
elsif field_types[index].kind_of?(Hash)
|
|
131
135
|
|
|
132
136
|
value = stored_value
|
|
133
137
|
else
|
|
134
138
|
|
|
135
139
|
# SimpleDB stores attribute shit as lists of values
|
|
136
|
-
value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
|
|
137
|
-
|
|
140
|
+
##value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
|
|
141
|
+
value = (stored_value.is_a?(Array) && stored_value.size == 1) ? stored_value.first : stored_value
|
|
142
|
+
|
|
138
143
|
if field_types[index] == Time
|
|
139
144
|
value = Time.parse(value)
|
|
140
145
|
elsif field_types[index] == DateTime
|
|
@@ -145,10 +150,11 @@ class Storable
|
|
|
145
150
|
value = value.to_f
|
|
146
151
|
elsif field_types[index] == Integer
|
|
147
152
|
value = value.to_i
|
|
148
|
-
elsif field_types[index].kind_of?(Storable) && stored_value.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
value =
|
|
153
|
+
elsif field_types[index].kind_of?(Storable) && stored_value.kind_of?(Hash)
|
|
154
|
+
# I don't know why this is here so I'm going to raise an exception
|
|
155
|
+
# and wait a while for an error in one of my other projects.
|
|
156
|
+
#value = field_types[index].from_hash(stored_value)
|
|
157
|
+
raise "Delano, delano, delano. Clean up Storable!"
|
|
152
158
|
end
|
|
153
159
|
end
|
|
154
160
|
|
|
@@ -162,7 +168,7 @@ class Storable
|
|
|
162
168
|
# Return the object data as a hash
|
|
163
169
|
# +with_titles+ is ignored.
|
|
164
170
|
def to_hash(with_titles=true)
|
|
165
|
-
tmp = {}
|
|
171
|
+
tmp = USE_ORDERED_HASH ? Storable::OrderedHash.new : {}
|
|
166
172
|
field_names.each do |fname|
|
|
167
173
|
tmp[fname] = self.send(fname)
|
|
168
174
|
end
|
|
@@ -170,12 +176,11 @@ class Storable
|
|
|
170
176
|
end
|
|
171
177
|
|
|
172
178
|
# Create a new instance of the object from YAML.
|
|
173
|
-
# +from+ a YAML
|
|
174
|
-
def self.from_yaml(from
|
|
175
|
-
|
|
176
|
-
from_str = from.join('')
|
|
179
|
+
# +from+ a YAML String or Array (split into by line).
|
|
180
|
+
def self.from_yaml(*from)
|
|
181
|
+
from_str = [from].flatten.compact.join('')
|
|
177
182
|
hash = YAML::load(from_str)
|
|
178
|
-
hash = from_hash(hash) if hash.
|
|
183
|
+
hash = from_hash(hash) if hash.kind_of?(Hash)
|
|
179
184
|
hash
|
|
180
185
|
end
|
|
181
186
|
def to_yaml(with_titles=true)
|
|
@@ -183,21 +188,18 @@ class Storable
|
|
|
183
188
|
end
|
|
184
189
|
|
|
185
190
|
# Create a new instance of the object from a JSON string.
|
|
186
|
-
# +from+ a
|
|
187
|
-
def self.from_json(from
|
|
188
|
-
|
|
189
|
-
# from is an array of strings
|
|
190
|
-
from_str = from.join('')
|
|
191
|
+
# +from+ a YAML String or Array (split into by line).
|
|
192
|
+
def self.from_json(*from)
|
|
193
|
+
from_str = [from].flatten.compact.join('')
|
|
191
194
|
tmp = JSON::load(from_str)
|
|
192
195
|
hash_sym = tmp.keys.inject({}) do |hash, key|
|
|
193
196
|
hash[key.to_sym] = tmp[key]
|
|
194
197
|
hash
|
|
195
198
|
end
|
|
196
|
-
hash_sym = from_hash(hash_sym) if hash_sym.
|
|
199
|
+
hash_sym = from_hash(hash_sym) if hash_sym.kind_of?(Hash)
|
|
197
200
|
hash_sym
|
|
198
201
|
end
|
|
199
202
|
def to_json(with_titles=true)
|
|
200
|
-
require 'json'
|
|
201
203
|
to_hash.to_json
|
|
202
204
|
end
|
|
203
205
|
|
|
@@ -255,7 +257,7 @@ class Storable
|
|
|
255
257
|
next unless values[index]
|
|
256
258
|
hash[key.to_sym] = values[index]
|
|
257
259
|
end
|
|
258
|
-
hash = from_hash(hash) if hash.
|
|
260
|
+
hash = from_hash(hash) if hash.kind_of?(Hash)
|
|
259
261
|
hash
|
|
260
262
|
end
|
|
261
263
|
|
data/storable.gemspec
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@spec = Gem::Specification.new do |s|
|
|
2
2
|
s.name = "storable"
|
|
3
3
|
s.rubyforge_project = "storable"
|
|
4
|
-
s.version = "0.5.
|
|
4
|
+
s.version = "0.5.2"
|
|
5
5
|
s.summary = "Storable: Marshal Ruby classes into and out of multiple formats (yaml, json, csv, tsv)"
|
|
6
6
|
s.description = s.summary
|
|
7
7
|
s.author = "Delano Mandelbaum"
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
# = DEPENDENCIES =
|
|
18
18
|
# Add all gem dependencies
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
# = MANIFEST =
|
|
22
21
|
# The complete list of files to be included in the release. When GitHub packages your gem,
|
|
23
22
|
# it doesn't allow you to run any command that accesses the filesystem. You will get an
|
|
@@ -30,6 +29,7 @@
|
|
|
30
29
|
README.rdoc
|
|
31
30
|
Rakefile
|
|
32
31
|
lib/storable.rb
|
|
32
|
+
lib/storable/orderedhash.rb
|
|
33
33
|
storable.gemspec
|
|
34
34
|
)
|
|
35
35
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: delano-storable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
@@ -12,16 +12,6 @@ cert_chain: []
|
|
|
12
12
|
date: 2009-04-08 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
|
-
- !ruby/object:Gem::Dependency
|
|
16
|
-
name: sysinfo
|
|
17
|
-
type: :runtime
|
|
18
|
-
version_requirement:
|
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
-
requirements:
|
|
21
|
-
- - ">="
|
|
22
|
-
- !ruby/object:Gem::Version
|
|
23
|
-
version: 0.5.0
|
|
24
|
-
version:
|
|
25
15
|
- !ruby/object:Gem::Dependency
|
|
26
16
|
name: RedCloth
|
|
27
17
|
type: :runtime
|
|
@@ -47,6 +37,7 @@ files:
|
|
|
47
37
|
- README.rdoc
|
|
48
38
|
- Rakefile
|
|
49
39
|
- lib/storable.rb
|
|
40
|
+
- lib/storable/orderedhash.rb
|
|
50
41
|
- storable.gemspec
|
|
51
42
|
has_rdoc: true
|
|
52
43
|
homepage: http://solutious.com/
|