csv_pirate 4.1.4 → 5.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/lib/csv_pirate.rb CHANGED
@@ -7,686 +7,18 @@
7
7
  #Version: 1.0
8
8
  #Project owners:
9
9
  # peter.boling (The Cap'n)
10
+ require 'yaml'
11
+
10
12
  if RUBY_VERSION.to_f >= 1.9
11
13
  require 'csv'
12
14
  else
13
15
  require 'faster_csv'
14
16
  end
15
17
 
16
- class CsvPirate
17
-
18
- BOOKIE = [:counter, :timestamp, :none]
19
- MOP_HEADS = [:clean, :dirty]
20
- BRIGANTINE = [:first, :last]
21
- CSV_CLASS = (defined?(CSV) ? CSV : FasterCSV)
22
-
23
- attr_accessor :waggoner # First part of filename
24
- attr_accessor :chart # directory, default is (['log','csv'])
25
- attr_accessor :aft # extension, default is ('.csv')
26
- attr_accessor :gibbet # part of the filename after waggoner and date, before swabbie and aft
27
- attr_accessor :chronometer # Date object or false
28
-
29
- # Must provide swag or grub (not both)
30
- attr_accessor :swag # Array of objects
31
- attr_accessor :grub # Class
32
- # spyglasses is only used with grub, not swag
33
- attr_accessor :spyglasses # named_scopes
34
-
35
- # These are the booty of the CSV
36
- # Should be methods/columns on the swag
37
- # also used to create the figurehead (CSV header)
38
- attr_accessor :booty # methods / columns
39
-
40
- attr_accessor :bury_treasure # should we store the csv data in an array for later inspection, or just write the CSV?
41
- # default is false
42
- # The array that gets built as we write the CSV... could be useful?
43
- attr_accessor :buried_treasure # info array
44
-
45
- attr_accessor :rhumb_lines # the file object to write the CSV lines to
46
-
47
- attr_accessor :astrolabe # when true then read only CsvPirate instance for loading of CSVs
48
-
49
- attr_accessor :shrouds # CSV column separator
18
+ require 'csv_pirate/version.rb'
19
+ require 'csv_pirate/the_capn.rb'
20
+ require 'csv_pirate/pirate_ship.rb'
50
21
 
51
- attr_accessor :swab # default is :counter
52
- attr_accessor :mop # default is :clean (only has an effect if :swab is :none) since overwriting is irrelevant for a new file
53
- attr_accessor :swabbie # value of the counter / timestamp
54
- attr_accessor :maroon # text of csv
55
- attr_accessor :nocturnal # basename of the filepath (i.e. filename)
56
- attr_accessor :blackjack # Hash: Specify how you want your CSV header
57
- # {:join => '-'} joins the method names called to get hte data for that column with '_' underscores.
58
- # {:humanize => '-'} first joins as above, then humanizes the string
59
- # {:array => ['col1',col2','col3'] Uses the column names provided in the array. If the array provided is too short defaults to :humanize =>'_'
60
-
61
- attr_accessor :brigantine # the complete file path
62
- attr_accessor :pinnacle # the complete file path
63
- class << self
64
- attr_accessor :parlay # verbosity on a scale of 0 - 3 (0=:none, 1=:error, 2=:info, 3=:debug, 0 being no screen output, 1 is default
65
- end
66
-
67
- # CsvPirate only works for commissions of swag OR grub!
68
- # :swag the ARrr collection of swag to work on (optional)
69
- # :grub the ARrr class that the spyglasses will be used on (optional)
70
- # :spyglasses named scopes in your model that will refine the rows in the CSV according to conditions of the spyglasses,
71
- # and order them according to the order of the spyglasses (optional)
72
- # :booty booty (columns/methods) on your model that you want printed in the CSV, also used to create the figurehead (CSV header)
73
- # :chart array of directory names (relative to rails root if using rails) which will be the filepath where you want to hide your loot
74
- # :wagonner name of document where you will give detailed descriptions of the loot
75
- # :aft filename extention ('.csv')
76
- # :shrouds CSV column separator, default is ','. For tsv, tab-delimited, "\t"
77
- # :chronometer keeps track of when you hunt for treasure, can be false if you don't want to keep track.
78
- # :gibbet filename spacer after the date, and before the iterative counter/timestamp. MuST contain a '.'
79
- # :swab can be :counter, :timestamp, or :none
80
- # :counter - default, each successive run will create a new file using a counter
81
- # :timestamp - each successive run will create a new file using a HHMMSS time stamp
82
- # :none - no iterative file naming convention, just use waggoner, aft and gibbet
83
- # :mop can be :clean or :dirty (:overwrite or :append) (only has an effect if :swab is :none) since overwriting is irrelevant for a new file
84
- # :clean - do not use :counter or :timestamp, and instead overwrite the file
85
- # :dirty - do not use :counter, or :timestamp, or :overwrite. Just keep adding on.
86
- # :bury_treasure should we store the csv data as it is collected in an array in Ruby form for later use (true), or just write the CSV (false)?
87
- # :blackjack Specify how you want your CSV header
88
- # {:join => '-'} joins the method names called to get hte data for that column with '_' underscores.
89
- # {:humanize =>'-'} first joins as above, then humanizes the string
90
- # {:array => ['col1',col2','col3'] Uses the column names provided. If the array provided is too short defaults to :humanize =>'_'
91
- # See README for examples
22
+ module CsvPirate
92
23
 
