spreet 0.0.3 → 0.0.4
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.
- 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
|