csv_pirate 4.1.4 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|