93
- def initialize(*args)
94
- raise ArgumentError, "must provide required options" if args.nil?
95
-
96
- @swag = args.first[:swag]
97
- @grub = args.first[:grub]
98
-
99
- # if they provide both
100
- raise ArgumentError, "must provide either :swag or :grub, not both" if !self.swag.nil? && !self.grub.nil?
101
- # if they provide neither
102
- raise ArgumentError, "must provide either :swag or :grub" if self.swag.nil? && self.grub.nil?
103
-
104
- @swab = args.first[:swab] || :counter
105
- raise ArgumentError, ":swab is #{self.swab.inspect}, but must be one of #{CsvPirate::BOOKIE.inspect}" unless CsvPirate::BOOKIE.include?(self.swab)
106
-
107
- @mop = args.first[:mop] || :clean
108
- raise ArgumentError, ":mop is #{self.mop.inspect}, but must be one of #{CsvPirate::MOP_HEADS.inspect}" unless CsvPirate::MOP_HEADS.include?(self.mop)
109
-
110
- @gibbet = args.first[:gibbet] || '.export'
111
- raise ArgumentError, ":gibbet is #{self.gibbet.inspect}, and does not contain a '.' character, which is required when using iterative filenames (set :swab => :none to turn off iterative filenames)" if self.swab != :none && (self.gibbet.nil? || !self.gibbet.include?('.'))
112
-
113
- @waggoner = args.first[:waggoner] || "#{self.grub || self.swag}"
114
- raise ArgumentError, ":waggoner is #{self.waggoner.inspect}, and must be a string at least one character long" if self.waggoner.nil? || self.waggoner.length < 1
115
-
116
- # Not checking if empty here because PirateShip will send [] if the database is unavailable, to allow tasks like rake db:create to run
117
- @booty = args.first[:booty] || [] # would like to use Class#instance_variables for generic classes
118
- raise ArgumentError, ":booty is #{self.booty.inspect}, and must be an array of methods to call on a class for CSV data" if self.booty.nil? || !self.booty.is_a?(Array)
119
-
120
- @chart = args.first[:chart] || ['log','csv']
121
- raise ArgumentError, ":chart is #{self.chart.inspect}, and must be an array of directory names, which will become the filepath for the csv file" if self.chart.nil? || !self.chart.is_a?(Array) || self.chart.empty?
122
-
123
- @aft = args.first[:aft] || '.csv'
124
- @chronometer = args.first[:chronometer] == false ? false : (args.first[:chronometer] || Date.today)
125
-
126
- @spyglasses = (args.first[:spyglasses] || (self.respond_to?(:all) ? [:all] : nil)) if self.grub
127
-
128
- @shrouds = args.first[:shrouds] || ',' # for tsv, tab-delimited, "\t"
129
- raise ArgumentError, ":shrouds is #{self.shrouds.inspect}, and must be a string (e.g. ',' or '\t'), which will be used as the delimeter for the csv file" if self.shrouds.nil? || !self.shrouds.is_a?(String)
130
-
131
- @astrolabe = args.first[:astrolabe] || false
132
-
133
- @bury_treasure = args.first[:astrolabe] || false
134
- @buried_treasure = []
135
-
136
- #does not rely on rails humanize!
137
- @blackjack = args.first[:blackjack] || {:humanize => '_'}
138
-
139
- #Make sure the header array is the same length as the booty columns
140
- if self.blackjack.keys.first == :array && (self.blackjack.values.first.length != self.booty.length)
141
- @blackjack = {:join => '_'}
142
- puts "Warning: :blackjack reset to {:join => '_'} because the length of the :booty is different than the length of the array provided to :blackjack" if CsvPirate.parlance(2)
143
- end
144
-
145
- @pinnacle = self.block_and_tackle
146
-
147
- # Initialize doesn't write anything to a CSV,
148
- # but does create the traverse_board and opens the waggoner for reading / writing
149
- self.northwest_passage unless self.astrolabe
150
-
151
- # This will contain the text of the csv from this particular execution
152
- @maroon = ""
153
-
154
- # Once the traverse_board (dir) exists, then check if the rhumb_lines (file) already exists, and set our rhumb_lines counter
155
- @swabbie = self.insult_swabbie
156
-
157
- @brigantine = self.poop_deck(args.first[:brigantine])
158
-
159
- @nocturnal = File.basename(self.brigantine)
160
-
161
-
162
- # Then open the rhumb_lines
163
- self.rhumb_lines = File.open(File.expand_path(self.brigantine),self.astrolabe ? "r" : "a")
164
- end
165
-
166
- def self.create(*args)
167
- csv_pirate = CsvPirate.new({
168
- :chart => args.first[:chart],
169
- :aft => args.first[:aft],
170
- :gibbet => args.first[:gibbet],
171
- :chronometer => args.first[:chronometer],
172
- :waggoner => args.first[:waggoner],
173
- :swag => args.first[:swag],
174
- :swab => args.first[:swab],
175
- :shrouds => args.first[:shrouds],
176
- :mop => args.first[:mop],
177
- :grub => args.first[:grub],
178
- :spyglasses => args.first[:spyglasses],
179
- :booty => args.first[:booty],
180
- :astrolabe => args.first[:astrolabe],
181
- :blackjack => args.first[:blackjack],
182
- :bury_treasure => args.first[:bury_treasure]
183
- })
184
- csv_pirate.hoist_mainstay()
185
- csv_pirate
186
- end
187
-
188
- ########################################
189
- ########### INSTANCE METHODS ###########
190
- ########################################
191
-
192
- # This is the hardest working method. Get your shovels!
193
- def dig_for_treasure(&block)
194
- return false unless block_given?
195
-
196
- if !grub.nil?
197
- self.swag = grub
198
- spyglasses.each {|x| self.swag = self.swag.send(x) }
199
- end
200
-
201
- treasure_chest = self.swag.map do |spoils|
202
- self.prize(spoils)
203
- end
204
-
205
- treasure_chest.each do |loot|
206
- yield loot
207
- end
208
- end
209
-
210
- def prize(spoils)
211
- gold_doubloons = []
212
- self.booty.each do |plunder|
213
- # Check for nestedness
214
- if plunder.is_a?(Hash)
215
- gold_doubloons << CsvPirate.marlinespike(spoils, plunder)
216
- else
217
- gold_doubloons << spoils.send(plunder.to_sym)
218
- end
219
- end
220
- gold_doubloons
221
- end
222
-
223
- def scrivener(msg)
224
- self.rhumb_lines.puts msg
225
- end
226
-
227
- # Sail through your db looking for buried treasure!
228
- # - restricted to loot that can be seen through spyglasses (if provided)!
229
- def hoist_mainstay
230
-
231
- self.swab_poop_deck
232
-
233
- self.dead_mans_chest
234
-
235
- self.rhumb_lines.close
236
-
237
- self.jolly_roger if CsvPirate.parlay && CsvPirate.parlance(1)
238
-
239
- # returns the text of this CSV export
240
- return self.maroon
241
- end
242
-
243
- def dead_mans_chest
244
- self.maroon = CSV_CLASS.generate(:col_sep => self.shrouds) do |csv|
245
- self.sounding(csv)
246
- end
247
- self.scrivener(self.maroon)
248
- self.maroon
249
- end
250
-
251
- def jolly_roger
252
- if self.bury_treasure
253
- if self.buried_treasure.is_a?(Array)
254
- puts "Found #{self.buried_treasure.length} deniers buried here: '#{self.brigantine}'" if CsvPirate.parlay && CsvPirate.parlance(1)
255
- puts "You must weigh_anchor to review your plunder!" if CsvPirate.parlay && CsvPirate.parlance(1)
256
- else
257
- puts "Failed to locate treasure" if CsvPirate.parlay && CsvPirate.parlance(1)
258
- end
259
- end
260
- end
261
-
262
- def sounding(csv)
263
- csv << self.block_and_tackle
264
- # create the data for the csv
265
- self.dig_for_treasure do |treasure|
266
- moidore = treasure.map {|x| "#{x}"}
267
- csv << moidore # |x| marks the spot!
268
- self.buried_treasure << moidore if self.bury_treasure
269
- end
270
- end
271
-
272
- #Takes a potentially nested hash element of a booty array and turns it into a string for a column header
273
- # hash = {:a => {:b => {:c => {:d => {"e" => "fghi"}}}}}
274
- # run_through(hash, '_')
275
- # => "a_b_c_d_e_fghi"
276
- # Ooooh so recursive!
277
- def run_through(hash, join_value)
278
- hash.map do |k,v|
279
- if v.is_a?(Hash)
280
- [k,run_through(v, join_value)].join(join_value)
281
- else #works for Symbols and Strings
282
- [k,v].join(join_value)
283
- end
284
- end.first
285
- end
286
-
287
- #complete file path
288
- def poop_deck(brig)
289
- if BRIGANTINE.include?(brig)
290
- self.old_csv_dump(brig)
291
- elsif brig.is_a?(String)
292
- "#{self.analemma}#{brig}"
293
- else
294
- "#{self.analemma}#{self.swabbie}#{self.aft}"
295
- end
296
- end
297
-
298
- # Swabs the poop_deck (of the brigantine) if the mop is clean. (!)
299
- def swab_poop_deck
300
- self.rhumb_lines.truncate(0) if self.swab == :none && self.mop == :clean && File.size(self.brigantine) > 0
301
- end
302
-
303
- # Must be done on order to rummage through the loot found by the pirate ship
304
- def weigh_anchor
305
- CsvPirate.rinse(self.brigantine)
306
- end
307
-
308
- # Sink your own ship! Or run a block of code on each row of the current CSV
309
- def scuttle(&block)
310
- return false unless block_given?
311
- CsvPirate.broadside(self.brigantine) do |careen|
312
- yield careen
313
- end
314
- end
315
-
316
- #permanence can be any of:
317
- # {:new => :new} - only calls the initializer with data hash for each row to instantiate objects (useful with any vanilla Ruby Class)
318
- # {:new => :save} - calls the initializer with the data hash for each row and then calls save on each (useful with ActiveRecord)
319
- # {:new => :create} - calls a create method with the data hash for each row (useful with ActiveRecord)
320
- # {:find_or_new => [column names for find_by]} - see below (returns only the new objects
321
- # {:find_or_save => [column names for find_by]} - see below (returns all found or saved objects)
322
- # {:find_or_create => [column names for find_by]} - looks for existing objects using find_by_#{columns.join('_and_')}, (returns all found or created objects)
323
- # and if not found creates a new object.
324
- # The difference between the new, save and create versions are the same as the various :new hashes above.
325
- # {:update_or_new => [column names for find_by]} - see below (returns only the new objects)
326
- # {:update_or_save => [column names for find_by]} - see below (returns all updated or saved objects)
327
- # {:update_or_create => [column names for find_by]} - looks for existing objects using find_by_#{columns.join('_and_')} , (returns all updated or created objects)
328
- # and updates them with the data hash form the csv row, otherwise creates a new object.
329
- #TODO: This is a nasty method. Just a quick hack to GTD. Needs to be rethought and refactored. --pboling
330
- def to_memory(permanence = {:new => :new}, exclude_id = true, exclude_timestamps = true)
331
- return nil unless self.grub
332
- begin
333
- example = self.grub.new
334
- rescue Exception
335
- puts "cannot instantiate instance of #{self.grub} with #{self.grub}.new. CsvPirate#to_memory works most reliably when #{self.grub}.new works with no arguments." if CsvPirate.parlance(1)
336
- example = nil
337
- end
338
- buccaneers = []
339
- self.scuttle do |row|
340
- data_hash = self.data_hash_from_row(row, exclude_id, exclude_timestamps, example)
341
- case permanence
342
- when {:new => :new} then
343
- buccaneers << self.grub.new(data_hash)
344
- when {:new => :save} then
345
- obj = self.grub.new(data_hash)
346
- buccaneers << obj.save(false)
347
- when {:new => :create} then
348
- buccaneers << self.grub.create(data_hash)
349
- else
350
- if permanence[:find_or_new]
351
- obj = self.send_aye(data_hash, permanence[:find_or_new])
352
- buccaneers << self.grub.new(data_hash) if obj.nil?
353
- elsif permanence[:find_or_save]
354
- obj = self.send_aye(data_hash, permanence[:find_or_save])
355
- if obj.nil?
356
- obj = self.grub.new(data_hash)
357
- obj.save(false) if obj.respond_to?(:save)
358
- end
359
- buccaneers << obj
360
- elsif permanence[:find_or_create]
361
- obj = self.send_aye(data_hash, permanence[:find_or_create])
362
- if obj.nil?
363
- self.grub.create(data_hash)
364
- end
365
- buccaneers << obj
366
- elsif permanence[:update_or_new]
367
- obj = self.send_aye(data_hash, permanence[:update_or_new])
368
- if obj.nil?
369
- obj = self.grub.new(data_hash)
370
- else
371
- self.save_object(obj, data_hash)
372
- end
373
- buccaneers << obj
374
- elsif permanence[:update_or_save]
375
- obj = self.send_aye(data_hash, permanence[:update_or_save])
376
- if obj.nil?
377
- obj = self.grub.new(data_hash)
378
- obj.save(false)
379
- else
380
- self.save_object(obj, data_hash)
381
- end
382
- buccaneers << obj
383
- elsif permanence[:update_or_create]
384
- obj = self.send_aye(data_hash, permanence[:update_or_create])
385
- if obj.nil?
386
- obj = self.grub.create(data_hash)
387
- else
388
- self.save_object(obj, data_hash)
389
- end
390
- buccaneers << obj
391
- end
392
- end
393
- end
394
- buccaneers
395
- end
396
-
397
- def save_object(obj, data_hash)
398
- data_hash.each do |k,v|
399
- obj.send("#{k}=".to_sym, v)
400
- end
401
- unless obj.save(false)
402
- puts "Save Failed: #{obj.inspect}" if CsvPirate.parlance(1)
403
- end
404
- end
405
-
406
- def send_aye(data_hash, columns)
407
- obj = self.grub.send(self.find_aye(columns), self.find_aye_arr(data_hash, columns))
408
- if obj
409
- puts "#{self.grub}.#{find_aye(columns)}(#{self.find_aye_arr(data_hash, columns).inspect}): found id = #{obj.id}" if CsvPirate.parlance(2)
410
- end
411
- end
412
-
413
- def find_aye(columns)
414
- "find_by_#{columns.join('_and_')}".to_sym
415
- end
416
-
417
- def find_aye_arr(data_hash, columns)
418
- columns.map do |col|
419
- data_hash[col.to_s]
420
- end
421
- end
422
-
423
- def data_hash_from_row(row, exclude_id = true, exclude_timestamps = true, example = nil)
424
- plunder = {}
425
- my_booty = self.booty.reject {|x| x.is_a?(Hash)}
426
- my_booty = exclude_id ? my_booty.reject {|x| a = x.to_sym; [:id, :ID,:dbid, :DBID, :db_id, :DB_ID].include?(a)} : self.booty
427
- my_booty = exclude_timestamps ? my_booty.reject {|x| a = x.to_sym; [:created_at, :updated_at, :created_on, :updated_on].include?(a)} : self.booty
428
- my_booty = my_booty.reject {|x| !example.respond_to?("#{x}=".to_sym)} unless example.nil?
429
- my_booty.each do |method|
430
- plunder = plunder.merge({method => row[self.pinnacle[self.booty.index(method)]]})
431
- end
432
- plunder
433
- end
434
-
435
- def data_hash_from_row(row, exclude_id = true, exclude_timestamps = true, example = nil)
436
- data_hash = {}
437
- my_booty = self.booty.reject {|x| x.is_a?(Hash)}
438
- my_booty = exclude_id ? my_booty.reject {|x| a = x.to_sym; [:id, :ID,:dbid, :DBID, :db_id, :DB_ID].include?(a)} : self.booty
439
- my_booty = exclude_timestamps ? my_booty.reject {|x| a = x.to_sym; [:created_at, :updated_at, :created_on, :updated_on].include?(a)} : self.booty
440
- my_booty = my_booty.reject {|x| !example.respond_to?("#{x}=".to_sym)} unless example.nil?
441
- my_booty.each do |method|
442
- data_hash = data_hash.merge({method => row[self.pinnacle[self.booty.index(method)]]})
443
- end
444
- data_hash
445
- end
446
-
447
- # Grab an old CSV dump (first or last)
448
- def old_csv_dump(brig)
449
- file = Dir.entries(self.traverse_board).reject {|x| x.match(/^\./)}.sort.send(brig)
450
- "#{self.traverse_board}#{file}"
451
- end
452
-
453
- protected
454
-
455
- # create the header of the CSV (column/method names)
456
- # returns an array of strings for CSV header based on blackjack
457
- def block_and_tackle
458
- self.blackjack.map do |k,v|
459
- case k
460
- #Joining is only relevant when the booty contains a nested hash of method calls as at least one of the booty array elements
461
- #Use the booty (methods) as the column headers
462
- when :join then self.binnacle(v, false)
463
- #Use the humanized booty (methods) as the column headers
464
- when :humanize then self.binnacle(v, true)
465
- when :array then v
466
- end
467
- end.first
468
- end
469
-
470
- #returns an array of strings for CSV header based on booty
471
- def binnacle(join_value, humanize = true)
472
- self.booty.map do |compass|
473
- string = compass.is_a?(Hash) ?
474
- self.run_through(compass, join_value) :
475
- compass.is_a?(String) ?
476
- compass :
477
- compass.is_a?(Symbol) ?
478
- compass.to_s :
479
- compass.to_s
480
- humanize ? string.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize : string
481
- end
482
- end
483
-
484
- # The directory path to the csv
485
- def traverse_board
486
- #If we have rails environment then we use rails root, otherwise self.chart stands on its own as a relative path
487
- "#{self.north_pole}#{self.chart.join('/')}/"
488
- end
489
-
490
- def sand_glass
491
- "#{self.chronometer.respond_to?(:strftime) ? '.' + self.chronometer.strftime("%Y%m%d") : ''}"
492
- end
493
-
494
- def merchantman
495
- "#{self.waggoner}#{self.sand_glass}#{self.gibbet}"
496
- end
497
-
498
- def analemma
499
- "#{self.traverse_board}#{self.merchantman}"
500
- end
501
-
502
- def north_pole
503
- "#{defined?(Rails) ? "#{Rails.root}/" : defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/" : ''}"
504
- end
505
-
506
- def northwest_passage
507
- self.chart.length.times do |i|
508
- north_star = self.north_pole + self.chart[0..i].join('/')
509
- Dir.mkdir(north_star) if Dir.glob(north_star).empty?
510
- end
511
- end
512
-
513
- def lantern
514
- "#{self.analemma}.*"
515
- end
516
-
517
- def filibuster(flotsam)
518
- base = File.basename(flotsam, self.aft)
519
- index = base.rindex('.')
520
- tail = index.nil? ? nil : base[index+1,base.length]
521
- # Ensure numbers
522
- tail.nil? ? 0 : tail[/\d*/].to_i
523
- end
524
-
525
- # Get all the files in the dir
526
- def axe
527
- Dir.glob(self.lantern)
528
- end
529
-
530
- # File increment for next CSV to dump
531
- def boatswain
532
- return self.swabbie unless self.swabbie.nil?
533
- highval = 0
534
- self.axe.each do |flotsam|
535
- counter = self.filibuster(flotsam)
536
- highval = ((highval <=> counter) == 1) ? highval : counter
537
- end
538
- ".#{highval + 1}"
539
- end
540
-
541
- def coxswain
542
- return self.swabbie unless self.swabbie.nil?
543
- ".#{Time.now.strftime("%I%M%S")}"
544
- end
545
-
546
- # Sets the swabby file counter
547
- def insult_swabbie
548
- return case self.swab
549
- when :counter
550
- self.boatswain
551
- when :timestamp
552
- self.coxswain
553
- else
554
- ""
555
- end
556
- end
557
-
558
- public
559
-
560
- ########################################
561
- ############ CLASS METHODS #############
562
- ########################################
563
-
564
- # if this is your booty:
565
- # {:booty => [
566
- # :id,
567
- # {:region => {:country => :name }, :state => :name },
568
- # :name
569
- # ]}
570
- # so nested_hash = {:region => {:country => :name }, :state => :name }
571
- def self.marlinespike(spoils, navigation)
572
- navigation.map do |east,west|
573
- spoils = spoils.send(east.to_sym)
574
- unless spoils.nil?
575
- if west.is_a?(Hash)
576
- # Recursive madness is here!
577
- spoils = CsvPirate.marlinespike(spoils, west)
578
- else
579
- spoils = spoils.send(west.to_sym)
580
- end
581
- end
582
- spoils
583
- end.compact.join(' - ')
584
- end
585
-
586
- # Used to read any loot found by any pirate
587
- def self.rinse(quarterdeck)
588
- File.open(File.expand_path(quarterdeck), "r") do |bucket_line|
589
- bucket_line.each_line do |bucket|
590
- puts bucket
591
- end
592
- end
593
- end
594
-
595
- # Sink other ships! Or run a block of code on each row of a CSV
596
- def self.broadside(galley, &block)
597
- return false unless block_given?
598
- CSV_CLASS.foreach(galley, {:headers => :first_row, :return_headers => false}) do |gun|
599
- yield gun
600
- end
601
- end
602
-
603
- # During a mutiny things are a little different!
604
- # Essentially you are using an existing CSV to drive queries to create a second CSV cased on the first
605
- # The capn hash is:
606
- # :grub => is the class on which to make booty [method] calls
607
- # :swag => column index in CSV (0th, 1st, 2nd, etc. column?)
608
- # (swag OR spyglasses can be specified, but code defers to swag if provided)
609
- # :spyglasses => is the column to load ("find_by_#{booty}") the ARrr object for each row on the first CSV
610
- # (swag OR spyglasses can be specified, but code defers to swag if provided)
611
- # :waggoner => where the capn's loot was stashed (filename)
612
- # :chart => array of directory names where capn's waggoner is located
613
- # :astrolabe => true (file is opened at top of file in read only mode when true)
614
- # The first_mate hash is:
615
- # :grub => is the class on which to make booty [method] calls, or
616
- # is a method (as a string) we call to get from the object loaded by capn,
617
- # to the object on which we'll make the first_mate booty [method] calls, or nil, if same object
618
- # :swag => is the method to call on first CSV row's object to find second CSV row's object (if grub is a class)
619
- # :spyglasses => is the column to load ("find_by_#{booty}") the ARrr object for each row on the second CSV (if grub is a class)
620
- # :booty => is the methods to call on the ARrr object for each row on the second CSV
621
- # :waggoner => where to stash the first mate's loot (filename)
622
- # :chart => array of directory names where first mate's waggoner is located
623
- # :astrolabe => false (false is the default for astrolabe, so we could leave it off the first_mate)
624
- #
625
- # Example:
626
- # capn = {:grub => User,:spyglasses => [:inactive],:booty => ['id','login','status'],:waggoner => 'orig',:chart => ['log','csv'],:astrolabe => false}
627
- # make_orig = CsvPirate.new(capn)
628
- # make_orig.hoist_mainstay
629
- # make_orig.weigh_anchor
630
- #
631
- # first_mate = {:grub => 'account',:booty => ["id","number","name","created_at"],:waggoner => 'fake',:chart => ['log','csv']}
632
- # OR
633
- # # for same class, we re-use the object loaded from first CSV and make the booty [method] calls on it
634
- # first_mate = {:grub => User,:booty => ["id","login","visits_count"],:waggoner => 'fake',:chart => ['log','csv']}
635
- # OR
636
- # first_mate = {:grub => Account,:spyglasses => 'id',:swag=>'user_id',:booty => ["id","name","number"],:waggoner => 'fake',:chart => ['log','csv']}
637
- # AND
638
- # capn = {:grub => User,:spyglasses => 'login',:swag => 1,:waggoner => 'orig',:chart => ['log','csv'],:astrolabe => true}
639
- # after_mutiny = CsvPirate.mutiny(capn, first_mate)
640
- #
641
- def self.mutiny(capn, first_mate)
642
- carrack = CsvPirate.new(capn)
643
- cutthroat = CsvPirate.new(first_mate)
644
-
645
- cutthroat.figurehead
646
-
647
- carrack.scuttle do |cutlass|
648
- puts "CUTLASS: #{cutlass.inspect}" if CsvPirate.parlance(2)
649
- puts "CARRACK.SWAG: #{carrack.swag.inspect}" if CsvPirate.parlance(2)
650
- backstaff = cutlass[carrack.swag] || cutlass["#{carrack.spyglasses}"]
651
- puts "BACKSTAFF: #{backstaff}" if CsvPirate.parlance(2)
652
- puts "CARRACK.SPYGLASSES: #{carrack.spyglasses.inspect}" if CsvPirate.parlance(2)
653
- gully = carrack.grub.send("find_by_#{carrack.spyglasses}".to_sym, backstaff)
654
- puts "GULLY: #{gully.inspect}" if CsvPirate.parlance(2)
655
- if gully
656
- flotsam = cutthroat.grub.is_a?(String) ?
657
- gully.send(cutthroat.grub.to_sym) :
658
- cutthroat.grub.is_a?(Symbol) ?
659
- gully.send(cutthroat.grub) :
660
- cutthroat.grub.class == carrack.grub.class ?
661
- gully :
662
- cutthroat.grub.class == Class ?
663
- cutthroat.grub.send("find_by_#{cutthroat.swag}", gully.send(cutthroat.spyglasses)) :
664
- nil
665
- puts "FLOTSAM: #{flotsam.inspect}" if CsvPirate.parlance(2)
666
- if flotsam
667
- plunder = cutthroat.prize(flotsam)
668
- cutthroat.buried_treasure << plunder
669
- cutthroat.scrivener(plunder.map {|bulkhead| "#{bulkhead}"}.join(','))
670
- else
671
- puts "Unable to locate: #{cutthroat.grub} related to #{carrack.grub}.#{carrack.spyglasses} '#{gully.send(carrack.spyglasses)}'" if CsvPirate.parlance(1)
672
- end
673
- else
674
- puts "Unable to locate: #{carrack.grub}.#{carrack.spyglasses} '#{gully.send(carrack.spyglasses)}'" if CsvPirate.parlance(1)
675
- end
676
- end
677
-
678
- carrack.rhumb_lines.close
679
- cutthroat.rhumb_lines.close
680
-
681
- cutthroat.jolly_roger
682
-
683
- # returns the array that is created before exporting it to CSV
684
- return cutthroat
685
- end
686
-
687
- # verbosity on a scale of 0 - 3 (0=:none, 1=:error, 2=:info, 3=:debug, 0 being no screen output, 1 is default
688
- def self.parlance(level = 1)
689
- self.parlay.is_a?(Numeric) && self.parlay >= level
690
- end
691
-
692
24
  end