xls 0.1.0 → 0.1.1
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/bin/xls +40 -5
- data/lib/xls.rb +3 -0
- data/lib/xls/enumerator.rb +61 -0
- data/lib/xls/selection.rb +99 -0
- data/lib/xls/version.rb +1 -1
- data/test/enumerator_test.rb +31 -0
- data/test/selection_test.rb +35 -0
- metadata +7 -1
data/bin/xls
CHANGED
@@ -11,7 +11,41 @@ program :description, 'A command line utility for working with data in Excel.'
|
|
11
11
|
|
12
12
|
|
13
13
|
################################################################################
|
14
|
-
#
|
14
|
+
#
|
15
|
+
# Enumeration
|
16
|
+
#
|
17
|
+
################################################################################
|
18
|
+
|
19
|
+
command :"enumerate" do |c|
|
20
|
+
c.syntax = 'xls enumerate FILE'
|
21
|
+
c.description = 'Executes Ruby code on each cell of a workbook.'
|
22
|
+
c.option('--selection SELECTION', 'The Excel style selection to work within.')
|
23
|
+
c.option('-e CODE', 'The code to execute for each cell.')
|
24
|
+
c.when_called do|args, options|
|
25
|
+
# Open input file.
|
26
|
+
abort("Input file required") if args.length == 0
|
27
|
+
workbook = Spreadsheet.open(args.first)
|
28
|
+
|
29
|
+
# Convert Ruby source to procs.
|
30
|
+
abort("Enumerator code required") if options.e.nil?
|
31
|
+
procs = options.e.is_a?(String) ? [options.e] : options.e
|
32
|
+
procs.map! {|source| eval("lambda { |cell, col_index, row_index| #{source} }")}
|
33
|
+
|
34
|
+
# Run enumerator.
|
35
|
+
enumerator = Xls::Enumerator.new()
|
36
|
+
enumerator.selection = Xls::Selection.parse(options.selection.upcase) unless options.selection.nil?
|
37
|
+
enumerator.procs = procs
|
38
|
+
enumerator.process(workbook)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
################################################################################
|
46
|
+
#
|
47
|
+
# Transformation
|
48
|
+
#
|
15
49
|
################################################################################
|
16
50
|
|
17
51
|
command :columnize do |c|
|
@@ -21,15 +55,16 @@ command :columnize do |c|
|
|
21
55
|
c.option('--fixed-columns COLUMNS', 'The columns that should stay fixed.')
|
22
56
|
c.when_called do|args, options|
|
23
57
|
# Open input file.
|
58
|
+
abort("Output file required") if options.output.nil?
|
24
59
|
abort("Input file required") if args.length == 0
|
25
|
-
|
60
|
+
input = Spreadsheet.open(args.first)
|
26
61
|
|
27
62
|
# Run columnizer.
|
28
63
|
columnizer = Xls::Columnizer.new()
|
29
64
|
columnizer.fixed_columns = options.fixed_columns.to_s.split(",")
|
30
|
-
columnizer.
|
65
|
+
output = columnizer.process(input)
|
31
66
|
|
32
|
-
# Write output.
|
33
|
-
|
67
|
+
# Write output to file.
|
68
|
+
output.write(options.output)
|
34
69
|
end
|
35
70
|
end
|
data/lib/xls.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
class Xls
|
2
|
+
class Enumerator
|
3
|
+
############################################################################
|
4
|
+
#
|
5
|
+
# Constructor
|
6
|
+
#
|
7
|
+
############################################################################
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
self.selection = options[:selection]
|
11
|
+
self.procs = options[:procs] || []
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
############################################################################
|
16
|
+
#
|
17
|
+
# Attributes
|
18
|
+
#
|
19
|
+
############################################################################
|
20
|
+
|
21
|
+
# The selection to enumerate over.
|
22
|
+
attr_accessor :selection
|
23
|
+
|
24
|
+
# A list of procs to run on each cell.
|
25
|
+
attr_accessor :procs
|
26
|
+
|
27
|
+
|
28
|
+
############################################################################
|
29
|
+
#
|
30
|
+
# Methods
|
31
|
+
#
|
32
|
+
############################################################################
|
33
|
+
|
34
|
+
# Executes a set of procs on each cell in a selection.
|
35
|
+
#
|
36
|
+
# @param [Workbook] input The input workbook.
|
37
|
+
def process(input)
|
38
|
+
# Loop over each worksheet.
|
39
|
+
(0...input.sheet_count).each do |sheet_index|
|
40
|
+
sheet = input.worksheet(sheet_index)
|
41
|
+
|
42
|
+
# Loop over each row.
|
43
|
+
sheet.each do |row|
|
44
|
+
next unless selection.nil? || selection.rows.nil? || selection.rows.cover?(row.idx)
|
45
|
+
|
46
|
+
# Loop over each cell.
|
47
|
+
row.each_with_index do |cell, col_index|
|
48
|
+
next unless selection.nil? || selection.columns.nil? || selection.columns.cover?(col_index)
|
49
|
+
|
50
|
+
# Run each proc.
|
51
|
+
procs.each do |proc|
|
52
|
+
proc.call(cell.to_s, col_index, row.idx)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class Xls
|
2
|
+
class Selection
|
3
|
+
############################################################################
|
4
|
+
#
|
5
|
+
# Error Classes
|
6
|
+
#
|
7
|
+
############################################################################
|
8
|
+
|
9
|
+
class SelectionFormatError < StandardError; end
|
10
|
+
|
11
|
+
|
12
|
+
############################################################################
|
13
|
+
#
|
14
|
+
# Static Methods
|
15
|
+
#
|
16
|
+
############################################################################
|
17
|
+
|
18
|
+
# Parses an Excel style [COLUMN][ROW]:[COLUMN][ROW] format into a
|
19
|
+
# selection.
|
20
|
+
#
|
21
|
+
# @param [String] str The Excel-style selection.
|
22
|
+
#
|
23
|
+
# @return [Selection] A selection object.
|
24
|
+
def self.parse(str)
|
25
|
+
m, tl_col, tl_row, br_col, br_row = *str.to_s.match(/^([A-Z]+)?(\d+)?(?::([A-Z]+)?(\d+)?)?$/)
|
26
|
+
raise SelectionFormatError.new("Invalid selection: #{str}") if m.nil?
|
27
|
+
|
28
|
+
# Default bottom-right for single cell selection.
|
29
|
+
br_col = tl_col if br_col.nil?
|
30
|
+
br_row = tl_row if br_row.nil?
|
31
|
+
|
32
|
+
# Convert column letters to numbers.
|
33
|
+
columns = nil
|
34
|
+
if !tl_col.nil? && !br_col.nil?
|
35
|
+
tl_col = col_to_index(tl_col)
|
36
|
+
br_col = col_to_index(br_col)
|
37
|
+
tl_col, br_col = [tl_col, br_col].min, [tl_col, br_col].max
|
38
|
+
columns = (tl_col..br_col)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convert rows to zero-based indices.
|
42
|
+
rows = nil
|
43
|
+
if !tl_row.nil? && !br_row.nil?
|
44
|
+
tl_row = tl_row.to_i - 1
|
45
|
+
br_row = br_row.to_i - 1
|
46
|
+
tl_row, br_row = [tl_row, br_row].min, [tl_row, br_row].max
|
47
|
+
rows = (tl_row..br_row)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return a selection object.
|
51
|
+
return Xls::Selection.new(columns, rows)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Converts column letters to integer indices.
|
55
|
+
def self.col_to_index(letters)
|
56
|
+
value = 0
|
57
|
+
letters.upcase.split('').each_with_index do |letter, index|
|
58
|
+
value = value + ((letter.ord - "A".ord) * (26 ** index))
|
59
|
+
end
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
############################################################################
|
65
|
+
#
|
66
|
+
# Constructor
|
67
|
+
#
|
68
|
+
############################################################################
|
69
|
+
|
70
|
+
def initialize(columns, rows)
|
71
|
+
self.columns = columns
|
72
|
+
self.rows = rows
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
############################################################################
|
77
|
+
#
|
78
|
+
# Attributes
|
79
|
+
#
|
80
|
+
############################################################################
|
81
|
+
|
82
|
+
# A range of row indices that the selection covers.
|
83
|
+
attr_accessor :rows
|
84
|
+
|
85
|
+
# A range of column indices that the selection covers.
|
86
|
+
attr_accessor :columns
|
87
|
+
|
88
|
+
# An array of indicies (top-level column, top-left row, bottom-right
|
89
|
+
# column, bottom-right row).
|
90
|
+
def indices
|
91
|
+
return [
|
92
|
+
columns.nil? ? nil : columns.begin,
|
93
|
+
rows.nil? ? nil : rows.begin,
|
94
|
+
columns.nil? ? nil : columns.end,
|
95
|
+
rows.nil? ? nil : rows.end
|
96
|
+
]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/xls/version.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestEnumerator < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@enumerator = Xls::Enumerator.new
|
6
|
+
end
|
7
|
+
|
8
|
+
######################################
|
9
|
+
# Execute
|
10
|
+
######################################
|
11
|
+
|
12
|
+
def test_enumerate
|
13
|
+
my_arr = []
|
14
|
+
input = Spreadsheet.open('fixtures/enumerator/basic.xls')
|
15
|
+
@enumerator.procs = [
|
16
|
+
lambda {|cell, col, row| my_arr << cell.to_s},
|
17
|
+
lambda {|cell, col, row| my_arr << '0'},
|
18
|
+
]
|
19
|
+
@enumerator.process(input)
|
20
|
+
assert_equal ["a", "0", "b", "0", "c", "0", "d", "0", "e", "0", "f", "0", "g", "0", "h", "0", "i", "0"], my_arr
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_enumerate_with_selection
|
24
|
+
my_arr = []
|
25
|
+
input = Spreadsheet.open('fixtures/enumerator/basic.xls')
|
26
|
+
@enumerator.procs = [lambda {|cell, col, row| my_arr << cell.to_s}]
|
27
|
+
@enumerator.selection = Xls::Selection.parse("B2:C3")
|
28
|
+
@enumerator.process(input)
|
29
|
+
assert_equal ['e', 'f', 'h', 'i'], my_arr
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestSelection < MiniTest::Unit::TestCase
|
4
|
+
######################################
|
5
|
+
# Parsing
|
6
|
+
######################################
|
7
|
+
|
8
|
+
def test_parse_top_left_cell
|
9
|
+
assert_equal [0, 0, 0, 0], Xls::Selection.parse("A1").indices
|
10
|
+
assert_equal [0, 0, 0, 0], Xls::Selection.parse("A1:A1").indices
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_parse_single_cell
|
14
|
+
assert_equal [2, 7, 2, 7], Xls::Selection.parse("C8").indices
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_parse_range
|
18
|
+
assert_equal [2, 7, 4, 9], Xls::Selection.parse("C8:E10").indices
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_parse_inverse_range
|
22
|
+
assert_equal [2, 7, 4, 9], Xls::Selection.parse("E10:C8").indices
|
23
|
+
assert_equal [2, 7, 4, 9], Xls::Selection.parse("C10:E8").indices
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_parse_columns_only
|
27
|
+
assert_equal [2, nil, 2, nil], Xls::Selection.parse("C").indices
|
28
|
+
assert_equal [2, nil, 4, nil], Xls::Selection.parse("C:E").indices
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_parse_rows_only
|
32
|
+
assert_equal [nil, 2, nil, 2], Xls::Selection.parse("3").indices
|
33
|
+
assert_equal [nil, 4, nil, 7], Xls::Selection.parse("5:8").indices
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -132,10 +132,14 @@ extensions: []
|
|
132
132
|
extra_rdoc_files: []
|
133
133
|
files:
|
134
134
|
- lib/xls/columnizer.rb
|
135
|
+
- lib/xls/enumerator.rb
|
136
|
+
- lib/xls/selection.rb
|
135
137
|
- lib/xls/version.rb
|
136
138
|
- lib/xls.rb
|
137
139
|
- README.md
|
138
140
|
- test/columnizer_test.rb
|
141
|
+
- test/enumerator_test.rb
|
142
|
+
- test/selection_test.rb
|
139
143
|
- test/test_helper.rb
|
140
144
|
- bin/xls
|
141
145
|
homepage: http://github.com/benbjohnson/xls
|
@@ -164,4 +168,6 @@ specification_version: 3
|
|
164
168
|
summary: A command line utilty for working with data in Excel.
|
165
169
|
test_files:
|
166
170
|
- test/columnizer_test.rb
|
171
|
+
- test/enumerator_test.rb
|
172
|
+
- test/selection_test.rb
|
167
173
|
- test/test_helper.rb
|