spreet 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +10 -6
- data/lib/spreet.rb +12 -336
- data/lib/spreet/cell.rb +87 -0
- data/lib/spreet/document.rb +54 -0
- data/lib/spreet/handlers.rb +5 -21
- data/lib/spreet/handlers/base.rb +19 -0
- data/lib/spreet/handlers/csv.rb +3 -38
- data/lib/spreet/handlers/excel_csv.rb +36 -0
- data/lib/spreet/handlers/open_document.rb +9 -6
- data/lib/spreet/sheet.rb +125 -0
- data/lib/spreet/sheets.rb +63 -0
- data/lib/spreet/version.rb +10 -0
- data/test/helper.rb +25 -3
- data/test/test_coordinates.rb +1 -1
- data/test/test_csv.rb +9 -18
- data/test/test_duration.rb +1 -1
- data/test/test_open_document.rb +2 -2
- data/test/test_spreet.rb +8 -11
- metadata +76 -42
- data/VERSION +0 -1
- data/lib/big_array.rb +0 -100
- data/test/test_big_array.rb +0 -24
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2d8d287bb71584ad802cd18d4414c4961188dbb
|
4
|
+
data.tar.gz: 2fb3326dbdd89711ae5f3e3dea251c117d7b4016
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 088059597e1261b79a0f94fef327042b48c83819c2348f809aab35a62303b2a9f8b7629f25c30759425c5e471e7327259c6f292cdb58590ae7555c3b80d2a2a1
|
7
|
+
data.tar.gz: 304a17f577bb54a2292995b7504f44956584bd55b8bab7608127ab4fa14571331dd6ea61cf3c588c7e8185c9f30d7b2721f3604a76464c87704a490b79c45054
|
data/README.rdoc
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
= Spreet
|
2
|
+
{<img src="https://badge.fury.io/rb/spreet.png" alt="Gem Version" />}[http://badge.fury.io/rb/spreet]
|
3
|
+
{<img src="https://secure.travis-ci.org/ekylibre/spreet.png"/>}[http://travis-ci.org/ekylibre/spreet]
|
2
4
|
|
3
5
|
Universal handler for spr[eadsh]eets.
|
4
6
|
|
7
|
+
Compatible with Ruby ≥ 1.9.2.
|
8
|
+
|
5
9
|
== Why ?
|
6
10
|
This gems is a handler for spreadsheets. With its independent API, it is possible to create, update files in some formats. Today the list is not very long:
|
7
11
|
|
@@ -24,7 +28,7 @@ This gems is a handler for spreadsheets. With its independent API, it is possibl
|
|
24
28
|
# ...or more classic style...
|
25
29
|
sheet[1,0] = "First name"
|
26
30
|
# ...or if necessary as a Hash
|
27
|
-
sheet[:
|
31
|
+
sheet[x: 2, y: 0] = "Born on"
|
28
32
|
|
29
33
|
sheet.next_row
|
30
34
|
for person in People.all
|
@@ -37,13 +41,13 @@ This gems is a handler for spreadsheets. With its independent API, it is possibl
|
|
37
41
|
sheet.write("people-2.csv", :format=>:xcsv) # CSV for Excel
|
38
42
|
# or write it as an Open Document Spreadsheet
|
39
43
|
sheet.write("people-3.ods")
|
44
|
+
|
45
|
+
# Read an existing document
|
46
|
+
doc = Spreet::Document.read("doc.ods")
|
40
47
|
|
41
|
-
== To do
|
48
|
+
== To do
|
42
49
|
|
43
50
|
* Add style management for cells
|
44
51
|
* Add Header/Footer
|
45
52
|
* HTML Writer
|
46
|
-
* PDF Writer like
|
47
|
-
|
48
|
-
== Travis
|
49
|
-
{<img src="https://secure.travis-ci.org/burisu/spreet.png"/>}[http://travis-ci.org/burisu/spreet]
|
53
|
+
* PDF Writer like LibreOffice would make it
|
data/lib/spreet.rb
CHANGED
@@ -1,341 +1,17 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require 'pathname'
|
3
|
-
require 'duration'
|
4
|
-
require 'money'
|
5
|
-
require 'time'
|
6
|
-
require 'big_array'
|
7
|
-
require 'spreet/coordinates'
|
8
|
-
|
9
|
-
# Create class for arrays
|
10
|
-
BigArray.new("Cells", 10, 3)
|
11
2
|
|
12
3
|
module Spreet
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
PATCH = TINY.freeze
|
21
|
-
PRE = version[3].freeze
|
22
|
-
STRING = version.freeze
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
# Represents a cell in a sheet
|
27
|
-
class Cell
|
28
|
-
attr_reader :text, :value, :type, :sheet, :coordinates
|
29
|
-
attr_accessor :annotation
|
30
|
-
|
31
|
-
def initialize(sheet, *args)
|
32
|
-
@sheet = sheet
|
33
|
-
@coordinates = Coordinates.new(*args)
|
34
|
-
self.value = nil
|
35
|
-
@empty = true
|
36
|
-
@covered = false # determine_covered
|
37
|
-
@annotation = nil
|
38
|
-
end
|
39
|
-
|
40
|
-
def value=(val)
|
41
|
-
if val.is_a?(Cell)
|
42
|
-
@value = val.value
|
43
|
-
@type = val.type
|
44
|
-
self.text = val.text
|
45
|
-
@empty = val.empty?
|
46
|
-
@annotation = val.annotation
|
47
|
-
else
|
48
|
-
@value = val
|
49
|
-
@type = determine_type
|
50
|
-
self.text = val
|
51
|
-
@empty = false
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def empty?
|
56
|
-
@empty
|
57
|
-
end
|
58
|
-
|
59
|
-
def covered?
|
60
|
-
@covered
|
61
|
-
end
|
62
|
-
|
63
|
-
def clear!
|
64
|
-
self.value = nil
|
65
|
-
@empty = true
|
66
|
-
end
|
67
|
-
|
68
|
-
def remove!
|
69
|
-
@sheet.remove(self.coordinates)
|
70
|
-
end
|
71
|
-
|
72
|
-
def <=>(other_cell)
|
73
|
-
self.coordinates <=> other_cell.coordinates
|
74
|
-
end
|
75
|
-
|
76
|
-
def text=(val)
|
77
|
-
@text = val.to_s
|
78
|
-
end
|
79
|
-
|
80
|
-
def inspect
|
81
|
-
"<#{self.coordinates}: #{self.text.inspect}#{'('+self.value.inspect+')' if self.text != self.value}>"
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
|
87
|
-
def determine_type
|
88
|
-
if value.is_a? Date or value.is_a? DateTime
|
89
|
-
:date
|
90
|
-
elsif value.is_a? Numeric # or percentage
|
91
|
-
:float
|
92
|
-
elsif value.is_a? Money
|
93
|
-
:currency
|
94
|
-
elsif value.is_a? Duration
|
95
|
-
:time
|
96
|
-
elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
97
|
-
:boolean
|
98
|
-
else # if value.is_a?(String)
|
99
|
-
:string
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
class Sheet
|
106
|
-
attr_reader :document, :name, :columns
|
107
|
-
attr_accessor :current_row
|
108
|
-
|
109
|
-
def initialize(document, name=nil)
|
110
|
-
@document = document
|
111
|
-
self.name = name
|
112
|
-
raise ArgumentError.new("Must be a Document") unless document.is_a? Document
|
113
|
-
@current_row = 0
|
114
|
-
@cells = {} # BigArray::Cells.new
|
115
|
-
@bound = compute_bound
|
116
|
-
end
|
117
|
-
|
118
|
-
def name=(value)
|
119
|
-
unless value
|
120
|
-
value = (@document.sheets.count > 0 ? @document.sheets[-1].name.succ : "Sheet 1")
|
121
|
-
end
|
122
|
-
raise ArgumentError.new("Name of sheet must be given") if value.to_s.strip.size.zero?
|
123
|
-
if @document.sheets[value]
|
124
|
-
raise ArgumentError.new("Name of sheet must be unique")
|
125
|
-
end
|
126
|
-
@name = value
|
127
|
-
end
|
128
|
-
|
129
|
-
def next_row(increment = 1)
|
130
|
-
@current_row += increment
|
131
|
-
end
|
132
|
-
|
133
|
-
def previous_row(increment = 1)
|
134
|
-
@current_row -= increment
|
135
|
-
end
|
136
|
-
|
137
|
-
def [](*args)
|
138
|
-
coord = Coordinates.new(*args)
|
139
|
-
@cells[coord.to_i] ||= Cell.new(self, coord)
|
140
|
-
return @cells[coord.to_i]
|
141
|
-
end
|
142
|
-
|
143
|
-
def []=(*args)
|
144
|
-
value = args.delete_at(-1)
|
145
|
-
cell = self[*args]
|
146
|
-
cell.value = value
|
147
|
-
@updated = true
|
148
|
-
end
|
149
|
-
|
150
|
-
def row(*args)
|
151
|
-
options = {}
|
152
|
-
options = args.delete_at(-1) if args[-1].is_a? Hash
|
153
|
-
row = options[:row] || @current_row
|
154
|
-
args.each_index do |index|
|
155
|
-
self[index, row] = args[index]
|
156
|
-
end
|
157
|
-
next_row
|
158
|
-
end
|
159
|
-
|
160
|
-
def rows(index)
|
161
|
-
row = []
|
162
|
-
for i in 0..bound.x
|
163
|
-
row[i] = self[i, index]
|
164
|
-
end
|
165
|
-
return row
|
166
|
-
end
|
167
|
-
|
168
|
-
def each_row(&block)
|
169
|
-
for j in 0..bound.y
|
170
|
-
yield rows(j)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# Find or build cell
|
175
|
-
def cell(*args)
|
176
|
-
return c
|
177
|
-
end
|
178
|
-
|
179
|
-
def bound
|
180
|
-
if @updated
|
181
|
-
compute_bound
|
182
|
-
else
|
183
|
-
@bound
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def remove!(coordinates)
|
188
|
-
raise ArgumentError.new("Must be a Coordinates") unless document.is_a?(Coordinates)
|
189
|
-
@cells.delete(coordinates.to_i)
|
190
|
-
@updated = true
|
191
|
-
end
|
192
|
-
|
193
|
-
# Moves the sheet to an other position in the list of sheets
|
194
|
-
def move_to(position)
|
195
|
-
@document.sheets.move_at(self, position)
|
196
|
-
end
|
197
|
-
|
198
|
-
# Moves the sheet higher in the list of sheets
|
199
|
-
def move_higher(increment=1)
|
200
|
-
@document.sheets.move(self, increment)
|
201
|
-
end
|
202
|
-
|
203
|
-
# Moves the sheet lower in the list of sheets
|
204
|
-
def move_lower(increment=1)
|
205
|
-
@document.sheets.move(self, -increment)
|
206
|
-
end
|
207
|
-
|
208
|
-
private
|
209
|
-
|
210
|
-
def compute_bound
|
211
|
-
bound = Coordinates.new(0,0)
|
212
|
-
for index, cell in @cells
|
213
|
-
# for cell in @cells.compact
|
214
|
-
unless cell.empty?
|
215
|
-
bound.x = cell.coordinates.x if cell.coordinates.x > bound.x
|
216
|
-
bound.y = cell.coordinates.y if cell.coordinates.y > bound.y
|
217
|
-
end
|
218
|
-
end
|
219
|
-
@updated = false
|
220
|
-
@bound = bound
|
221
|
-
return @bound
|
222
|
-
end
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
class Sheets
|
228
|
-
|
229
|
-
def initialize(document)
|
230
|
-
raise ArgumentError.new("Must be a Document") unless document.is_a?(Document)
|
231
|
-
@document = document
|
232
|
-
@array = []
|
233
|
-
end
|
234
|
-
|
235
|
-
def count
|
236
|
-
@array.size
|
237
|
-
end
|
238
|
-
|
239
|
-
def index(name_or_sheet)
|
240
|
-
if name_or_sheet.is_a? String
|
241
|
-
@array.each_index do |i|
|
242
|
-
return i if @array[i].name == name_or_sheet
|
243
|
-
end
|
244
|
-
elsif name_or_sheet.is_a? Integer
|
245
|
-
return (@array[name_or_sheet].nil? ? nil : name_or_sheet)
|
246
|
-
else
|
247
|
-
return @array.index(name_or_sheet)
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def add(name=nil, position=-1)
|
252
|
-
sheet = Sheet.new(@document, name)
|
253
|
-
@array.insert(position, sheet)
|
254
|
-
return sheet
|
255
|
-
end
|
256
|
-
|
257
|
-
def [](sheet)
|
258
|
-
sheet = index(sheet)
|
259
|
-
return (sheet.is_a?(Integer) ? @array[sheet] : nil)
|
260
|
-
end
|
261
|
-
|
262
|
-
def remove(sheet)
|
263
|
-
@array.delete_at(index(sheet))
|
264
|
-
end
|
265
|
-
|
266
|
-
def move(sheet, shift=0)
|
267
|
-
position = index(sheet) + shift
|
268
|
-
position = 0 if position < 0
|
269
|
-
position = self.count-1 if position >= self.count
|
270
|
-
move_at(sheet, position)
|
271
|
-
end
|
272
|
-
|
273
|
-
def move_at(sheet, position=-1)
|
274
|
-
if i = index(sheet)
|
275
|
-
@array.insert(position, @array.delete_at(i))
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def each(&block)
|
280
|
-
for item in @array
|
281
|
-
yield item
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
end
|
286
|
-
|
287
|
-
|
288
|
-
class Document
|
289
|
-
attr_reader :sheets
|
290
|
-
@@handlers = {}
|
291
|
-
@@associations = {}
|
292
|
-
|
293
|
-
def initialize(option={})
|
294
|
-
@sheets = Sheets.new(self)
|
295
|
-
end
|
296
|
-
|
297
|
-
def write(file, options={})
|
298
|
-
handler = self.class.extract_handler(file, options.delete(:format))
|
299
|
-
handler.write(self, file, options)
|
300
|
-
end
|
301
|
-
|
302
|
-
class << self
|
303
|
-
|
304
|
-
def register_handler(klass, name, options={})
|
305
|
-
if klass.respond_to?(:read) or klass.respond_to?(:write)
|
306
|
-
if name.is_a?(Symbol)
|
307
|
-
@@handlers[name] = klass # options.merge(:class=>klass)
|
308
|
-
elsif
|
309
|
-
raise ArgumentError.new("Name is invalid. Symbol expected, #{name.class.name} got.")
|
310
|
-
end
|
311
|
-
else
|
312
|
-
raise ArgumentError.new("Handler do not support :read or :write method.")
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
def read(file, options={})
|
317
|
-
handler = extract_handler(file, options.delete(:format))
|
318
|
-
return handler.read(file, options)
|
319
|
-
end
|
320
|
-
|
321
|
-
def extract_handler(file, handler_name=nil)
|
322
|
-
file_path = Pathname.new(file)
|
323
|
-
extension = file_path.extname.to_s[1..-1]
|
324
|
-
if !handler_name and extension.size > 0
|
325
|
-
handler_name = extension.to_sym
|
326
|
-
end
|
327
|
-
if @@handlers[handler_name]
|
328
|
-
return @@handlers[handler_name]
|
329
|
-
else
|
330
|
-
raise ArgumentError.new("No corresponding handler (#{handler_name.inspect}). Available: #{@@handlers.keys.collect{|k| k.inspect}.join(', ')}.")
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
end
|
335
|
-
|
336
|
-
|
337
|
-
end
|
338
|
-
|
4
|
+
autoload :VERSION, 'spreet/version'
|
5
|
+
autoload :Coordinates, 'spreet/coordinates'
|
6
|
+
autoload :Cell, 'spreet/cell'
|
7
|
+
autoload :Sheet, 'spreet/sheet'
|
8
|
+
autoload :Sheets, 'spreet/sheets'
|
9
|
+
autoload :Document, 'spreet/document'
|
10
|
+
autoload :Handlers, 'spreet/handlers'
|
339
11
|
end
|
340
12
|
|
341
|
-
|
13
|
+
Spreet::Document.register_handler Spreet::Handlers::CSV, :csv
|
14
|
+
Spreet::Document.register_handler Spreet::Handlers::ExcelCSV, :xcsv
|
15
|
+
# Spreet::Document.register_handler Spreet::Handlers::HTML, :html
|
16
|
+
Spreet::Document.register_handler Spreet::Handlers::OpenDocument, :ods
|
17
|
+
# Spreet::Document.register_handler Spreet::Handlers::PDF, :pdf
|
data/lib/spreet/cell.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'money'
|
2
|
+
require 'time'
|
3
|
+
require 'duration'
|
4
|
+
|
5
|
+
module Spreet
|
6
|
+
|
7
|
+
# Represents a cell in a sheet
|
8
|
+
class Cell
|
9
|
+
attr_reader :text, :value, :type, :sheet, :coordinates
|
10
|
+
attr_accessor :annotation
|
11
|
+
|
12
|
+
def initialize(sheet, *args)
|
13
|
+
@sheet = sheet
|
14
|
+
@coordinates = Coordinates.new(*args)
|
15
|
+
self.value = nil
|
16
|
+
@empty = true
|
17
|
+
@covered = false # determine_covered
|
18
|
+
@annotation = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def value=(val)
|
22
|
+
if val.is_a?(Cell)
|
23
|
+
@value = val.value
|
24
|
+
@type = val.type
|
25
|
+
self.text = val.text
|
26
|
+
@empty = val.empty?
|
27
|
+
@annotation = val.annotation
|
28
|
+
else
|
29
|
+
@value = val
|
30
|
+
@type = determine_type
|
31
|
+
self.text = val
|
32
|
+
@empty = false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
@empty
|
38
|
+
end
|
39
|
+
|
40
|
+
def covered?
|
41
|
+
@covered
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear!
|
45
|
+
self.value = nil
|
46
|
+
@empty = true
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove!
|
50
|
+
@sheet.remove(self.coordinates)
|
51
|
+
end
|
52
|
+
|
53
|
+
def <=>(other_cell)
|
54
|
+
self.coordinates <=> other_cell.coordinates
|
55
|
+
end
|
56
|
+
|
57
|
+
def text=(val)
|
58
|
+
@text = val.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"<#{self.coordinates}: #{self.text.inspect}#{'('+self.value.inspect+')' if self.text != self.value}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
|
68
|
+
def determine_type
|
69
|
+
if value.is_a? Date or value.is_a? DateTime
|
70
|
+
:date
|
71
|
+
elsif value.is_a? Numeric # or percentage
|
72
|
+
:float
|
73
|
+
elsif value.is_a? Money
|
74
|
+
:currency
|
75
|
+
elsif value.is_a? Duration
|
76
|
+
:time
|
77
|
+
elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
78
|
+
:boolean
|
79
|
+
else # if value.is_a?(String)
|
80
|
+
:string
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|