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