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
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|