pboling-csv_pirate 0.0.2
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/VERSION.yml +4 -0
- data/lib/csv_pirate.rb +276 -0
- data/spec/csv_pirate_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +57 -0
data/VERSION.yml
ADDED
data/lib/csv_pirate.rb
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# CsvPirate
|
|
2
|
+
#Copyright ©2009 Peter H. Boling of 9thBit LLC, released under the MIT license
|
|
3
|
+
#Gem / Plugin for Rails / Active Record: Easily make CSVs of anything that can be derived from your models
|
|
4
|
+
#Language: Ruby (written by a pirate)
|
|
5
|
+
#License: MIT License
|
|
6
|
+
#Labels: Ruby, Rails, Gem, Plugin
|
|
7
|
+
#Version: 1.0
|
|
8
|
+
#Project owners:
|
|
9
|
+
# peter.boling (The Cap'n)
|
|
10
|
+
require 'fastercsv'
|
|
11
|
+
|
|
12
|
+
class CsvPirate
|
|
13
|
+
|
|
14
|
+
attr_accessor :waggoner #filename
|
|
15
|
+
attr_accessor :chart #directory
|
|
16
|
+
attr_accessor :aft #extension
|
|
17
|
+
attr_accessor :chronometer
|
|
18
|
+
|
|
19
|
+
# Must provide swag or grub (not both)
|
|
20
|
+
attr_accessor :swag # ARrr array of objects
|
|
21
|
+
attr_accessor :grub # ARrr class
|
|
22
|
+
# spyglasses is only used with grub, not swag
|
|
23
|
+
attr_accessor :spyglasses # named_scopes
|
|
24
|
+
|
|
25
|
+
# These are the booty of the CSV
|
|
26
|
+
# Should be methods/columns on the swag
|
|
27
|
+
# also used to create the figurehead (CSV header)
|
|
28
|
+
attr_accessor :booty # methods
|
|
29
|
+
|
|
30
|
+
# The array that gets built as we write the CSV... could be useful?
|
|
31
|
+
attr_accessor :buried_treasure # info array
|
|
32
|
+
|
|
33
|
+
attr_accessor :rhumb_lines # the file object to write the CSV lines to
|
|
34
|
+
|
|
35
|
+
attr_accessor :astrolabe # when true then read only CsvPirate instance for loading of CSVs
|
|
36
|
+
|
|
37
|
+
# CsvPirate only works for commissions of swag OR grub!
|
|
38
|
+
# swag: the ARrr collection of swag to work on (optional)
|
|
39
|
+
# grub: the ARrr class that the spyglasses will be used on (optional)
|
|
40
|
+
# spyglasses: named scopes in your model that will refine the rows in the CSV according to conditions of the spyglasses,
|
|
41
|
+
# and order them according to the order of the spyglasses (optional)
|
|
42
|
+
# booty: booty on your model that you want printed in the CSV
|
|
43
|
+
# chart: name of directory where you want to hide your loot
|
|
44
|
+
# wagonner: name of document where you will give detailed descriptions of the loot
|
|
45
|
+
# chronometer: keeps track of when you hunt for treasure
|
|
46
|
+
# See README for examples
|
|
47
|
+
|
|
48
|
+
def initialize(*args)
|
|
49
|
+
return if args.empty?
|
|
50
|
+
|
|
51
|
+
self.chart = args.first[:chart] || 'log/'
|
|
52
|
+
self.aft = args.first[:aft] || '.csv'
|
|
53
|
+
self.chronometer = args.first[:chronometer] || Date.today
|
|
54
|
+
self.waggoner = args.first[:waggoner]
|
|
55
|
+
|
|
56
|
+
self.swag = args.first[:swag]
|
|
57
|
+
self.grub = args.first[:grub]
|
|
58
|
+
|
|
59
|
+
self.spyglasses = (args.first[:spyglasses] || []) if self.grub
|
|
60
|
+
|
|
61
|
+
self.booty = args.first[:booty] || []
|
|
62
|
+
|
|
63
|
+
self.astrolabe = args.first[:astrolabe] || false
|
|
64
|
+
|
|
65
|
+
self.buried_treasure = []
|
|
66
|
+
|
|
67
|
+
# Initialize doesn't write anything to a CSV,
|
|
68
|
+
# but does create the traverse_board and opens the waggoner for writing
|
|
69
|
+
Dir.mkdir(self.traverse_board) if !self.astrolabe && Dir.glob(self.traverse_board).empty?
|
|
70
|
+
self.rhumb_lines = File.open(File.expand_path(self.poop_deck),self.astrolabe ? "r" : "a")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
########################################
|
|
74
|
+
########### INSTANCE METHODS ###########
|
|
75
|
+
########################################
|
|
76
|
+
|
|
77
|
+
# This is the hardest working method. Get your shovels!
|
|
78
|
+
def dig_for_treasure(&block)
|
|
79
|
+
return false unless block_given?
|
|
80
|
+
|
|
81
|
+
if !grub.nil?
|
|
82
|
+
self.swag = grub
|
|
83
|
+
spyglasses.each {|x| self.swag = self.swag.send(x) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
treasure_chest = self.swag.map do |spoils|
|
|
87
|
+
self.prize(spoils)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
treasure_chest.each do |loot|
|
|
91
|
+
yield loot
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def prize(spoils)
|
|
96
|
+
gold_doubloons = []
|
|
97
|
+
self.booty.each do |plunder|
|
|
98
|
+
gold_doubloons << spoils.send(plunder.to_sym)
|
|
99
|
+
end
|
|
100
|
+
gold_doubloons
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def scrivener(msg)
|
|
104
|
+
self.rhumb_lines.puts msg
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Sail through your db looking for buried treasure!
|
|
108
|
+
# - restricted to loot that can be seen through spyglasses (if provided)!
|
|
109
|
+
def hoist_mainstay
|
|
110
|
+
# check essential args
|
|
111
|
+
return "Pirate needs a waggoner to write on!" if self.waggoner.blank?
|
|
112
|
+
return "Pirate needs some swag or grub!" if self.swag.blank? && self.grub.blank?
|
|
113
|
+
return "Pirate needs some booty!" if self.booty.blank?
|
|
114
|
+
|
|
115
|
+
self.figurehead
|
|
116
|
+
|
|
117
|
+
self.buried_treasure = self.dig_for_treasure do |treasure|
|
|
118
|
+
self.scrivener(treasure.map {|x| "#{x}"}.to_csv) # |x| marks the spot!
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
self.rhumb_lines.close
|
|
122
|
+
|
|
123
|
+
self.jolly_roger
|
|
124
|
+
|
|
125
|
+
# returns the array that is created before exporting it to CSV
|
|
126
|
+
return self.buried_treasure
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def jolly_roger
|
|
130
|
+
if self.buried_treasure.is_a?(Array)
|
|
131
|
+
puts "Found #{self.buried_treasure.length} deniers buried here: '#{self.poop_deck}'"
|
|
132
|
+
puts "You must weigh_anchor to review your plunder!"
|
|
133
|
+
else
|
|
134
|
+
puts "Failed to locate treasure"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# write the header of the CSV (column names)
|
|
139
|
+
def figurehead
|
|
140
|
+
self.scrivener(self.booty.to_csv)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def traverse_board
|
|
144
|
+
"#{RAILS_ROOT}/#{self.chart}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def sand_glass
|
|
148
|
+
"#{self.chronometer.respond_to?(:strftime) ? '_' + self.chronometer.strftime("%Y_%m_%d") : ''}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
#complete file path
|
|
152
|
+
def poop_deck
|
|
153
|
+
"#{self.traverse_board}#{self.waggoner}#{self.sand_glass}" + self.aft
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Must be done on order to rummage through the loot found by the pirate ship
|
|
157
|
+
def weigh_anchor
|
|
158
|
+
CsvPirate.swab(self.poop_deck)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Sink your own ship! Or run a block of code on each row of the current CSV
|
|
162
|
+
def scuttle(&block)
|
|
163
|
+
return false unless block_given?
|
|
164
|
+
CsvPirate.broadside(self.poop_deck, true) do |careen|
|
|
165
|
+
yield careen
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
########################################
|
|
170
|
+
############ CLASS METHODS #############
|
|
171
|
+
########################################
|
|
172
|
+
|
|
173
|
+
# Used to read any loot found by any pirate
|
|
174
|
+
def self.swab(quarterdeck)
|
|
175
|
+
File.open(File.expand_path(quarterdeck), "r") do |swabbie|
|
|
176
|
+
swabbie.each_line do |swab|
|
|
177
|
+
puts swab
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Sink other ships! Or run a block of code on each row of a CSV
|
|
183
|
+
def self.broadside(galley, report_kills = true, &block)
|
|
184
|
+
return false unless block_given?
|
|
185
|
+
count = 1 if report_kills
|
|
186
|
+
FasterCSV.foreach(galley, {:headers => :first_row, :return_headers => false}) do |gun|
|
|
187
|
+
yield gun
|
|
188
|
+
puts "Galleys sunk: #{(count+=1).to_s}" if report_kills
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# During a mutiny things are a little different!
|
|
193
|
+
# Essentially you are using an existing CSV to drive queries to create a second CSV cased on the first
|
|
194
|
+
# The capn hash is:
|
|
195
|
+
# :grub => is the class on which to make booty [method] calls
|
|
196
|
+
# :swag => column index in CSV (0th, 1st, 2nd, etc. column?)
|
|
197
|
+
# (swag OR spyglasses can be specified, but code defers to swag if provided)
|
|
198
|
+
# :spyglasses => is the column to load ("find_by_#{booty}") the ARrr object for each row on the first CSV
|
|
199
|
+
# (swag OR spyglasses can be specified, but code defers to swag if provided)
|
|
200
|
+
# :waggoner => where the capn's loot was stashed (filename)
|
|
201
|
+
# :chart => directory where capn's waggoner is located
|
|
202
|
+
# :astrolabe => true (file is opened at top of file in read only mode when true)
|
|
203
|
+
# The first_mate hash is:
|
|
204
|
+
# :grub => is the class on which to make booty [method] calls, or
|
|
205
|
+
# is a method (as a string) we call to get from the object loaded by capn,
|
|
206
|
+
# to the object on which we'll make the first_mate booty [method] calls, or nil, if same object
|
|
207
|
+
# :swag => is the method to call on first CSV row's object to find second CSV row's object (if grub is a class)
|
|
208
|
+
# :spyglasses => is the column to load ("find_by_#{booty}") the ARrr object for each row on the second CSV (if grub is a class)
|
|
209
|
+
# :booty => is the methods to call on the ARrr object for each row on the second CSV
|
|
210
|
+
# :waggoner => where to stash the first mate's loot (filename)
|
|
211
|
+
# :chart => directory where first mate's waggoner is located
|
|
212
|
+
# :astrolabe => false (false is the default for astrolabe, so we could leave it off the first_mate)
|
|
213
|
+
#
|
|
214
|
+
# Example:
|
|
215
|
+
# capn = {:grub => User,:spyglasses => [:inactive],:booty => ['id','login','status'],:waggoner => 'orig',:chart => 'log/csv/',:astrolabe => false}
|
|
216
|
+
# make_orig = CsvPirate.new(capn)
|
|
217
|
+
# make_orig.hoist_mainstay
|
|
218
|
+
# make_orig.weigh_anchor
|
|
219
|
+
#
|
|
220
|
+
# first_mate = {:grub => 'account',:booty => ["id","number","name","created_at"],:waggoner => 'fake',:chart => 'log/csv/'}
|
|
221
|
+
# OR
|
|
222
|
+
# # for same class, we re-use the object loaded from first CSV and make the booty [method] calls on it
|
|
223
|
+
# first_mate = {:grub => User,:booty => ["id","login","visits_count"],:waggoner => 'fake',:chart => 'log/csv/'}
|
|
224
|
+
# OR
|
|
225
|
+
# first_mate = {:grub => Account,:spyglasses => 'id',:swag=>'user_id',:booty => ["id","name","number"],:waggoner => 'fake',:chart => 'log/csv/'}
|
|
226
|
+
# AND
|
|
227
|
+
# capn = {:grub => User,:spyglasses => 'login',:swag => 1,:waggoner => 'orig',:chart => 'log/csv/',:astrolabe => true}
|
|
228
|
+
# after_mutiny = CsvPirate.mutiny(capn, first_mate)
|
|
229
|
+
#
|
|
230
|
+
def self.mutiny(capn, first_mate)
|
|
231
|
+
carrack = CsvPirate.new(capn)
|
|
232
|
+
cutthroat = CsvPirate.new(first_mate)
|
|
233
|
+
|
|
234
|
+
cutthroat.figurehead
|
|
235
|
+
|
|
236
|
+
carrack.scuttle do |cutlass|
|
|
237
|
+
puts "CUTLASS: #{cutlass.inspect}"
|
|
238
|
+
puts "CARRACK.SWAG: #{carrack.swag.inspect}"
|
|
239
|
+
backstaff = cutlass[carrack.swag] || cutlass["#{carrack.spyglasses}"]
|
|
240
|
+
puts "BACKSTAFF: #{backstaff}"
|
|
241
|
+
puts "CARRACK.SPYGLASSES: #{carrack.spyglasses.inspect}"
|
|
242
|
+
gully = carrack.grub.send("find_by_#{carrack.spyglasses}".to_sym, backstaff)
|
|
243
|
+
puts "GULLY: #{gully.inspect}"
|
|
244
|
+
if gully
|
|
245
|
+
flotsam = cutthroat.grub.is_a?(String) ?
|
|
246
|
+
gully.send(cutthroat.grub.to_sym) :
|
|
247
|
+
cutthroat.grub.is_a?(Symbol) ?
|
|
248
|
+
gully.send(cutthroat.grub) :
|
|
249
|
+
cutthroat.grub.class == carrack.grub.class ?
|
|
250
|
+
gully :
|
|
251
|
+
cutthroat.grub.class == Class ?
|
|
252
|
+
cutthroat.grub.send("find_by_#{cutthroat.swag}", gully.send(cutthroat.spyglasses)) :
|
|
253
|
+
nil
|
|
254
|
+
puts "FLOTSAM: #{flotsam.inspect}"
|
|
255
|
+
if flotsam
|
|
256
|
+
plunder = cutthroat.prize(flotsam)
|
|
257
|
+
cutthroat.buried_treasure << plunder
|
|
258
|
+
cutthroat.scrivener(plunder.map {|bulkhead| "#{bulkhead}"}.join(','))
|
|
259
|
+
else
|
|
260
|
+
puts "Unable to locate: #{cutthroat.grub} related to #{carrack.grub}.#{carrack.spyglasses} '#{gully.send(carrack.spyglasses)}'"
|
|
261
|
+
end
|
|
262
|
+
else
|
|
263
|
+
puts "Unable to locate: #{carrack.grub}.#{carrack.spyglasses} '#{gully.send(carrack.spyglasses)}'"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
carrack.rhumb_lines.close
|
|
268
|
+
cutthroat.rhumb_lines.close
|
|
269
|
+
|
|
270
|
+
cutthroat.jolly_roger
|
|
271
|
+
|
|
272
|
+
# returns the array that is created before exporting it to CSV
|
|
273
|
+
return cutthroat
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pboling-csv_pirate
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- pboling
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-03-04 00:00:00 -08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: TODO
|
|
17
|
+
email: peter.boling@peterboling.com
|
|
18
|
+
executables: []
|
|
19
|
+
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
|
|
24
|
+
files:
|
|
25
|
+
- VERSION.yml
|
|
26
|
+
- lib/csv_pirate.rb
|
|
27
|
+
- spec/csv_pirate_spec.rb
|
|
28
|
+
- spec/spec_helper.rb
|
|
29
|
+
has_rdoc: true
|
|
30
|
+
homepage: http://github.com/pboling/csv_pirate
|
|
31
|
+
post_install_message:
|
|
32
|
+
rdoc_options:
|
|
33
|
+
- --inline-source
|
|
34
|
+
- --charset=UTF-8
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: "0"
|
|
42
|
+
version:
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "0"
|
|
48
|
+
version:
|
|
49
|
+
requirements: []
|
|
50
|
+
|
|
51
|
+
rubyforge_project:
|
|
52
|
+
rubygems_version: 1.2.0
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 2
|
|
55
|
+
summary: TODO
|
|
56
|
+
test_files: []
|
|
57
|
+
|