simplesync 0.0.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.
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
+