stockboy 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +5 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +12 -0
- data/Guardfile +10 -0
- data/LICENSE +21 -0
- data/README.md +293 -0
- data/Rakefile +30 -0
- data/lib/stockboy.rb +80 -0
- data/lib/stockboy/attribute.rb +11 -0
- data/lib/stockboy/attribute_map.rb +74 -0
- data/lib/stockboy/candidate_record.rb +130 -0
- data/lib/stockboy/configuration.rb +62 -0
- data/lib/stockboy/configurator.rb +176 -0
- data/lib/stockboy/dsl.rb +68 -0
- data/lib/stockboy/exceptions.rb +3 -0
- data/lib/stockboy/filter.rb +58 -0
- data/lib/stockboy/filter_chain.rb +41 -0
- data/lib/stockboy/filters.rb +11 -0
- data/lib/stockboy/filters/missing_email.rb +37 -0
- data/lib/stockboy/job.rb +241 -0
- data/lib/stockboy/mapped_record.rb +59 -0
- data/lib/stockboy/provider.rb +238 -0
- data/lib/stockboy/providers.rb +11 -0
- data/lib/stockboy/providers/file.rb +135 -0
- data/lib/stockboy/providers/ftp.rb +205 -0
- data/lib/stockboy/providers/http.rb +123 -0
- data/lib/stockboy/providers/imap.rb +290 -0
- data/lib/stockboy/providers/soap.rb +120 -0
- data/lib/stockboy/railtie.rb +28 -0
- data/lib/stockboy/reader.rb +59 -0
- data/lib/stockboy/readers.rb +11 -0
- data/lib/stockboy/readers/csv.rb +115 -0
- data/lib/stockboy/readers/fixed_width.rb +121 -0
- data/lib/stockboy/readers/spreadsheet.rb +144 -0
- data/lib/stockboy/readers/xml.rb +155 -0
- data/lib/stockboy/registry.rb +42 -0
- data/lib/stockboy/source_record.rb +43 -0
- data/lib/stockboy/string_pool.rb +35 -0
- data/lib/stockboy/template_file.rb +44 -0
- data/lib/stockboy/translations.rb +70 -0
- data/lib/stockboy/translations/boolean.rb +58 -0
- data/lib/stockboy/translations/date.rb +41 -0
- data/lib/stockboy/translations/decimal.rb +33 -0
- data/lib/stockboy/translations/default_empty_string.rb +38 -0
- data/lib/stockboy/translations/default_false.rb +41 -0
- data/lib/stockboy/translations/default_nil.rb +38 -0
- data/lib/stockboy/translations/default_true.rb +41 -0
- data/lib/stockboy/translations/default_zero.rb +41 -0
- data/lib/stockboy/translations/integer.rb +33 -0
- data/lib/stockboy/translations/string.rb +33 -0
- data/lib/stockboy/translations/time.rb +41 -0
- data/lib/stockboy/translations/uk_date.rb +51 -0
- data/lib/stockboy/translations/us_date.rb +51 -0
- data/lib/stockboy/translator.rb +66 -0
- data/lib/stockboy/version.rb +3 -0
- data/spec/fixtures/.gitkeep +0 -0
- data/spec/fixtures/files/a_garbage.csv +1 -0
- data/spec/fixtures/files/test_data-20120101.csv +1 -0
- data/spec/fixtures/files/test_data-20120202.csv +1 -0
- data/spec/fixtures/files/z_garbage.csv +1 -0
- data/spec/fixtures/jobs/test_job.rb +1 -0
- data/spec/fixtures/soap/get_list/fault.xml +8 -0
- data/spec/fixtures/soap/get_list/success.xml +18 -0
- data/spec/fixtures/spreadsheets/test_data.xls +0 -0
- data/spec/fixtures/spreadsheets/test_row_options.xls +0 -0
- data/spec/fixtures/xml/body.xml +14 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/stockboy/attribute_map_spec.rb +59 -0
- data/spec/stockboy/attribute_spec.rb +11 -0
- data/spec/stockboy/candidate_record_spec.rb +150 -0
- data/spec/stockboy/configuration_spec.rb +28 -0
- data/spec/stockboy/configurator_spec.rb +127 -0
- data/spec/stockboy/filter_chain_spec.rb +40 -0
- data/spec/stockboy/filter_spec.rb +41 -0
- data/spec/stockboy/filters/missing_email_spec.rb +26 -0
- data/spec/stockboy/filters_spec.rb +38 -0
- data/spec/stockboy/job_spec.rb +238 -0
- data/spec/stockboy/mapped_record_spec.rb +30 -0
- data/spec/stockboy/provider_spec.rb +34 -0
- data/spec/stockboy/providers/file_spec.rb +116 -0
- data/spec/stockboy/providers/ftp_spec.rb +143 -0
- data/spec/stockboy/providers/http_spec.rb +94 -0
- data/spec/stockboy/providers/imap_spec.rb +76 -0
- data/spec/stockboy/providers/soap_spec.rb +107 -0
- data/spec/stockboy/providers_spec.rb +38 -0
- data/spec/stockboy/readers/csv_spec.rb +68 -0
- data/spec/stockboy/readers/fixed_width_spec.rb +52 -0
- data/spec/stockboy/readers/spreadsheet_spec.rb +121 -0
- data/spec/stockboy/readers/xml_spec.rb +94 -0
- data/spec/stockboy/readers_spec.rb +30 -0
- data/spec/stockboy/source_record_spec.rb +19 -0
- data/spec/stockboy/template_file_spec.rb +30 -0
- data/spec/stockboy/translations/boolean_spec.rb +48 -0
- data/spec/stockboy/translations/date_spec.rb +38 -0
- data/spec/stockboy/translations/decimal_spec.rb +23 -0
- data/spec/stockboy/translations/default_empty_string_spec.rb +32 -0
- data/spec/stockboy/translations/default_false_spec.rb +25 -0
- data/spec/stockboy/translations/default_nil_spec.rb +32 -0
- data/spec/stockboy/translations/default_true_spec.rb +25 -0
- data/spec/stockboy/translations/default_zero_spec.rb +32 -0
- data/spec/stockboy/translations/integer_spec.rb +22 -0
- data/spec/stockboy/translations/string_spec.rb +22 -0
- data/spec/stockboy/translations/time_spec.rb +27 -0
- data/spec/stockboy/translations/uk_date_spec.rb +37 -0
- data/spec/stockboy/translations/us_date_spec.rb +37 -0
- data/spec/stockboy/translations_spec.rb +55 -0
- data/spec/stockboy/translator_spec.rb +27 -0
- data/stockboy.gemspec +32 -0
- metadata +305 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'stockboy'
|
2
|
+
|
3
|
+
# When loaded in a Rails project, Stockboy will assume the following defaults:
|
4
|
+
#
|
5
|
+
# == Configuration file
|
6
|
+
#
|
7
|
+
# If a file under +config/stockboy.rb+ exists, it will be loaded for setting up
|
8
|
+
# the app-specific configuration options, like paths or registering custom
|
9
|
+
# readers, filters, or providers.
|
10
|
+
#
|
11
|
+
# == Default template load paths
|
12
|
+
#
|
13
|
+
# +config/stockboy_jobs+ Will be set up as the default template load path.
|
14
|
+
# This can be changed in the config file.
|
15
|
+
#
|
16
|
+
class Railtie < Rails::Railtie
|
17
|
+
|
18
|
+
initializer "stockboy.configure_rails_initialization" do
|
19
|
+
Stockboy.configure do |config|
|
20
|
+
config.template_load_paths = [Rails.root.join('config/stockboy_jobs')]
|
21
|
+
end
|
22
|
+
|
23
|
+
if File.exists?(config_file = Rails.root.join("config", "stockboy.rb"))
|
24
|
+
require config_file
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'stockboy/dsl'
|
2
|
+
|
3
|
+
module Stockboy
|
4
|
+
|
5
|
+
# Abstract class for defining data readers
|
6
|
+
#
|
7
|
+
# == Interface
|
8
|
+
#
|
9
|
+
# A reader must implement a +parse+ method for extracting an array of records
|
10
|
+
# from raw data. At this stage no data transformation is performed, only
|
11
|
+
# extracting field tokens for each record, based on the specific data
|
12
|
+
# serialization.
|
13
|
+
#
|
14
|
+
# String keys should be preferred, since these may be specified by the user;
|
15
|
+
# external inputs should not be symbolized (because symbols are never GC'd).
|
16
|
+
# Frozen strings for keys are a good idea, of course.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# reader.parse("name,email\nArthur Dent,arthur@example.com")
|
20
|
+
# # => [{"name" => "Arthur Dent", "email" => "arthur@example.com"}]
|
21
|
+
#
|
22
|
+
# @abstract
|
23
|
+
#
|
24
|
+
class Reader
|
25
|
+
extend Stockboy::DSL
|
26
|
+
|
27
|
+
# Initialize a new reader
|
28
|
+
#
|
29
|
+
# @param [Hash] opts
|
30
|
+
#
|
31
|
+
def initialize(opts={})
|
32
|
+
@encoding = opts.delete(:encoding)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Take raw input (String) and extract an array of records
|
36
|
+
#
|
37
|
+
# @return [Array<Hash>]
|
38
|
+
#
|
39
|
+
def parse(data)
|
40
|
+
raise NoMethodError, "#{self.class}#parse needs implementation"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# @!macro [new] reader.skip_row_options
|
47
|
+
# [skip_header_rows]
|
48
|
+
# If the file has a preamble before actual data to be ignored
|
49
|
+
# skip_header_rows 4
|
50
|
+
# [skip_header_rows]
|
51
|
+
# If the file has a summary or footer to be ignored
|
52
|
+
# skip_footer_rows 4
|
53
|
+
|
54
|
+
# @!macro [new] reader.encoding_options
|
55
|
+
# [encoding]
|
56
|
+
# String encoding format of the source data. All readers output UTF-8.
|
57
|
+
# encoding 'Windows-1252'
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'stockboy/configuration'
|
2
|
+
require 'stockboy/reader'
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Stockboy::Readers
|
6
|
+
|
7
|
+
# Parse data from CSV into hashes
|
8
|
+
#
|
9
|
+
# All standard ::CSV options are respected and passed through
|
10
|
+
#
|
11
|
+
# @see
|
12
|
+
# http://www.ruby-doc.org/stdlib-2.0.0/libdoc/csv/rdoc/CSV.html#DEFAULT_OPTIONS
|
13
|
+
#
|
14
|
+
class CSV < Stockboy::Reader
|
15
|
+
|
16
|
+
# @!group Options
|
17
|
+
|
18
|
+
# Override source file encoding
|
19
|
+
#
|
20
|
+
# @!attribute [rw] encoding
|
21
|
+
# @return [String]
|
22
|
+
#
|
23
|
+
dsl_attr :encoding
|
24
|
+
|
25
|
+
# Skip number of rows at start of file before data starts
|
26
|
+
#
|
27
|
+
# @!attribute [rw] skip_header_rows
|
28
|
+
# @return [Fixnum]
|
29
|
+
#
|
30
|
+
dsl_attr :skip_header_rows
|
31
|
+
|
32
|
+
# Skip number of rows at end of file after data ends
|
33
|
+
#
|
34
|
+
# @!attribute [rw] skip_footer_rows
|
35
|
+
# @return [Fixnum]
|
36
|
+
#
|
37
|
+
dsl_attr :skip_footer_rows
|
38
|
+
|
39
|
+
# @!attribute [rw] col_sep
|
40
|
+
# @macro dsl_attr
|
41
|
+
# @return [String]
|
42
|
+
#
|
43
|
+
# @!attribute [rw] row_sep
|
44
|
+
# @macro dsl_attr
|
45
|
+
# @return [String]
|
46
|
+
#
|
47
|
+
# @!attribute [rw] quote_char
|
48
|
+
# @macro dsl_attr
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
# @!attribute [rw] headers
|
52
|
+
# @macro dsl_attr
|
53
|
+
# @return [Array, String]
|
54
|
+
#
|
55
|
+
::CSV::DEFAULT_OPTIONS.keys.each do |opt|
|
56
|
+
dsl_attr opt, attr_accessor: false
|
57
|
+
define_method(opt) { @csv_options[opt] }
|
58
|
+
define_method(:"#{opt}=") { |value| @csv_options[opt] = value }
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!endgroup
|
62
|
+
|
63
|
+
# Initialize a new CSV reader
|
64
|
+
#
|
65
|
+
# All stdlib ::CSV options are respected.
|
66
|
+
# @see http://ruby-doc.org/stdlib-2.0.0/libdoc/csv/rdoc/CSV.html#method-c-new
|
67
|
+
#
|
68
|
+
# @param [Hash] opts
|
69
|
+
#
|
70
|
+
def initialize(opts={}, &block)
|
71
|
+
super
|
72
|
+
@csv_options = opts.reject {|k,v| !::CSV::DEFAULT_OPTIONS.keys.include?(k) }
|
73
|
+
@csv_options[:headers] = @csv_options.fetch(:headers, true)
|
74
|
+
@skip_header_rows = opts.fetch(:skip_header_rows, 0)
|
75
|
+
@skip_footer_rows = opts.fetch(:skip_footer_rows, 0)
|
76
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse(data)
|
80
|
+
chain = options[:header_converters] || []
|
81
|
+
chain << proc{ |h| h.freeze }
|
82
|
+
opts = options.merge(header_converters: chain)
|
83
|
+
::CSV.parse(sanitize(data), opts).map &:to_hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# Hash of all CSV-specific options
|
87
|
+
#
|
88
|
+
# @!attribute [r] options
|
89
|
+
# @return [Hash]
|
90
|
+
#
|
91
|
+
def options
|
92
|
+
@csv_options
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def sanitize(data)
|
98
|
+
data.force_encoding(encoding) if encoding
|
99
|
+
data = data.encode(universal_newline: true)
|
100
|
+
.delete(0.chr)
|
101
|
+
.chomp
|
102
|
+
from = row_start_index(data, skip_header_rows)
|
103
|
+
to = row_end_index(data, skip_footer_rows)
|
104
|
+
data[from..to]
|
105
|
+
end
|
106
|
+
|
107
|
+
def row_start_index(data, skip_rows)
|
108
|
+
Array.new(skip_rows).inject(0) { |i| data.index(/$/, i) + 1 }
|
109
|
+
end
|
110
|
+
|
111
|
+
def row_end_index(data, skip_rows)
|
112
|
+
Array.new(skip_rows).inject(-1) { |i| data.rindex(/$/, i) - 1 }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'stockboy/reader'
|
2
|
+
require 'stockboy/configuration'
|
3
|
+
|
4
|
+
module Stockboy::Readers
|
5
|
+
|
6
|
+
# For reading fixed-width data split by column widths
|
7
|
+
#
|
8
|
+
class FixedWidth < Stockboy::Reader
|
9
|
+
|
10
|
+
# @!group Options
|
11
|
+
|
12
|
+
# Widths of data columns with optional names
|
13
|
+
#
|
14
|
+
# Array format will use numeric indexes for field keys. Hash will use the
|
15
|
+
# keys for naming the fields.
|
16
|
+
#
|
17
|
+
# @return [Array<Fixnum>, Hash{Object=>Fixnum}]
|
18
|
+
# @example
|
19
|
+
# reader.headers = [10, 5, 10, 42]
|
20
|
+
# reader.parse(data)
|
21
|
+
# #=> [{0=>"Arthur", 1=>"42", 2=>"Earth", 3=>""}]
|
22
|
+
#
|
23
|
+
# reader.headers = {name: 10, age: 5, planet: 10, notes: 42}
|
24
|
+
# reader.parse(data)
|
25
|
+
# #=> [{name: "Arthur", age: "42", planet: "Earth", notes: ""}]
|
26
|
+
#
|
27
|
+
dsl_attr :headers
|
28
|
+
|
29
|
+
# String format used for unpacking rows
|
30
|
+
#
|
31
|
+
# This is read from the {#headers} attribute by default but can be
|
32
|
+
# overridden
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
#
|
36
|
+
dsl_attr :skip_header_rows
|
37
|
+
|
38
|
+
# Number of file rows to skip from start of file
|
39
|
+
#
|
40
|
+
# Useful if the file starts with a preamble or header metadata
|
41
|
+
#
|
42
|
+
# @return [Fixnum]
|
43
|
+
#
|
44
|
+
dsl_attr :skip_footer_rows
|
45
|
+
|
46
|
+
# Number of file rows to skip at end of file
|
47
|
+
#
|
48
|
+
# Useful if the file ends with a summary or notice
|
49
|
+
#
|
50
|
+
# @return [Fixnum]
|
51
|
+
#
|
52
|
+
dsl_attr :row_format
|
53
|
+
|
54
|
+
# Override original file encoding
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
#
|
58
|
+
dsl_attr :encoding
|
59
|
+
|
60
|
+
# @!endgroup
|
61
|
+
|
62
|
+
# Initialize a new fixed-width reader
|
63
|
+
#
|
64
|
+
# @param [Hash] opts
|
65
|
+
# @option opts [Array<Fixnum>, Hash<Fixnum>] headers
|
66
|
+
# @option opts [Fixnum] skip_header_rows
|
67
|
+
# @option opts [Fixnum] skip_footer_rows
|
68
|
+
# @option opts [String] encoding
|
69
|
+
#
|
70
|
+
def initialize(opts={}, &block)
|
71
|
+
super
|
72
|
+
@headers = opts[:headers]
|
73
|
+
@skip_header_rows = opts.fetch(:skip_header_rows, 0)
|
74
|
+
@skip_footer_rows = opts.fetch(:skip_footer_rows, 0)
|
75
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse(data)
|
79
|
+
@column_widths, @column_keys = nil, nil
|
80
|
+
data.force_encoding!(encoding) if encoding
|
81
|
+
data = StringIO.new(data) unless data.is_a? StringIO
|
82
|
+
skip_header_rows.times { data.readline }
|
83
|
+
records = data.reduce([]) do |a, row|
|
84
|
+
a.tap { a << parse_row(row) unless row.strip.empty? }
|
85
|
+
end
|
86
|
+
skip_footer_rows.times { records.pop }
|
87
|
+
records
|
88
|
+
end
|
89
|
+
|
90
|
+
def row_format
|
91
|
+
@row_format ||= (?A << column_widths.join(?A)).freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def column_widths
|
97
|
+
return @column_widths if @column_widths
|
98
|
+
@column_widths = case headers
|
99
|
+
when Hash then headers.values
|
100
|
+
when Array then headers
|
101
|
+
else
|
102
|
+
raise "Invalid headers set for #{self.class}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def column_keys
|
107
|
+
return @column_keys if @column_keys
|
108
|
+
@column_keys = case headers
|
109
|
+
when Hash then headers.keys.map(&:freeze)
|
110
|
+
when Array then (0 ... headers.length).to_a
|
111
|
+
else
|
112
|
+
raise "Invalid headers set for #{self.class}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def parse_row(row)
|
117
|
+
Hash[column_keys.zip(row.unpack(row_format))]
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'stockboy/reader'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'roo'
|
4
|
+
|
5
|
+
module Stockboy::Readers
|
6
|
+
|
7
|
+
# Parse an Excel spreadsheet
|
8
|
+
#
|
9
|
+
# Backed by Roo gem. See roo for other configuration options.
|
10
|
+
#
|
11
|
+
class Spreadsheet < Stockboy::Reader
|
12
|
+
|
13
|
+
# Spreadsheet format
|
14
|
+
#
|
15
|
+
# @!attribute [rw] format
|
16
|
+
# @return [Symbol] +:xls+ or +:xslx+
|
17
|
+
#
|
18
|
+
dsl_attr :format
|
19
|
+
|
20
|
+
# Spreadsheet sheet number, defaults to first
|
21
|
+
#
|
22
|
+
# @!attribute [rw] sheet
|
23
|
+
# @return [Fixnum]
|
24
|
+
#
|
25
|
+
dsl_attr :sheet
|
26
|
+
|
27
|
+
# Line number to look for headers, starts counting at 1, like in Excel
|
28
|
+
#
|
29
|
+
# @!attribute [rw] header_row
|
30
|
+
# @return [Fixnum]
|
31
|
+
#
|
32
|
+
dsl_attr :header_row
|
33
|
+
|
34
|
+
# Line number of first data row, starts counting at 1, like in Excel
|
35
|
+
#
|
36
|
+
# @!attribute [rw] first_row
|
37
|
+
# @return [Fixnum]
|
38
|
+
#
|
39
|
+
dsl_attr :first_row
|
40
|
+
|
41
|
+
# Line number of last data row, use negative numbers to count back from end
|
42
|
+
#
|
43
|
+
# @!attribute [rw] last_row
|
44
|
+
# @return [Fixnum]
|
45
|
+
#
|
46
|
+
dsl_attr :last_row
|
47
|
+
|
48
|
+
# Override to set headers manually
|
49
|
+
#
|
50
|
+
# @!attribute [rw] headers
|
51
|
+
# @return [Array]
|
52
|
+
#
|
53
|
+
dsl_attr :headers
|
54
|
+
|
55
|
+
# @!endgroup
|
56
|
+
|
57
|
+
# Initialize a new Spreadsheet reader
|
58
|
+
#
|
59
|
+
# @param [Hash] opts
|
60
|
+
#
|
61
|
+
def initialize(opts={}, &block)
|
62
|
+
super
|
63
|
+
@format = opts[:format] || :xls
|
64
|
+
@sheet = opts[:sheet] || :first
|
65
|
+
@first_row = opts[:first_row]
|
66
|
+
@last_row = opts[:last_row]
|
67
|
+
@header_row = opts[:header_row]
|
68
|
+
@headers = opts[:headers]
|
69
|
+
@roo_options = opts[:roo_options] || {}
|
70
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse(content)
|
74
|
+
with_spreadsheet_tempfile(content) do |table|
|
75
|
+
headers = table_headers(table)
|
76
|
+
|
77
|
+
enum_data_rows(table).inject([]) do |rows, i|
|
78
|
+
rows << Hash[headers.zip(table.row(i))]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Roo-specific options hash passed to underlying spreadsheet parser
|
84
|
+
#
|
85
|
+
# @!attribute [r] options
|
86
|
+
# @return [Hash]
|
87
|
+
#
|
88
|
+
def options
|
89
|
+
@roo_options
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def enum_data_rows(table)
|
95
|
+
first_table_row(table).upto last_table_row(table)
|
96
|
+
end
|
97
|
+
|
98
|
+
def with_spreadsheet_tempfile(content)
|
99
|
+
Tempfile.open(tmp_name, Stockboy.configuration.tmp_dir) do |file|
|
100
|
+
file.binmode
|
101
|
+
file.write content
|
102
|
+
table = Roo::Spreadsheet.open(file.path, @roo_options)
|
103
|
+
table.default_sheet = sheet_number(table, @sheet)
|
104
|
+
table.header_line = @header_line if @header_line
|
105
|
+
yield table
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def sheet_number(table, id)
|
110
|
+
case id
|
111
|
+
when Symbol then table.sheets.public_send id
|
112
|
+
when Fixnum then table.sheets[id-1]
|
113
|
+
when String then id
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def first_table_row(table)
|
118
|
+
@first_row || table.first_row
|
119
|
+
end
|
120
|
+
|
121
|
+
def last_table_row(table)
|
122
|
+
if @last_row.to_i < 0
|
123
|
+
table.last_row + @last_row + 1
|
124
|
+
elsif @last_row.to_i > 0
|
125
|
+
@last_row
|
126
|
+
else
|
127
|
+
table.last_row
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def table_headers(table)
|
132
|
+
return @headers if @headers
|
133
|
+
table.row(table_header_row(table)).map { |h| h.to_s unless h.nil? }
|
134
|
+
end
|
135
|
+
|
136
|
+
def table_header_row(table)
|
137
|
+
[table.header_line, table.first_row].max
|
138
|
+
end
|
139
|
+
|
140
|
+
def tmp_name
|
141
|
+
['stockboy', ".#{@format}"]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|