roo 2.6.0 → 2.7.0

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