simplesync 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/LICENSE +16 -0
  2. data/README +40 -0
  3. data/Rakefile +78 -0
  4. data/doc/classes/Hash.html +153 -0
  5. data/doc/classes/Hash.src/M000001.html +28 -0
  6. data/doc/classes/SimpleSync.html +121 -0
  7. data/doc/classes/SimpleSync/Mappings.html +169 -0
  8. data/doc/classes/SimpleSync/Mappings.src/M000026.html +19 -0
  9. data/doc/classes/SimpleSync/Mappings.src/M000027.html +20 -0
  10. data/doc/classes/SimpleSync/Record.html +188 -0
  11. data/doc/classes/SimpleSync/Record.src/M000002.html +22 -0
  12. data/doc/classes/SimpleSync/Record.src/M000003.html +23 -0
  13. data/doc/classes/SimpleSync/Record.src/M000004.html +25 -0
  14. data/doc/classes/SimpleSync/RecordSet.html +235 -0
  15. data/doc/classes/SimpleSync/RecordSet.src/M000028.html +23 -0
  16. data/doc/classes/SimpleSync/RecordSet.src/M000029.html +18 -0
  17. data/doc/classes/SimpleSync/RecordSet.src/M000030.html +19 -0
  18. data/doc/classes/SimpleSync/RecordSet.src/M000031.html +20 -0
  19. data/doc/classes/SimpleSync/RecordSet.src/M000032.html +18 -0
  20. data/doc/classes/SimpleSync/Source.html +356 -0
  21. data/doc/classes/SimpleSync/Source.src/M000005.html +19 -0
  22. data/doc/classes/SimpleSync/Source.src/M000006.html +19 -0
  23. data/doc/classes/SimpleSync/Source.src/M000007.html +19 -0
  24. data/doc/classes/SimpleSync/Source.src/M000008.html +22 -0
  25. data/doc/classes/SimpleSync/Source.src/M000009.html +18 -0
  26. data/doc/classes/SimpleSync/Source.src/M000010.html +20 -0
  27. data/doc/classes/SimpleSync/Source.src/M000011.html +22 -0
  28. data/doc/classes/SimpleSync/Source.src/M000012.html +18 -0
  29. data/doc/classes/SimpleSync/Source.src/M000013.html +18 -0
  30. data/doc/classes/SimpleSync/Syncer.html +408 -0
  31. data/doc/classes/SimpleSync/Syncer.src/M000014.html +18 -0
  32. data/doc/classes/SimpleSync/Syncer.src/M000015.html +18 -0
  33. data/doc/classes/SimpleSync/Syncer.src/M000016.html +18 -0
  34. data/doc/classes/SimpleSync/Syncer.src/M000017.html +18 -0
  35. data/doc/classes/SimpleSync/Syncer.src/M000018.html +18 -0
  36. data/doc/classes/SimpleSync/Syncer.src/M000019.html +18 -0
  37. data/doc/classes/SimpleSync/Syncer.src/M000020.html +18 -0
  38. data/doc/classes/SimpleSync/Syncer.src/M000021.html +18 -0
  39. data/doc/classes/SimpleSync/Syncer.src/M000022.html +35 -0
  40. data/doc/classes/SimpleSync/Syncer.src/M000023.html +44 -0
  41. data/doc/classes/SimpleSync/Syncer.src/M000024.html +41 -0
  42. data/doc/classes/SimpleSync/Syncer.src/M000025.html +25 -0
  43. data/doc/created.rid +1 -0
  44. data/doc/files/LICENSE.html +129 -0
  45. data/doc/files/README.html +155 -0
  46. data/doc/files/lib/simple_sync_rb.html +107 -0
  47. data/doc/fr_class_index.html +33 -0
  48. data/doc/fr_file_index.html +29 -0
  49. data/doc/fr_method_index.html +58 -0
  50. data/doc/index.html +24 -0
  51. data/doc/rdoc-style.css +208 -0
  52. data/lib/simple_sync.rb +387 -0
  53. metadata +115 -0
