csv_pirate 4.1.4 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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