roo 2.6.0 → 2.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8604a4f1951ab6dfcd733de054d7bfb5a82a5fe0
4
- data.tar.gz: 9d9a0f430db3eaf985772a6a934374d2a04ab822
3
+ metadata.gz: 416c34282c125b08fbf46e19c398f6a1c07d269a
4
+ data.tar.gz: 56a97234a7ddef8c1cd8aa0aa308949bc9f00d52
5
5
  SHA512:
6
- metadata.gz: cd5bb56cbd0744febca931b1032b14910f512e5f4d5513ec81cbd6de011a75f557fd8855fe53311ab83e77f67f8564c1c8b2774ee16d89b44687906b1ca77332
7
- data.tar.gz: b3cef390c20fc5e5bf70d1896222352c04d8f4eac4af5e4a64e850fbb2abfbbf0e2ad5a92aa45568c229562fd5e6e8a82c6f28d8bb089722e7b5d0afd5bfda75
6
+ metadata.gz: 8d318576088c9fd269ffc3702c38aa00f9bca4bddf97f1125d0d289c9674b0bae4136dbf43b70f635bba8f597d14bd22894b739e5306ec0690aa244638a374dd
7
+ data.tar.gz: a578beb4286e83f6a308aac442bc39bc829badc7ff35d9b0b6914550ccf587106cfbcc9974c51c957d8c16cf08dacabdf7383be1170f91ed207834035a302d8a
@@ -0,0 +1,17 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ fixme:
9
+ enabled: true
10
+ rubocop:
11
+ enabled: true
12
+ ratings:
13
+ paths:
14
+ - "**.rb"
15
+ exclude_paths:
16
+ - spec/
17
+ - test/
@@ -4,8 +4,7 @@ rvm:
4
4
  - 2.3.1
5
5
  - 2.4.0
6
6
  - ruby-head
7
- - jruby-19mode # JRuby in 1.9 mode
8
- - rbx-2
7
+ - jruby-9.1.6.0
9
8
  matrix:
10
9
  include:
11
10
  - rvm: 2.0.0
@@ -14,6 +13,5 @@ matrix:
14
13
  gemfile: Gemfile_ruby2
15
14
  allow_failures:
16
15
  - rvm: ruby-head
17
- - rvm: jruby-19mode
18
- - rvm: rbx-2
16
+ - rvm: jruby-9.1.6.0
19
17
  bundler_args: --without local_development
@@ -1,5 +1,16 @@
1
1
  ## Unreleased
2
2
 
