lib/csv_pirate.rb in csv_pirate-3.4.4 vs lib/csv_pirate.rb in csv_pirate-4.0.1
- old
+ new
@@ -5,16 +5,16 @@
#License: MIT License
#Labels: Ruby, Rails, Gem, Plugin
#Version: 1.0
#Project owners:
# peter.boling (The Cap'n)
-require 'faster_csv' unless defined?(FasterCSV)
class CsvPirate
BOOKIE = [:counter, :timestamp, :none]
MOP_HEADS = [:clean, :dirty]
+ BRIGANTINE = [:first, :last]
attr_accessor :waggoner # First part of filename
attr_accessor :chart # directory, default is (['log','csv'])
attr_accessor :aft # extension, default is ('.csv')
attr_accessor :gibbet # part of the filename after waggoner and date, before swabbie and aft
@@ -50,11 +50,15 @@
attr_accessor :blackjack # Hash: Specify how you want your CSV header
# {:join => '-'} joins the method names called to get hte data for that column with '_' underscores.
# {:humanize => '-'} first joins as above, then humanizes the string
# {:array => ['col1',col2','col3'] Uses the column names provided in the array. If the array provided is too short defaults to :humanize =>'_'
- cattr_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
+ attr_accessor :brigantine # the complete file path
+ attr_accessor :pinnacle # the complete file path
+ class << self
+ 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
+ end
# CsvPirate only works for commissions of swag OR grub!
# :swag the ARrr collection of swag to work on (optional)
# :grub the ARrr class that the spyglasses will be used on (optional)
# :spyglasses named scopes in your model that will refine the rows in the CSV according to conditions of the spyglasses,
@@ -79,19 +83,19 @@
# {:humanize =>'-'} first joins as above, then humanizes the string
# {:array => ['col1',col2','col3'] Uses the column names provided. If the array provided is too short defaults to :humanize =>'_'
# See README for examples
def initialize(*args)
- raise ArgumentError, "must provide required options" if args.blank?
+ raise ArgumentError, "must provide required options" if args.nil?
@swag = args.first[:swag]
@grub = args.first[:grub]
# if they provide both
- raise ArgumentError, "must provide either :swag or :grub, not both" if !self.swag.blank? && !self.grub.blank?
+ raise ArgumentError, "must provide either :swag or :grub, not both" if !self.swag.nil? && !self.grub.nil?
# if they provide neither
- raise ArgumentError, "must provide either :swag or :grub" if self.swag.blank? && self.grub.blank?
+ raise ArgumentError, "must provide either :swag or :grub" if self.swag.nil? && self.grub.nil?
@swab = args.first[:swab] || :counter
raise ArgumentError, ":swab is #{self.swab.inspect}, but must be one of #{CsvPirate::BOOKIE.inspect}" unless CsvPirate::BOOKIE.include?(self.swab)
@mop = args.first[:mop] || :clean
@@ -101,51 +105,57 @@
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?('.'))
@waggoner = args.first[:waggoner] || "#{self.grub || self.swag}"
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
- @booty = args.first[:booty] || []
+ @booty = args.first[:booty] || [] # would like to use Class#instance_variables for generic classes
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) || self.booty.empty?
@chart = args.first[:chart] || ['log','csv']
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?
@aft = args.first[:aft] || '.csv'
- @chronometer = args.first[:chronometer] == false ? false : args.first[:chronometer] || Date.today
+ @chronometer = args.first[:chronometer] == false ? false : (args.first[:chronometer] || Date.today)
- @spyglasses = (args.first[:spyglasses] || [:all]) if self.grub
+ @spyglasses = (args.first[:spyglasses] || (self.respond_to?(:all) ? [:all] : nil)) if self.grub
+
@shrouds = args.first[:shrouds] || ',' # for tsv, tab-delimited, "\t"
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)
@astrolabe = args.first[:astrolabe] || false
@bury_treasure = args.first[:astrolabe] || false
@buried_treasure = []
- #Default is different than default for has_csv_pirate because this might gem wants to be clean for use outside rails, and humanize may not always be defined for String class
- @blackjack = args.first[:blackjack] || {:join => '_'}
+ #does not rely on rails humanize!
+ @blackjack = args.first[:blackjack] || {:humanize => '_'}
#Make sure the header array is the same length as the booty columns
if self.blackjack.keys.first == :array && (self.blackjack.values.first.length != self.booty.length)
@blackjack = {:join => '_'}
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)
end
- # Initialize doesn't write anything to a CSV,
+ @pinnacle = self.block_and_tackle
+
+ # Initialize doesn't write anything to a CSV,
# but does create the traverse_board and opens the waggoner for reading / writing
self.northwest_passage unless self.astrolabe
# This will contain the text of the csv from this particular execution
@maroon = ""
# Once the traverse_board (dir) exists, then check if the rhumb_lines (file) already exists, and set our rhumb_lines counter
@swabbie = self.insult_swabbie
- @nocturnal = File.basename(self.poop_deck)
+ @brigantine = self.poop_deck(args.first[:brigantine])
+
+ @nocturnal = File.basename(self.brigantine)
+
# Then open the rhumb_lines
- self.rhumb_lines = File.open(File.expand_path(self.poop_deck),self.astrolabe ? "r" : "a")
+ self.rhumb_lines = File.open(File.expand_path(self.brigantine),self.astrolabe ? "r" : "a")
end
def self.create(*args)
csv_pirate = CsvPirate.new({
:chart => args.first[:chart],
@@ -232,11 +242,11 @@
end
def jolly_roger
if self.bury_treasure
if self.buried_treasure.is_a?(Array)
- puts "Found #{self.buried_treasure.length} deniers buried here: '#{self.poop_deck}'" if CsvPirate.parlay && CsvPirate.parlance(1)
+ puts "Found #{self.buried_treasure.length} deniers buried here: '#{self.brigantine}'" if CsvPirate.parlay && CsvPirate.parlance(1)
puts "You must weigh_anchor to review your plunder!" if CsvPirate.parlay && CsvPirate.parlance(1)
else
puts "Failed to locate treasure" if CsvPirate.parlay && CsvPirate.parlance(1)
end
end
@@ -250,39 +260,10 @@
csv << moidore # |x| marks the spot!
self.buried_treasure << moidore if self.bury_treasure
end
end
- # create the header of the CSV (column/method names)
- # returns an array of strings for CSV header based on blackjack
- def block_and_tackle
- self.blackjack.map do |k,v|
- case k
- #Joining is only relevant when the booty contains a nested hash of method calls as at least one of the booty array elements
- #Use the booty (methods) as the column headers
- when :join then self.binnacle(v, false)
- #Use the humanized booty (methods) as the column headers
- when :humanize then self.binnacle(v, true)
- when :array then v
- end
- end.first
- end
-
- #returns an array of strings for CSV header based on booty
- def binnacle(join_value, humanize = true)
- self.booty.map do |compass|
- string = compass.is_a?(Hash) ?
- self.run_through(compass, join_value) :
- compass.is_a?(String) ?
- compass :
- compass.is_a?(Symbol) ?
- compass.to_s :
- compass.to_s
- humanize ? string.humanize : string
- end
- end
-
#Takes a potentially nested hash element of a booty array and turns it into a string for a column header
# hash = {:a => {:b => {:c => {:d => {"e" => "fghi"}}}}}
# run_through(hash, '_')
# => "a_b_c_d_e_fghi"
# Ooooh so recursive!
@@ -295,34 +276,106 @@
end
end.first
end
#complete file path
- def poop_deck
- "#{self.analemma}#{self.swabbie}#{self.aft}"
+ def poop_deck(brig)
+ if BRIGANTINE.include?(brig)
+ self.old_csv_dump(brig)
+ elsif brig.is_a?(String)
+ "#{self.analemma}#{brig}"
+ else
+ "#{self.analemma}#{self.swabbie}#{self.aft}"
+ end
end
- # Swabs the poop_deck if the mop is clean. (!)
+ # Swabs the poop_deck (of the brigantine) if the mop is clean. (!)
def swab_poop_deck
- self.rhumb_lines.truncate(0) if self.swab == :none && self.mop == :clean && File.size(self.poop_deck) > 0
+ self.rhumb_lines.truncate(0) if self.swab == :none && self.mop == :clean && File.size(self.brigantine) > 0
end
# Must be done on order to rummage through the loot found by the pirate ship
def weigh_anchor
- CsvPirate.rinse(self.poop_deck)
+ CsvPirate.rinse(self.brigantine)
end
# Sink your own ship! Or run a block of code on each row of the current CSV
def scuttle(&block)
return false unless block_given?
- CsvPirate.broadside(self.poop_deck) do |careen|
+ CsvPirate.broadside(self.brigantine) do |careen|
yield careen
end
end
+ def to_memory(exclude_id = true, exclude_timestamps = true)
+ return nil unless self.grub
+ begin
+ example = self.grub.new
+ rescue Exception
+ 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)
+ example = nil
+ end
+ buccaneers = []
+ self.scuttle do |row|
+ #puts "#{self.pinnacle.first.inspect} #{row[self.pinnacle.first].inspect}"
+ buccaneers << self.grub.new(self.data_hash_from_row(row, exclude_id, exclude_timestamps, example))
+ end
+ buccaneers
+ end
+
+ def data_hash_from_row(row, exclude_id = true, exclude_timestamps = true, example = nil)
+ data_hash = {}
+ my_booty = self.booty.reject {|x| x.is_a?(Hash)}
+ my_booty = exclude_id ? my_booty.reject {|x| a = x.to_sym; [:id, :ID,:dbid, :DBID, :db_id, :DB_ID].include?(a)} : self.booty
+ my_booty = exclude_timestamps ? my_booty.reject {|x| a = x.to_sym; [:created_at, :updated_at, :created_on, :updated_on].include?(a)} : self.booty
+ my_booty = my_booty.reject {|x| !example.respond_to?("#{x}=".to_sym)} unless example.nil?
+ my_booty.each do |method|
+ #puts "#{self.pinnacle[index]}"
+ data_hash = data_hash.merge({method => row[self.pinnacle[self.booty.index(method)]]})
+ end
+ #puts "#{data_hash.inspect}"
+ data_hash
+ end
+
+ # Grab an old CSV dump (first or last)
+ def old_csv_dump(brig)
+ file = Dir.entries(self.traverse_board).reject {|x| x.match(/^\./)}.sort.send(brig)
+ "#{self.traverse_board}#{file}"
+ end
+
protected
+ # create the header of the CSV (column/method names)
+ # returns an array of strings for CSV header based on blackjack
+ def block_and_tackle
+ self.blackjack.map do |k,v|
+ case k
+ #Joining is only relevant when the booty contains a nested hash of method calls as at least one of the booty array elements
+ #Use the booty (methods) as the column headers
+ when :join then self.binnacle(v, false)
+ #Use the humanized booty (methods) as the column headers
+ when :humanize then self.binnacle(v, true)
+ when :array then v
+ end
+ end.first
+ end
+
+ #returns an array of strings for CSV header based on booty
+ def binnacle(join_value, humanize = true)
+ self.booty.map do |compass|
+ string = compass.is_a?(Hash) ?
+ self.run_through(compass, join_value) :
+ compass.is_a?(String) ?
+ compass :
+ compass.is_a?(Symbol) ?
+ compass.to_s :
+ compass.to_s
+ humanize ? string.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize : string
+ end
+ end
+
+ # The directory path to the csv
def traverse_board
#If we have rails environment then we use rails root, otherwise self.chart stands on its own as a relative path
"#{self.north_pole}#{self.chart.join('/')}/"
end
@@ -356,22 +409,25 @@
def filibuster(flotsam)
base = File.basename(flotsam, self.aft)
index = base.rindex('.')
tail = index.nil? ? nil : base[index+1,base.length]
# Ensure numbers
- counter = tail.nil? ? 0 : tail[/\d*/].to_i
+ tail.nil? ? 0 : tail[/\d*/].to_i
end
-
+
+ # Get all the files in the dir
+ def axe
+ Dir.glob(self.lantern)
+ end
+
+ # File increment for next CSV to dump
def boatswain
return self.swabbie unless self.swabbie.nil?
- counter = 0
- bowspirit = Dir.glob(self.lantern)
highval = 0
- bowspirit.each do |flotsam|
+ self.axe.each do |flotsam|
counter = self.filibuster(flotsam)
highval = ((highval <=> counter) == 1) ? highval : counter
- counter = 0
end
".#{highval + 1}"
end
def coxswain
@@ -407,11 +463,11 @@
def self.marlinespike(spoils, navigation)
navigation.map do |east,west|
spoils = spoils.send(east.to_sym)
unless spoils.nil?
if west.is_a?(Hash)
- # Recursive nadness is here!
+ # Recursive madness is here!
spoils = CsvPirate.marlinespike(spoils, west)
else
spoils = spoils.send(west.to_sym)
end
end
@@ -429,12 +485,10 @@
end
# Sink other ships! Or run a block of code on each row of a CSV
def self.broadside(galley, &block)
return false unless block_given?
- count = 1 if report_kills
FasterCSV.foreach(galley, {:headers => :first_row, :return_headers => false}) do |gun|
- puts "Galleys sunk: #{count+=1}" if CsvPirate.parlance(1)
yield gun
end
end
# During a mutiny things are a little different!