xls 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|