3
+ ## [2.7.0] 2016-12-31
4
+ ### Fixed
5
+ - Added rack server for testing Roo's download capabilities [365](https://github.com/roo-rb/roo/pull/365)
6
+ - Refactored tests into different formats [365](https://github.com/roo-rb/roo/pull/365)
7
+ - Fixed OpenOffice for JRuby [362](https://github.com/roo-rb/roo/pull/362)
8
+ - Added '0.000000' => '%.6f' number format [354](https://github.com/roo-rb/roo/pull/354)
9
+ - Add additional formula cell types for to_csv [367][https://github.com/roo-rb/roo/pull/367]
10
+
11
+ ### Added
12
+ - Extracted formatters from Roo::Base#to_* methods [364](https://github.com/roo-rb/roo/pull/364)
13
+
3
14
  ## [2.6.0] 2016-12-28
4
15
  ### Fixed
5
16
  - Fixed error if sheet name starts with a slash [348](https://github.com/roo-rb/roo/pull/348)
data/Gemfile CHANGED
@@ -4,13 +4,12 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  # additional testing libs
7
- gem 'webmock'
8
7
  gem 'shoulda'
9
8
  gem 'activesupport', '< 5.1'
10
9
  gem 'rspec', '>= 3.0.0'
11
- gem 'vcr'
12
10
  gem 'simplecov', '>= 0.9.0', require: false
13
11
  gem 'coveralls', require: false
12
+ gem "minitest-reporters"
14
13
  end
15
14
 
16
15
  group :local_development do
@@ -18,8 +17,7 @@ group :local_development do
18
17
  gem 'guard-rspec', '>= 4.3.1', require: false
19
18
  gem 'guard-minitest', require: false
20
19
  gem 'guard-bundler', require: false
21
- gem 'guard-preek', require: false
22
20
  gem 'guard-rubocop', require: false
23
- gem 'guard-reek', github: 'pericles/guard-reek', require: false
21
+ gem "rb-readline"
24
22
  gem 'pry'
25
23
  end
@@ -6,14 +6,14 @@ gem 'nokogiri', "< 1.7.0"
6
6
 
7
7
  group :test do
8
8
  # additional testing libs
9
- gem 'webmock'
10
9
  gem 'shoulda'
11
10
  gem 'rspec', '>= 3.0.0'
12
- gem 'vcr'
13
11
  gem 'simplecov', '>= 0.9.0', require: false
14
12
  gem 'coveralls', require: false
15
- # gem "pry"
16
13
  gem "activesupport", "~> 4.2.0"
14
+ gem "tins", '~> 1.6.0'
15
+ gem "term-ansicolor", "~> 1.3.2"
16
+ gem "minitest-reporters"
17
17
  end
18
18
 
19
19
  group :local_development do
@@ -25,5 +25,6 @@ group :local_development do
25
25
  gem 'guard-preek', require: false
26
26
  gem 'guard-rubocop', require: false
27
27
  gem 'guard-reek', github: 'pericles/guard-reek', require: false
28
+ gem 'rb-readline'
28
29
  gem 'pry'
29
30
  end
data/README.md CHANGED
@@ -262,6 +262,16 @@ Roo's public methods have stayed relatively consistent between 1.13.x and 2.0.0,
262
262
  5. Push to the branch (`git push origin my-new-feature`)
263
263
  6. Create a new Pull Request
264
264
 
265
+ ### Testing
266
+ Roo uses Minitest and RSpec. The best of both worlds! Run `bundle exec rake` to
267
+ run the tests/examples.
268
+
269
+ Roo also has a few tests that take a long time (5+ seconds). To run these, use
270
+ `LONG_RUN=true bundle exec rake`
271
+
272
+ When testing using Ruby 2.0 or 2.1, use this command:
273
+ `BUNDLE_GEMFILE=Gemfile_ruby2 bundle exec rake`
274
+
265
275
  ### Issues
266
276
 
267
277
  If you find an issue, please create a gist and refer to it in an issue ([sample gist](https://gist.github.com/stevendaniels/98a05849036e99bb8b3c)). Here are some instructions for creating such a gist.
@@ -4,10 +4,20 @@ require 'tmpdir'
4
4
  require 'stringio'
5
5
  require 'nokogiri'
6
6
  require 'roo/utils'
7
+ require "roo/formatters/base"
8
+ require "roo/formatters/csv"
9
+ require "roo/formatters/matrix"
10
+ require "roo/formatters/xml"
11
+ require "roo/formatters/yaml"
7
12
 
8
13
  # Base class for all other types of spreadsheets
9
14
  class Roo::Base
10
15
  include Enumerable
16
+ include Roo::Formatters::Base
17
+ include Roo::Formatters::CSV
18
+ include Roo::Formatters::Matrix
19
+ include Roo::Formatters::XML
20
+ include Roo::Formatters::YAML
11
21
 
12
22
  MAX_ROW_COL = 999_999.freeze
13
23
  MIN_ROW_COL = 0.freeze
@@ -22,6 +32,10 @@ class Roo::Base
22
32
  Roo::TEMP_PREFIX
23
33
  end
24
34
 
35
+ def self.finalize(object_id)
36
+ proc { finalize_tempdirs(object_id) }
37
+ end
38
+
25
39
  def initialize(filename, options = {}, _file_warning = :error, _tmpdir = nil)
26
40
  @filename = filename
27
41
  @options = options
@@ -105,73 +119,6 @@ class Roo::Base
105
119
  EOS
106
120
  end
107
121
 
108
- # returns a rectangular area (default: all cells) as yaml-output
109
- # you can add additional attributes with the prefix parameter like:
110
- # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
111
- def to_yaml(prefix = {}, from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
112
- return '' unless first_row # empty result if there is no first_row in a sheet
113
-
114
- from_row ||= first_row(sheet)
115
- to_row ||= last_row(sheet)
116
- from_column ||= first_column(sheet)
117
- to_column ||= last_column(sheet)
118
-
119
- result = "--- \n"
120
- from_row.upto(to_row) do |row|
121
- from_column.upto(to_column) do |col|
122
- next if empty?(row, col, sheet)
123
-
124
- result << "cell_#{row}_#{col}: \n"
125
- prefix.each do|k, v|
126
- result << " #{k}: #{v} \n"
127
- end
128
- result << " row: #{row} \n"
129
- result << " col: #{col} \n"
130
- result << " celltype: #{celltype(row, col, sheet)} \n"
131
- value = cell(row, col, sheet)
132
- if celltype(row, col, sheet) == :time
133
- value = integer_to_timestring(value)
134
- end
135
- result << " value: #{value} \n"
136
- end
137
- end
138
-
139
- result
140
- end
141
-
142
- # write the current spreadsheet to stdout or into a file
143
- def to_csv(filename = nil, separator = ',', sheet = default_sheet)
144
- if filename
145
- File.open(filename, 'w') do |file|
146
- write_csv_content(file, sheet, separator)
147
- end
148
- true
149
- else
150
- sio = ::StringIO.new
151
- write_csv_content(sio, sheet, separator)
152
- sio.rewind
153
- sio.read
154
- end
155
- end
156
-
157
- # returns a matrix object from the whole sheet or a rectangular area of a sheet
158
- def to_matrix(from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
159
- require 'matrix'
160
-
161
- return Matrix.empty unless first_row
162
-
163
- from_row ||= first_row(sheet)
164
- to_row ||= last_row(sheet)
165
- from_column ||= first_column(sheet)
166
- to_column ||= last_column(sheet)
167
-
168
- Matrix.rows(from_row.upto(to_row).map do |row|
169
- from_column.upto(to_column).map do |col|
170
- cell(row, col, sheet)
171
- end
172
- end)
173
- end
174
-
175
122
  def inspect
176
123
  "<##{self.class}:#{object_id.to_s(8)} #{instance_variables.join(' ')}>"
177
124
  end
@@ -274,32 +221,6 @@ class Roo::Base
274
221
  end
275
222
  end
276
223
 
277
- # returns an XML representation of all sheets of a spreadsheet file
278
- def to_xml
279
- Nokogiri::XML::Builder.new do |xml|
280
- xml.spreadsheet do
281
- sheets.each do |sheet|
282
- self.default_sheet = sheet
283
- xml.sheet(name: sheet) do |x|
284
- if first_row && last_row && first_column && last_column
285
- # sonst gibt es Fehler bei leeren Blaettern
286
- first_row.upto(last_row) do |row|
287
- first_column.upto(last_column) do |col|
288
- next if empty?(row, col)
289
-
290
- x.cell(cell(row, col),
291
- row: row,
292
- column: col,
293
- type: celltype(row, col))
294
- end
295
- end
296
- end
297
- end
298
- end
299
- end
300
- end.to_xml
301
- end
302
-
303
224
  # when a method like spreadsheet.a42 is called
304
225
  # convert it to a call of spreadsheet.cell('a',42)
305
226
  def method_missing(m, *args)
@@ -675,76 +596,4 @@ class Roo::Base
675
596
  ret
676
597
  end
677
598
  end
678
-
679
- # Write all cells to the csv file. File can be a filename or nil. If the this
680
- # parameter is nil the output goes to STDOUT
681
- def write_csv_content(file = nil, sheet = nil, separator = ',')
682
- file ||= STDOUT
683
- return unless first_row(sheet) # The sheet is empty
684
-
685
- 1.upto(last_row(sheet)) do |row|
686
- 1.upto(last_column(sheet)) do |col|
687
- file.print(separator) if col > 1
688
- file.print cell_to_csv(row, col, sheet)
689
- end
690
- file.print("\n")
691
- end
692
- end
693
-
694
- # The content of a cell in the csv output
695
- def cell_to_csv(row, col, sheet)
696
- return '' if empty?(row, col, sheet)
697
-
698
- onecell = cell(row, col, sheet)
699
-
700
- case celltype(row, col, sheet)
701
- when :string
702
- %("#{onecell.gsub('"', '""')}") unless onecell.empty?
703
- when :boolean
704
- # TODO: this only works for excelx
705
- onecell = self.sheet_for(sheet).cells[[row, col]].formatted_value
706
- %("#{onecell.gsub('"', '""').downcase}")
707
- when :float, :percentage
708
- if onecell == onecell.to_i
709
- onecell.to_i.to_s
710
- else
711
- onecell.to_s
712
- end
713
- when :formula
714
- case onecell
715
- when String
716
- %("#{onecell.gsub('"', '""')}") unless onecell.empty?
717
- when Integer
718
- onecell.to_s
719
- when Float
720
- if onecell == onecell.to_i
721
- onecell.to_i.to_s
722
- else
723
- onecell.to_s
724
- end
725
- when DateTime
726
- onecell.to_s
727
- else
728
- fail "unhandled onecell-class #{onecell.class}"
729
- end
730
- when :date, :datetime
731
- onecell.to_s
732
- when :time
733
- integer_to_timestring(onecell)
734
- when :link
735
- %("#{onecell.url.gsub('"', '""')}")
736
- else
737
- fail "unhandled celltype #{celltype(row, col, sheet)}"
738
- end || ''
739
- end
740
-
741
- # converts an integer value to a time string like '02:05:06'
742
- def integer_to_timestring(content)
743
- h = (content / 3600.0).floor
744
- content -= h * 3600
745
- m = (content / 60.0).floor
746
- content -= m * 60
747
- s = content
748
- sprintf('%02d:%02d:%02d', h, m, s)
749
- end
750
599
  end
@@ -1,5 +1,5 @@
1
- require 'csv'
2
- require 'time'
1
+ require "csv"
2
+ require "time"
3
3
 
4
4
  # The CSV class can read csv files (must be separated with commas) which then
5
5
  # can be handled like spreadsheets. This means you can access cells like A5
@@ -9,124 +9,119 @@ require 'time'
9
9
  #
10
10
  # You can pass options to the underlying CSV parse operation, via the
11
11
  # :csv_options option.
12
- #
13
-
14
- class Roo::CSV < Roo::Base
12
+ module Roo
13
+ class CSV < Roo::Base
14
+ attr_reader :filename
15
+
16
+ # Returns an array with the names of the sheets. In CSV class there is only
17
+ # one dummy sheet, because a csv file cannot have more than one sheet.
18
+ def sheets
19
+ ["default"]
20
+ end
15
21
 
16
- attr_reader :filename
22
+ def cell(row, col, sheet = nil)
23
+ sheet ||= default_sheet
24
+ read_cells(sheet)
25
+ @cell[normalize(row, col)]
26
+ end
17
27
 
18
- # Returns an array with the names of the sheets. In CSV class there is only
19
- # one dummy sheet, because a csv file cannot have more than one sheet.
20
- def sheets
21
- ['default']
22
- end
28
+ def celltype(row, col, sheet = nil)
29
+ sheet ||= default_sheet
30
+ read_cells(sheet)
31
+ @cell_type[normalize(row, col)]
32
+ end
23
33
 
24
- def cell(row, col, sheet=nil)
25
- sheet ||= default_sheet
26
- read_cells(sheet)
27
- @cell[normalize(row,col)]
28
- end
34
+ def cell_postprocessing(_row, _col, value)
35
+ value
36
+ end
29
37
 
30
- def celltype(row, col, sheet=nil)
31
- sheet ||= default_sheet
32
- read_cells(sheet)
33
- @cell_type[normalize(row,col)]
34
- end
38
+ def csv_options
39
+ @options[:csv_options] || {}
40
+ end
35
41
 
36
- def cell_postprocessing(row,col,value)
37
- value
38
- end
42
+ def set_value(row, col, value, _sheet)
43
+ @cell[[row, col]] = value
44
+ end
39
45
 
40
- def csv_options
41
- @options[:csv_options] || {}
42
- end
46
+ def set_type(row, col, type, _sheet)
47
+ @cell_type[[row, col]] = type
48
+ end
43
49
 
44
- def set_value(row, col, value, _sheet)
45
- @cell[[row, col]] = value
46
- end
50
+ private
47
51
 
48
- def set_type(row, col, type, _sheet)
49
- @cell_type[[row, col]] = type
50
- end
52
+ TYPE_MAP = {
53
+ String => :string,
54
+ Float => :float,
55
+ Date => :date,
56
+ DateTime => :datetime,
57
+ }
51
58
 
52
- private
59
+ def celltype_class(value)
60
+ TYPE_MAP[value.class]
61
+ end
53
62
 
54
- TYPE_MAP = {
55
- String => :string,
56
- Float => :float,
57
- Date => :date,
58
- DateTime => :datetime,
59
- }
63
+ def read_cells(sheet = default_sheet)
64
+ sheet ||= default_sheet
65
+ return if @cells_read[sheet]
66
+ set_row_count(sheet)
67
+ set_column_count(sheet)
68
+ row_num = 1
69
+
70
+ each_row csv_options do |row|
71
+ row.each_with_index do |elem, col_num|
72
+ coordinate = [row_num, col_num + 1]
73
+ @cell[coordinate] = elem
74
+ @cell_type[coordinate] = celltype_class(elem)
75
+ end
76
+ row_num += 1
77
+ end
60
78
 
61
- def celltype_class(value)
62
- TYPE_MAP[value.class]
63
- end
79
+ @cells_read[sheet] = true
80
+ end
64
81
 
65
- def each_row(options, &block)
66
- if uri?(filename)
67
- ::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV['ROO_TMP']) do |tmpdir|
68
- tmp_filename = download_uri(filename, tmpdir)
69
- CSV.foreach(tmp_filename, options, &block)
82
+ def each_row(options, &block)
83
+ if uri?(filename)
84
+ each_row_using_temp_dir(filename)
85
+ elsif is_stream?(filename_or_stream)
86
+ ::CSV.new(filename_or_stream, options).each(&block)
87
+ else
88
+ ::CSV.foreach(filename, options, &block)
70
89
  end
71
- elsif is_stream?(filename_or_stream)
72
- CSV.new(filename_or_stream, options).each(&block)
73
- else
74
- CSV.foreach(filename, options, &block)
75
90
  end
76
- end
77
91
 
78
- def read_cells(sheet = default_sheet)
79
- sheet ||= default_sheet
80
- return if @cells_read[sheet]
81
- @first_row[sheet] = 1
82
- @last_row[sheet] = 0
83
- @first_column[sheet] = 1
84
- @last_column[sheet] = 1
85
- rownum = 1
86
- each_row csv_options do |row|
87
- row.each_with_index do |elem,i|
88
- @cell[[rownum,i+1]] = cell_postprocessing rownum,i+1, elem
89
- @cell_type[[rownum,i+1]] = celltype_class @cell[[rownum,i+1]]
90
- if i+1 > @last_column[sheet]
91
- @last_column[sheet] += 1
92
- end
92
+ def each_row_using_tempdir
93
+ ::Dir.mktmpdir(Roo::TEMP_PREFIX, ENV["ROO_TMP"]) do |tmpdir|
94
+ tmp_filename = download_uri(filename, tmpdir)
95
+ ::CSV.foreach(tmp_filename, options, &block)
93
96
  end
94
- rownum += 1
95
- @last_row[sheet] += 1
96
- end
97
- @cells_read[sheet] = true
98
- #-- adjust @first_row if neccessary
99
- while !row(@first_row[sheet]).any? and @first_row[sheet] < @last_row[sheet]
100
- @first_row[sheet] += 1
101
- end
102
- #-- adjust @last_row if neccessary
103
- while !row(@last_row[sheet]).any? and @last_row[sheet] and
104
- @last_row[sheet] > @first_row[sheet]
105
- @last_row[sheet] -= 1
106
97
  end
107
- #-- adjust @first_column if neccessary
108
- while !column(@first_column[sheet]).any? and
109
- @first_column[sheet] and
110
- @first_column[sheet] < @last_column[sheet]
111
- @first_column[sheet] += 1
98
+
99
+ def set_row_count(sheet)
100
+ @first_row[sheet] = 1
101
+ @last_row[sheet] = ::CSV.readlines(@filename).size
102
+ @last_row[sheet] = @first_row[sheet] if @last_row[sheet].zero?
103
+
104
+ nil
112
105
  end
113
- #-- adjust @last_column if neccessary
114
- while !column(@last_column[sheet]).any? and
115
- @last_column[sheet] and
116
- @last_column[sheet] > @first_column[sheet]
117
- @last_column[sheet] -= 1
106
+
107
+ def set_column_count(sheet)
108
+ @first_column[sheet] = 1
109
+ @last_column[sheet] = (::CSV.readlines(@filename).first || []).size
110
+ @last_column[sheet] = @first_column[sheet] if @last_column[sheet].zero?
111
+
112
+ nil
118
113
  end
119
- end
120
114
 
121
- def clean_sheet(sheet)
122
- read_cells(sheet)
115
+ def clean_sheet(sheet)
116
+ read_cells(sheet)
117
+
118
+ @cell.each_pair do |coord, value|
119
+ @cell[coord] = sanitize_value(value) if value.is_a?(::String)
120
+ end
123
121
 
124
- @cell.each_pair do |coord, value|
125
- @cell[coord] = sanitize_value(value) if value.is_a?(::String)
122
+ @cleaned[sheet] = true
126
123
  end
127
124
 
128
- @cleaned[sheet] = true
125
+ alias_method :filename_or_stream, :filename
129
126
  end
130
-
131
- alias_method :filename_or_stream, :filename
132
127
  end