gather 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/Manifest.txt +8 -0
- data/README +22 -0
- data/Rakefile +27 -0
- data/lib/changes.rb +315 -0
- data/lib/gather.rb +96 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- metadata +82 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
== gather
|
2
|
+
|
3
|
+
A gem that provides modules to make working with properties a bit easier.
|
4
|
+
|
5
|
+
For example:
|
6
|
+
|
7
|
+
class SumThing
|
8
|
+
include Gather
|
9
|
+
property :thing, :spla, :gumf, :troob
|
10
|
+
end
|
11
|
+
|
12
|
+
s = SumThing.new.gather :spla => 'five', :thing => 'sigma' do
|
13
|
+
troob %w{one two three four}
|
14
|
+
gumf 15 % 4
|
15
|
+
end
|
16
|
+
|
17
|
+
Or
|
18
|
+
|
19
|
+
s = SumThing.new.gather :spla => 'five', :thing => 'sigma' do |s|
|
20
|
+
s.troob = %w{one two three four}
|
21
|
+
s.gumf = 15 % 4
|
22
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
2
|
+
require File.dirname(__FILE__) + '/lib/version.rb'
|
3
|
+
|
4
|
+
# Generate all the Rake tasks
|
5
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
6
|
+
$hoe = Hoe.new('gather', Gather::VERSION) do |p|
|
7
|
+
p.developer('John Anderson', 'panic@semiosix.com')
|
8
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
9
|
+
p.rubyforge_name = 'clevic'
|
10
|
+
# p.extra_deps = [
|
11
|
+
# ['activesupport','>= 2.0.2'],
|
12
|
+
# ]
|
13
|
+
p.extra_dev_deps = [
|
14
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
15
|
+
]
|
16
|
+
|
17
|
+
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
18
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
19
|
+
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
20
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'newgem/tasks' # load /tasks/*.rake
|
24
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
25
|
+
|
26
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
27
|
+
# task :default => [:spec, :features]
|
data/lib/changes.rb
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'gather.rb'
|
2
|
+
|
3
|
+
class NilClass
|
4
|
+
def call_if( *args ); end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Proc
|
8
|
+
alias_method :call_if, :call
|
9
|
+
end
|
10
|
+
|
11
|
+
=begin rdoc
|
12
|
+
Same as Gather, except that history is kept for changes, and on_change
|
13
|
+
is called each time a property value changes.
|
14
|
+
=end
|
15
|
+
module Changes
|
16
|
+
include Gather
|
17
|
+
|
18
|
+
class Change
|
19
|
+
attr_accessor :old, :new
|
20
|
+
def initialize
|
21
|
+
@old = []
|
22
|
+
@new = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Gather values from the hash and the block, using the gather method.
|
27
|
+
unless instance_methods.include? 'initialize'
|
28
|
+
def initialize( hash = {}, &block )
|
29
|
+
init_properties( hash, &block )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# should probably use an object creation hook to do this
|
34
|
+
def init_properties( hash, &block )
|
35
|
+
@changes = {}
|
36
|
+
gather( hash, &block )
|
37
|
+
end
|
38
|
+
|
39
|
+
def changed?
|
40
|
+
changes.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def changes( *symbols )
|
44
|
+
if symbols.empty?
|
45
|
+
@changes ||= {}
|
46
|
+
else
|
47
|
+
symbols.map{|x| changes[x] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# called after the property has changed
|
52
|
+
# including classes should define this
|
53
|
+
def on_change( property, old, new )
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_writer :change_history
|
57
|
+
def change_history
|
58
|
+
@change_history ||= []
|
59
|
+
end
|
60
|
+
|
61
|
+
# make a mark in the change history
|
62
|
+
def mark
|
63
|
+
new_changes = @changes.clone
|
64
|
+
new_changes.each{|k,v| new_changes[k] = v.clone}
|
65
|
+
change_history << new_changes
|
66
|
+
end
|
67
|
+
|
68
|
+
# restore the last mark. Do not emit any changes.
|
69
|
+
def restore
|
70
|
+
@changes = change_history.pop unless change_history.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
# reset the passed properties, or all if none are specified
|
74
|
+
def reset!( *properties )
|
75
|
+
if properties.empty?
|
76
|
+
@changes = {}
|
77
|
+
else
|
78
|
+
properties.each {|x| changes[x] = Change.new}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# execute the block, undo the given symbols, and
|
83
|
+
# return the result of the executed block. If no symbols
|
84
|
+
# are given, just use mark and restore
|
85
|
+
def preserve( *symbols )
|
86
|
+
mark if symbols.empty?
|
87
|
+
retval = yield
|
88
|
+
if symbols.empty?
|
89
|
+
restore
|
90
|
+
else
|
91
|
+
symbols.each {|symbol| undo( symbol ) }
|
92
|
+
end
|
93
|
+
retval
|
94
|
+
end
|
95
|
+
|
96
|
+
def undo( symbol )
|
97
|
+
unless changes[symbol].old.empty?
|
98
|
+
current = send( symbol )
|
99
|
+
if changes[symbol] && changes[symbol].old
|
100
|
+
eval "@#{symbol.to_s} = changes[:#{symbol.to_s}].old.pop"
|
101
|
+
end
|
102
|
+
current
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add the property methods if
|
107
|
+
# they don't already exist, and if dynamic is in effect, which is
|
108
|
+
# the default. If static is in effect, the normal method_missing
|
109
|
+
# behaviour will be invoked.
|
110
|
+
def method_missing(sym, *args)
|
111
|
+
if self.class.dynamic?
|
112
|
+
self.class.property sym
|
113
|
+
send( sym, *args )
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return a collection of property symbols
|
120
|
+
def property_names
|
121
|
+
self.class.properties.to_a
|
122
|
+
end
|
123
|
+
|
124
|
+
module ClassMethods
|
125
|
+
include Gather::ClassMethods
|
126
|
+
# Remove all but the absolutely essential methods from this class.
|
127
|
+
# Useful if you want a dynamic property list, but
|
128
|
+
# can have some surprising side effects, obviously.
|
129
|
+
def blankslate!
|
130
|
+
# remove unused methods that might clash with user accessors
|
131
|
+
keep_methods = %w( __send__ __id__ self send class inspect instance_eval instance_variables )
|
132
|
+
instance_methods.each do |method|
|
133
|
+
undef_method( method ) unless keep_methods.include?( method )
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def module_name( symbol )
|
138
|
+
"#{symbol.to_s.capitalize}Property"
|
139
|
+
end
|
140
|
+
|
141
|
+
# For each property in symbols, add methods:
|
142
|
+
# instance.property as reader
|
143
|
+
# instance.property( value ) as writer
|
144
|
+
# instance.property = value as writer
|
145
|
+
# instance.before_property {|old,new| ... }
|
146
|
+
# instance.after_property {|old,new| ... }
|
147
|
+
# instance.on_property {|old,new| ... }
|
148
|
+
# instance.property_changed?
|
149
|
+
# instance.undo_property!
|
150
|
+
def property( *symbols )
|
151
|
+
@stripper ||= /^([^\= ]+)\s*\=?\s*$/
|
152
|
+
symbols.each do |sym|
|
153
|
+
property = @stripper.match( sym.to_s )[1]
|
154
|
+
|
155
|
+
# TODO false here until I figure out how to remove properties
|
156
|
+
unless false && const_defined?( module_name( property ).to_sym )
|
157
|
+
own_properties << property.to_sym
|
158
|
+
line, st = __LINE__, <<-EOF
|
159
|
+
module #{module_name( property )}
|
160
|
+
|
161
|
+
public
|
162
|
+
def #{property}(*val)
|
163
|
+
if val.empty?
|
164
|
+
@#{property}
|
165
|
+
else
|
166
|
+
self.#{property} = *val
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def on_#{property}( &block )
|
171
|
+
@on_#{property}_block = block
|
172
|
+
end
|
173
|
+
|
174
|
+
def after_#{property}( &block )
|
175
|
+
@after_#{property}_block = block
|
176
|
+
end
|
177
|
+
|
178
|
+
def before_#{property}( &block )
|
179
|
+
@before_#{property}_block = block
|
180
|
+
end
|
181
|
+
|
182
|
+
def #{property}=(*new_value)
|
183
|
+
unwrapped_value = new_value.size == 1 ? new_value[0] : new_value
|
184
|
+
return if unwrapped_value == @#{property}
|
185
|
+
|
186
|
+
@before_#{property}_block.call_if( @#{property}, unwrapped_value )
|
187
|
+
|
188
|
+
changes[:#{property}] ||= Change.new
|
189
|
+
changes[:#{property}].old << @#{property}
|
190
|
+
changes[:#{property}].new = unwrapped_value
|
191
|
+
|
192
|
+
@#{property} = unwrapped_value
|
193
|
+
|
194
|
+
@after_#{property}_block.call_if( changes[:#{property}].old, unwrapped_value )
|
195
|
+
@on_#{property}_block.call_if( changes[:#{property}].old, unwrapped_value )
|
196
|
+
on_change( :#{property}, changes[:#{property}].old, unwrapped_value )
|
197
|
+
end
|
198
|
+
|
199
|
+
def #{property}_changed?
|
200
|
+
changes.has_key?(:#{property}) && !changes[:#{property}].old.empty?
|
201
|
+
end
|
202
|
+
|
203
|
+
# Undo the last change and return the undone value.
|
204
|
+
# return nil if there were no changes to undo
|
205
|
+
def undo_#{property}!
|
206
|
+
undo( :#{property} )
|
207
|
+
end
|
208
|
+
|
209
|
+
def reset_#{property}!
|
210
|
+
changes[x] = Change.new
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
def direct_#{property}=( value )
|
215
|
+
@#{property} = value
|
216
|
+
end
|
217
|
+
|
218
|
+
end # module
|
219
|
+
EOF
|
220
|
+
class_eval st, __FILE__, line + 1
|
221
|
+
end
|
222
|
+
|
223
|
+
# always include the module, for re-definitions
|
224
|
+
class_eval "include #{property.capitalize}Property"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# not working yet. Once the module has been added, it seems like
|
229
|
+
# adding it again does not redefine the methods, even if they were
|
230
|
+
# removed in the interim.
|
231
|
+
#~ def remove( *symbols )
|
232
|
+
#~ symbols.each do |sym|
|
233
|
+
#~ if defined?( sym )
|
234
|
+
#~ # undefine methods
|
235
|
+
#~ module_const = eval "#{self.name}::#{module_name( sym )}"
|
236
|
+
#~ module_const.instance_methods.each do |im|
|
237
|
+
#~ undef_method( im )
|
238
|
+
#~ end
|
239
|
+
|
240
|
+
#~ # remove from properties
|
241
|
+
#~ properties.delete sym
|
242
|
+
#~ end
|
243
|
+
#~ end
|
244
|
+
#~ end
|
245
|
+
|
246
|
+
# Do not allow accessors to be added dynamically. In other words,
|
247
|
+
# a subclass like this
|
248
|
+
# class IndexCollector
|
249
|
+
# static_properties :row, :column
|
250
|
+
# end
|
251
|
+
# will fail if used like this
|
252
|
+
# collector = IndexCollector.new( :row => 4, :column => 6 ) do
|
253
|
+
# other 'oops'
|
254
|
+
# end
|
255
|
+
# because :other isn't added by static_properties.
|
256
|
+
def static_properties( *syms )
|
257
|
+
@dynamic = false
|
258
|
+
property( *syms )
|
259
|
+
end
|
260
|
+
|
261
|
+
# Allow accessors to be added dynamically, the default.
|
262
|
+
def dynamic_properties( *syms )
|
263
|
+
@dynamic = true
|
264
|
+
property( *syms )
|
265
|
+
end
|
266
|
+
|
267
|
+
def dynamic!; @dynamic = true; end
|
268
|
+
def static!; @dynamic = false; end
|
269
|
+
|
270
|
+
def dynamic?
|
271
|
+
# don't optimise this to @dynamic ||= true, because it will reset
|
272
|
+
# an @dynamic of false to true
|
273
|
+
@dynamic = false if @dynamic.nil?
|
274
|
+
@dynamic
|
275
|
+
end
|
276
|
+
|
277
|
+
# might use this instead of operation
|
278
|
+
#~ def method_added( *args )
|
279
|
+
#~ super
|
280
|
+
#~ end
|
281
|
+
|
282
|
+
# define a method 'name' (which can be symbol or a string)
|
283
|
+
# which takes args and a block
|
284
|
+
# calls gather and then executes the block. Any changes made
|
285
|
+
# from args and block are rolled back
|
286
|
+
# name! will not roll back values
|
287
|
+
def operation( name, &body )
|
288
|
+
# save the block to be executed later
|
289
|
+
class_eval "@@#{name.to_s}_operation_block = body"
|
290
|
+
|
291
|
+
# define the new method called name
|
292
|
+
class_eval <<-EOF, __FILE__, __LINE__.to_i + 1
|
293
|
+
def #{name.to_s}( args = {}, &block )
|
294
|
+
preserve do
|
295
|
+
gather( args, &block )
|
296
|
+
instance_eval( &@@#{name.to_s}_operation_block )
|
297
|
+
end
|
298
|
+
end
|
299
|
+
EOF
|
300
|
+
|
301
|
+
# define new method called name!
|
302
|
+
class_eval <<-EOF, __FILE__, __LINE__.to_i + 1
|
303
|
+
def #{name.to_s}!( args = {}, &block )
|
304
|
+
gather( args, &block )
|
305
|
+
instance_eval( &@@#{name.to_s}_operation_block )
|
306
|
+
end
|
307
|
+
EOF
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.included( base )
|
312
|
+
base.extend( ClassMethods )
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
data/lib/gather.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# Provides instance method gather and class method property
|
4
|
+
# Allows properties to be Enumerable.
|
5
|
+
module Gather
|
6
|
+
# Evaluate the block and gather options from args. Even if it's nil.
|
7
|
+
# Return self
|
8
|
+
def gather( args = {}, &block )
|
9
|
+
unless args.nil?
|
10
|
+
args.each do |key,value|
|
11
|
+
self.send( key, value )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
unless block.nil?
|
16
|
+
if block.arity == -1
|
17
|
+
instance_eval &block
|
18
|
+
else
|
19
|
+
yield self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# return a hash of the properties
|
26
|
+
def to_hash( properties = self.class.properties )
|
27
|
+
properties.inject({}) {|s,x| s[x] = send(x); s}
|
28
|
+
end
|
29
|
+
|
30
|
+
# set all values specified in the hash
|
31
|
+
def merge!( hash )
|
32
|
+
hash.each {|k,v| send( k, v ) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# return a new hash merged with the argument
|
36
|
+
def merge( hash )
|
37
|
+
to_hash.merge( hash )
|
38
|
+
end
|
39
|
+
|
40
|
+
def []( symbol )
|
41
|
+
send( symbol )
|
42
|
+
end
|
43
|
+
|
44
|
+
def []=( symbol, value )
|
45
|
+
send( symbol, value )
|
46
|
+
end
|
47
|
+
|
48
|
+
def each( &block )
|
49
|
+
self.class.properties.each( &block )
|
50
|
+
end
|
51
|
+
include Enumerable
|
52
|
+
|
53
|
+
def self.included( base )
|
54
|
+
base.extend( ClassMethods )
|
55
|
+
end
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
# return properties for only this class
|
59
|
+
def own_properties
|
60
|
+
@properties ||= Set.new
|
61
|
+
end
|
62
|
+
|
63
|
+
# return properties for this class and ancestors
|
64
|
+
def properties
|
65
|
+
own_properties +
|
66
|
+
if superclass.respond_to? :properties
|
67
|
+
superclass.properties
|
68
|
+
else
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def property( *symbols )
|
74
|
+
@stripper ||= /^([^\= ]+)\s*\=?\s*$/
|
75
|
+
symbols.each do |sym|
|
76
|
+
stripped = @stripper.match( sym.to_s )[1]
|
77
|
+
own_properties << stripped.to_sym
|
78
|
+
line, st = __LINE__, <<-EOF
|
79
|
+
def #{stripped}(*val)
|
80
|
+
if val.empty?
|
81
|
+
@#{stripped}
|
82
|
+
else
|
83
|
+
@#{stripped} = val.size == 1 ? val[0] : val
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def #{stripped}=(*val)
|
88
|
+
@#{stripped} = val.size == 1 ? val[0] : val
|
89
|
+
end
|
90
|
+
EOF
|
91
|
+
class_eval st, __FILE__, line + 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:newgem_simple, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:newgem_simple, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gather
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Anderson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-30 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: newgem
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.0
|
34
|
+
version:
|
35
|
+
description: ""
|
36
|
+
email:
|
37
|
+
- panic@semiosix.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- Manifest.txt
|
45
|
+
files:
|
46
|
+
- History.txt
|
47
|
+
- Manifest.txt
|
48
|
+
- README
|
49
|
+
- Rakefile
|
50
|
+
- lib/gather.rb
|
51
|
+
- lib/changes.rb
|
52
|
+
- script/destroy
|
53
|
+
- script/generate
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: A gem that provides modules to make working with properties a bit easier.
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --main
|
59
|
+
- README
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project: clevic
|
77
|
+
rubygems_version: 1.3.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: ""
|
81
|
+
test_files: []
|
82
|
+
|