@@ -0,0 +1,387 @@
1
+ # See README for usage.
2
+ module SimpleSync
3
+ # Syncer represents the sync setup: Add to this Sources via add_source, and use as a hub for the Mappings between them.
4
+ class Syncer
5
+ attr_reader :last_sync
6
+
7
+ # Call with an optional Time object, which will be initiated into the Syncer object as the last_sync time.
8
+ def initialize(last_sync)
9
+ @last_sync = last_sync
10
+ end
11
+
12
+ # Keeps a list of sources in a simple Array
13
+ def sources
14
+ @sources ||= []
15
+ end
16
+
17
+ # Initializes and holds on to the Mappings for this Syncer object.
18
+ def mappings
19
+ @mappings ||= Mappings.new
20
+ end
21
+
22
+ # Adds a Source to the Syncer's list of sources. If a block is supplied, it is sent to Source.new,
23
+ # where it is understood to be a block to be run on a newly-created record object of this source type.
24
+ def add_source(klass, identifier, finder_scope=nil, &block)
25
+ (block_given? ? (sources << Source.new(klass, identifier, mappings, finder_scope, &block)) : (sources << Source.new(klass, identifier, mappings, finder_scope)))[-1]
26
+ end
27
+
28
+ # Lists the new_records from all sources put together. Generally only for `poking around' purposes, not used internally.
29
+ def new_records
30
+ sources.collect {|s| s.new_records.set}.flatten
31
+ end
32
+ # Lists the changed_records from all sources put together. Generally only for `poking around' purposes, not used internally.
33
+ def changed_records
34
+ sources.collect {|s| s.changed_records.set}.flatten
35
+ end
36
+ # Lists the deleted_records from all sources put together. Generally only for `poking around' purposes, not used internally.
37
+ def deleted_records
38
+ sources.collect {|s| s.deleted_records.set}.flatten
39
+ end
40
+
41
+ def inspect # :nodoc:
42
+ "#<SimpleSync::Syncer:#{object_id} sources: #{@sources.collect {|s| s.name}.join(', ')}>"
43
+ end
44
+
45
+ # Takes a snapshot of every source, provided you've set a block to each source's new_records, changed_records, and deleted_records
46
+ # so it knows how to retrieve them.
47
+ def snapshot!
48
+ sources.each {|s| s.snapshot!}
49
+ end
50
+
51
+ # Synchronizes only the deleted_records, from each source one at a time. With each source, first it takes a snapshot of just the
52
+ # deleted_records on just that source, then it loops through the other sources and deletes the record from each of them,
53
+ # after checking to make sure it exists before attempting to delete it. (This is rather inefficient, we should just 'delete' and
54
+ # if it doesn't exist it doesn't matter.)
55
+ def sync_deleted!
56
+ sources.each do |source|
57
+ source.deleted_records.snapshot!
58
+ puts "Deleting #{source.deleted_records.length} records that were deleted on #{source.name}..."
59
+ source.deleted_records.each do |sourcerec|
60
+ sources.except(source).each do |target|
61
+ # Some of these may already be deleted on the other sources. (Especially if we've synced within the last sync time already.)
62
+ # If the record exists on the source,
63
+ targetrec = sourcerec.find_on(target)
64
+ # delete it.
65
+ if targetrec
66
+ puts "\tDeleting #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
67
+ targetrec.delete
68
+ else
69
+ puts "\tSkipped #{sourcerec.identifier} on #{target.name}"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # Synchronizes only the new_records, from each source one at a time. With each source, first it takes a snapshot of just the
77
+ # new_records on just that source, then it loops through the other sources and creates the record on each of them. Since creating
78
+ # new records sometimes requires saving the associated-id back to the original record, the just-updated record is morphed back to
79
+ # see if there are any attributes that should be updated on the original record, and if so, it is also updated with the differences.
80
+ def sync_new!
81
+ sources.each do |source|
82
+ source.new_records.snapshot!
83
+ puts "Creating #{source.new_records.length} records that were created on #{source.name}..."
84
+ source.new_records.each do |sourcerec|
85
+ sources.except(source).each do |target|
86
+ # Need to weed out the ones that were already created
87
+ targetrec = sourcerec.find_on(target)
88
+ if targetrec
89
+ puts "\tSkipped #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
90
+ else
91
+ if (targetrec = sourcerec.morph_to(target)).save
92
+ puts "\tCreated #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
93
+ if sourcerec.morph_to(target).morph_to(source).to_hash != targetrec.morph_to(source).to_hash
94
+ sourcerec.data = targetrec.morph_to(source).to_hash.delete_if {|k,v| v.nil?}
95
+ if sourcerec.save
96
+ puts "\t -> Saved record relationship #{sourcerec.identifier} -> #{targetrec.identifier}."
97
+ else
98
+ puts "\t -> PROBLEM SAVING record relationship #{sourcerec.identifier} -> #{targetrec.identifier} !"
99
+ end
100
+ end
101
+ else
102
+ puts "\tPROBLEM SAVING #{sourcerec.identifier} on #{target.name} !"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ # Synchronizes only the changed_records, from each source one at a time. With each source, first it takes a snapshot of just the
111
+ # changed_records on just that source, then it loops through the other sources and updates them, by grabbing the existing record,
112
+ # updating the attributes, and saving the record back.
113
+ def sync_changed!
114
+ sources.each do |source|
115
+ source.changed_records.snapshot!
116
+ puts "Updating #{source.changed_records.length} records that were updated on #{source.name}..."
117
+ source.changed_records.each do |sourcerec|
118
+ sources.except(source).each do |target|
119
+ # 1) Get the same record from the target
120
+ targetrec = sourcerec.find_on(target)
121
+ # 2) Update the attributes
122
+ target_update = sourcerec.morph_to(target).to_hash.reject {|k,v| !mappings[target => source].has_key?(k) }
123
+ target_attrs = targetrec.to_hash.reject {|k,v| !target_update.has_key?(k)}
124
+ if target_update != target_update # If any data was actually changed.
125
+ targetrec.update_data(target_update)
126
+ # 3) Save the target record
127
+ if targetrec.save
128
+ puts "\tUpdated #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
129
+ else
130
+ puts "\tPROBLEM SAVING #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"+(targetrec.respond_to?(:error) ? targetrec.error.to_s : '')
131
+ end
132
+ else
133
+ puts "\tNo changes mapped from #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # Synchronizes deleted_records, then new_records, then changed_records, simply by calling the sync! method for each of them in turn.
141
+ # By sync-ing deleted_records first and snapshot!-ing the new_records and changed_records when their turn comes, we can be sure
142
+ # those records deleted on one source won't show up in the new_records or changed_records on other sources.
143
+ def sync!
144
+ # To sync, we have all the tools, here comes the magic!
145
+ # 1) Sync DELETED.
146
+ sync_deleted!
147
+ # 2) Sync NEW.
148
+ sync_new!
149
+ # 3) Sync CHANGED.
150
+ sync_changed!
151
+ @last_sync = Time.now
152
+ end
153
+ end
154
+
155
+ # Mappings represents and encapsulates the mappings for a group of sync sources.
156
+ class Mappings
157
+
158
+ # The mappings hash is to be accessed not by a word-index but rather by a single key-value hash of {source => target_source}
159
+ # For example, mappings[@source_a => @source_b] should be set to a hash that represents the attribute mappings where
160
+ # @source_a's attributes are the keys and @source_b's attributes are the values.
161
+ def [](src_tgt)
162
+ source, target = src_tgt.keys[0], src_tgt.values[0]
163
+ mappings[source.name + '--' + target.name] || {}
164
+ end
165
+
166
+ # See above.
167
+ def []=(src_tgt, mapping)
168
+ source, target = src_tgt.keys[0], src_tgt.values[0]
169
+ raise TypeError, "mapping must be a hash" unless mapping.is_a?(Hash)
170
+ mappings[source.name + '--' + target.name] = mapping
171
+ end
172
+
173
+ private
174
+ def mappings
175
+ @mappings ||= {}
176
+ end
177
+ end
178
+
179
+ # Source represents each sync source. There may be more than two. A source must be tied to a Syncer
180
+ # (use Syncer#add_source to create sources), and any two sources must have mappings defined between them.
181
+ class Source
182
+ attr_accessor :klass, :identifier, :finder_scope, :targets, :mappings
183
+ def name # :nodoc:
184
+ "#{klass.name}#{finder_scope ? '('+finder_scope.inspect+')' : ''}"
185
+ end
186
+
187
+ # When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the new_records
188
+ # from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
189
+ # new_records for this source.
190
+ def new_records(&block)
191
+ new_records.getter = block if block_given?
192
+ @new_records ||= RecordSet.new(self)
193
+ end
194
+ # When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the changed_records
195
+ # from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
196
+ # changed_records for this source.
197
+ def changed_records(&block)
198
+ changed_records.getter = block if block_given?
199
+ @changed_records ||= RecordSet.new(self)
200
+ end
201
+ # When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the deleted_records
202
+ # from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
203
+ # deleted_records for this source.
204
+ def deleted_records(&block)
205
+ deleted_records.getter = block if block_given?
206
+ @deleted_records ||= RecordSet.new(self)
207
+ end
208
+
209
+ # Creates a new Source object. Use Syncer#add_source unless you know why you want to call this directly. If a block is
210
+ # supplied, it is understood to be a block to be run on a newly-created record object of this source type.
211
+ def initialize(klass, identifier, mappings, finder_scope, &block)
212
+ @klass = klass
213
+ @identifier = identifier
214
+ @mappings = mappings
215
+ @finder_scope = finder_scope
216
+ @initialize = block if block_given?
217
+ end
218
+
219
+ # Calls klass.get with the :finder_scope option(s) merged into the finder_options.
220
+ def get(finder_options={})
221
+ klass.get(finder_options.merge(@finder_scope || {}))
222
+ end
223
+
224
+ # Initializes a record retrieved by the SimpleSync gem. Simply runs the block supplied to the Source object creation, if one was given.
225
+ def initialize_record(record)
226
+ return record if @initialize.nil?
227
+ record.send(:eval, @initialize.to_ruby).call
228
+ record
229
+ end
230
+
231
+ def inspect # :nodoc:
232
+ "#<SimpleSync::Source #{name}: #{new_records.length} new, #{changed_records.length} changed, #{deleted_records.length} deleted >"
233
+ end
234
+
235
+ # Takes a snapshot of new_records, changed_records and deleted_records. Returns self (the Source object).
236
+ def snapshot!
237
+ raise RuntimeError, "Must send new_records, changed_records, and deleted_records each a block before this function can be run" unless @new_records_getter.is_a?(Proc) && @changed_records_getter.is_a?(Proc) && @deleted_records_getter.is_a?(Proc)
238
+ new_records.snapshot!
239
+ changed_records.snapshot!
240
+ deleted_records.snapshot!
241
+ self
242
+ end
243
+
244
+ # source.mapping_to(target) is synonymous to source.mappings[source => target].
245
+ def mapping_to(target)
246
+ mappings[self => target].merge(self.identifier.to_s => target.identifier.to_s)
247
+ end
248
+ # Returns true or false after checking to see if mappings are defined in source.mappings[source => target].
249
+ def can_map?(target)
250
+ !mappings[self => target].empty?
251
+ end
252
+ end
253
+
254
+ # RecordSet is a class used internally to store collections of records, for new_records, changed_records, and deleted_records.
255
+ # Though not documented here, you can use several of the Array or Enumerable methods: [], <<, each, clear, length.
256
+ # RecordSets are created automatically, so the new method isn't documented either. The biggest thing it does is extend
257
+ # all record objects with the SimpleSync::Record module, which gives them a few essential methods.
258
+ # RecordSet is also where all the snapshot! methods actually do their work.
259
+ class RecordSet
260
+ attr_accessor :getter # :nodoc:
261
+
262
+ def initialize(source,getter_proc=nil) # :nodoc:
263
+ @source = source
264
+ @getter = getter_proc if getter_proc
265
+ end
266
+
267
+ # Takes a snapshot of whatever type of records this RecordSet holds, by using the block assigned to the RecordSet.
268
+ def snapshot!
269
+ if @getter.is_a?(Proc)
270
+ clear
271
+ self << @getter.call
272
+ else
273
+ nil
274
+ end
275
+ end
276
+
277
+ # Returns a simple Array of the collection - provided public mostly for debugging purposes. Typically you won't use this method
278
+ # publicly in production code.
279
+ def set
280
+ @set ||= []
281
+ end
282
+
283
+ def [](i) # :nodoc:
284
+ set[i]
285
+ end
286
+
287
+ def <<(values) # :nodoc:
288
+ [values].flatten.compact.each do |rec|
289
+ rec.extend Record
290
+ rec.source = @source
291
+ @set << rec
292
+ end
293
+ end
294
+
295
+ # Same as RecordSet#find (below), but removes the found record from this RecordSet.
296
+ def delete(record_or_identifier)
297
+ identifier = get_mapped_identifier(record_or_identifier)
298
+ !!(set.reject! {|e| e.identifier == identifier})
299
+ end
300
+
301
+ # Finds a in this RecordSet given a record from another RecordSet on another source, or returns nil on not found.
302
+ def find(record)
303
+ # ident_key = source.mapping_to(record.source)[source.identifier.to_s]
304
+ # ident = {source.identifier.to_s => record.send(ident_key)}
305
+ set.reject {|e| e.identifier != record.identifier}[0]
306
+ end
307
+
308
+ def each(&block) # :nodoc:
309
+ set.each(&block)
310
+ end
311
+
312
+ # Simply clears the collection.
313
+ def clear
314
+ set.clear
315
+ end
316
+
317
+ def length # :nodoc:
318
+ set.length
319
+ end
320
+
321
+ private
322
+ def get_mapped_identifier(record_or_identifier)
323
+ if record_or_identifier.respond_to?(:identifier)
324
+ (record_or_identifier.source == @source ? record_or_identifier : record_or_identifier.morph_to(@source)).identifier
325
+ else
326
+ record_or_identifier
327
+ end
328
+ end
329
+ end
330
+
331
+ # The Record module is used to extend record objects with a couple methods needed for sync-ing purposes.
332
+ module Record
333
+ def self.extended(base)
334
+ # Automatically adds 'identifier' method to the object if the object's class has an 'identifier'
335
+ # method that mentions the name of the identifier attribute.
336
+ def base.identifier
337
+ send(self.class.identifier.to_sym)
338
+ end if base.class.respond_to?(:identifier) && !base.respond_to?(:identifier)
339
+ end
340
+ attr_accessor :source
341
+ def morph_to(target)
342
+ raise ArgumentError, "can only morph by known mappings." unless source.can_map?(target)
343
+ new_attrs = to_hash.merge(self.class.identifier => identifier).transform_keys_by_mapping(source.mapping_to(target))
344
+ rec = target.initialize_record(target.klass.new(new_attrs))
345
+ rec.extend Record
346
+ rec.source = target
347
+ rec
348
+ end
349
+ # slave from master: {self => rec(external_id):map}
350
+ # slave from deleted: {self => rec(external_id):map}
351
+ # master from slave: {external_id => rec(self):map}
352
+ # master from deleted: {external_id => rec(self):map}
353
+ def find_on(target)
354
+ ident_key = target.mapping_to(source)[target.identifier.to_s]
355
+ ident = {target.identifier.to_s => send(ident_key)}
356
+ # puts "Looking for record #{source.identifier.to_s} => #{identifier} as record #{ident_key} => #{ident.inspect}"
357
+ found = ident.values[0].nil? ? nil : target.get(ident)
358
+ # puts "Found: #{found.inspect}"
359
+ found.extend Record
360
+ found.source = target
361
+ found
362
+ end
363
+ end
364
+ end
365
+
366
+ # A little enhancement that we need...
367
+ class Hash
368
+ # Given a mapping (hash), this will transform the keys from the keys to the values in the mapping.
369
+ # For example:
370
+ # mapping = {:cat => :Cat, :dog => :Canine}
371
+ # hash = {:cat => 'Felix', :dog => 'Toby'}
372
+ # hash.transform_keys_by_mapping(mapping)
373
+ # => {:Cat => 'Felix', :Canine => 'Toby'}
374
+ def transform_keys_by_mapping(mapping)
375
+ ddup = {}
376
+ self.each do |k,v|
377
+ if mapping[k]
378
+ if mapping[k].is_a?(Proc)
379
+ ddup = mapping[k].call(ddup,v)
380
+ else
381
+ ddup[mapping[k]] = v
382
+ end
383
+ end
384
+ end
385
+ ddup
386
+ end
387
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplesync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-20 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "QuickSync: an interface to synchronization logic."
17
+ email: gems@behindlogic.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - LICENSE
25
+ files:
26
+ - lib/simple_sync.rb
27
+ - LICENSE
28
+ - Rakefile
29
+ - README
30
+ - doc/classes
31
+ - doc/classes/Hash.html
32
+ - doc/classes/Hash.src
33
+ - doc/classes/Hash.src/M000001.html
34
+ - doc/classes/SimpleSync
35
+ - doc/classes/SimpleSync/Mappings.html
36
+ - doc/classes/SimpleSync/Mappings.src
37
+ - doc/classes/SimpleSync/Mappings.src/M000026.html
38
+ - doc/classes/SimpleSync/Mappings.src/M000027.html
39
+ - doc/classes/SimpleSync/Record.html
40
+ - doc/classes/SimpleSync/Record.src
41
+ - doc/classes/SimpleSync/Record.src/M000002.html
42
+ - doc/classes/SimpleSync/Record.src/M000003.html
43
+ - doc/classes/SimpleSync/Record.src/M000004.html
44
+ - doc/classes/SimpleSync/RecordSet.html
45
+ - doc/classes/SimpleSync/RecordSet.src
46
+ - doc/classes/SimpleSync/RecordSet.src/M000028.html
47
+ - doc/classes/SimpleSync/RecordSet.src/M000029.html
48
+ - doc/classes/SimpleSync/RecordSet.src/M000030.html
49
+ - doc/classes/SimpleSync/RecordSet.src/M000031.html
50
+ - doc/classes/SimpleSync/RecordSet.src/M000032.html
51
+ - doc/classes/SimpleSync/Source.html
52
+ - doc/classes/SimpleSync/Source.src
53
+ - doc/classes/SimpleSync/Source.src/M000005.html
54
+ - doc/classes/SimpleSync/Source.src/M000006.html
55
+ - doc/classes/SimpleSync/Source.src/M000007.html
56
+ - doc/classes/SimpleSync/Source.src/M000008.html
57
+ - doc/classes/SimpleSync/Source.src/M000009.html
58
+ - doc/classes/SimpleSync/Source.src/M000010.html
59
+ - doc/classes/SimpleSync/Source.src/M000011.html
60
+ - doc/classes/SimpleSync/Source.src/M000012.html
61
+ - doc/classes/SimpleSync/Source.src/M000013.html
62
+ - doc/classes/SimpleSync/Syncer.html
63
+ - doc/classes/SimpleSync/Syncer.src
64
+ - doc/classes/SimpleSync/Syncer.src/M000014.html
65
+ - doc/classes/SimpleSync/Syncer.src/M000015.html
66
+ - doc/classes/SimpleSync/Syncer.src/M000016.html
67
+ - doc/classes/SimpleSync/Syncer.src/M000017.html
68
+ - doc/classes/SimpleSync/Syncer.src/M000018.html
69
+ - doc/classes/SimpleSync/Syncer.src/M000019.html
70
+ - doc/classes/SimpleSync/Syncer.src/M000020.html
71
+ - doc/classes/SimpleSync/Syncer.src/M000021.html
72
+ - doc/classes/SimpleSync/Syncer.src/M000022.html
73
+ - doc/classes/SimpleSync/Syncer.src/M000023.html
74
+ - doc/classes/SimpleSync/Syncer.src/M000024.html
75
+ - doc/classes/SimpleSync/Syncer.src/M000025.html
76
+ - doc/classes/SimpleSync.html
77
+ - doc/created.rid
78
+ - doc/files
79
+ - doc/files/lib
80
+ - doc/files/lib/simple_sync_rb.html
81
+ - doc/files/LICENSE.html
82
+ - doc/files/README.html
83
+ - doc/fr_class_index.html
84
+ - doc/fr_file_index.html
85
+ - doc/fr_method_index.html
86
+ - doc/index.html
87
+ - doc/rdoc-style.css
88
+ has_rdoc: true
89
+ homepage: http://simplesync.rubyforge.org
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ version:
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: "0"
106
+ version:
107
+ requirements: []
108
+
109
+ rubyforge_project: simplesync
110
+ rubygems_version: 1.0.1
111
+ signing_key:
112
+ specification_version: 2
113
+ summary: "QuickSync: an interface to synchronization logic."
114
+ test_files: []
115
+