to-csv 1.0.0 → 1.0.1

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.
data/lib/to_csv.rb CHANGED
@@ -1,59 +1,59 @@
1
- require 'fastercsv'
2
- require 'ostruct'
3
- require 'active_support'
4
- require 'to_csv/csv_converter'
5
-
6
- module ToCSV
7
- mattr_accessor :byte_order_marker, :locale, :primary_key, :timestamps
8
- mattr_accessor :csv_options
9
- self.csv_options = { :col_sep => ';' }
10
- end
11
-
12
- class Array
13
-
14
- #
15
- # Returns a CSV string.
16
- #
17
- # ==== Available Options:
18
- #
19
- # 1. *options*
20
- # +byte_order_marker+::
21
- # If true, a Byte Order Maker (BOM) will be inserted at
22
- # the beginning of the output. It's useful if you want to force
23
- # MS Excel to read UTF-8 encoded files, otherwise it will just
24
- # decode them as Latin1 (ISO-8859-1). Default: +false+.
25
- # +only+::
26
- # Same behavior as with the +to_json+ method.
27
- # +except+::
28
- # Same as +only+ option.
29
- # +methods+::
30
- # Accepts a symbol or an array with additional methods to be included.
31
- # +timestamps+::
32
- # Include timestamps +created_at+, +created_on+, +updated_at+ and
33
- # +updated_on+. If false Default: +false+.
34
- # +primary_key+::
35
- # If +true+ the object's primary key will be added as an attribute,
36
- # which in turn will be mapped to a CSV column. Default: +false+.
37
- # +headers+::
38
- # If this list is <tt>nil</tt> then headers will be in alphabetical order.
39
- # If it is an empty array or <tt>false</tt>, no headers will be shown.
40
- # If it is non empty, headers will be sorted in the order specified.
41
- # <tt>:all</tt> can be used as a placeholder for all attributes not
42
- # explicitly named.
43
- # +locale+::
44
- # In a Rails environment, it will automatically take the current locale
45
- # and will use it to translate the columns to friendly headers.
46
- # Methods will be translated from
47
- # <tt>[:activerecord, :attributes, <model>]</tt>. If the translation
48
- # is missing, then a simple humanize will be called.
49
- #
50
- # 2. *csv_options*
51
- # Accepts all options listed in <tt>FasterCSV::DEFAULT_OPTIONS</tt>.
52
- #
53
- def to_csv(options = {}, csv_options = {}, &block)
54
- return '' if empty?
55
- csv_converter = ToCSV::Converter.new(self, options, csv_options, &block)
56
- csv_converter.to_csv
57
- end
58
- end
59
-
1
+ require RUBY_VERSION < '1.9' ? 'fastercsv' : 'csv'
2
+ require 'ostruct'
3
+ require 'active_support'
4
+ require 'to_csv/csv_converter'
5
+
6
+ module ToCSV
7
+ mattr_accessor :byte_order_marker, :locale, :primary_key, :timestamps
8
+ mattr_accessor :csv_options
9
+ self.csv_options = { :col_sep => ';' }
10
+ end
11
+
12
+ class Array
13
+
14
+ #
15
+ # Returns a CSV string.
16
+ #
17
+ # ==== Available Options:
18
+ #
19
+ # 1. *options*
20
+ # +byte_order_marker+::
21
+ # If true, a Byte Order Maker (BOM) will be inserted at
22
+ # the beginning of the output. It's useful if you want to force
23
+ # MS Excel to read UTF-8 encoded files, otherwise it will just
24
+ # decode them as Latin1 (ISO-8859-1). Default: +false+.
25
+ # +only+::
26
+ # Same behavior as with the +to_json+ method.
27
+ # +except+::
28
+ # Same as +only+ option.
29
+ # +methods+::
30
+ # Accepts a symbol or an array with additional methods to be included.
31
+ # +timestamps+::
32
+ # Include timestamps +created_at+, +created_on+, +updated_at+ and
33
+ # +updated_on+. If false Default: +false+.
34
+ # +primary_key+::
35
+ # If +true+ the object's primary key will be added as an attribute,
36
+ # which in turn will be mapped to a CSV column. Default: +false+.
37
+ # +headers+::
38
+ # If this list is <tt>nil</tt> then headers will be in alphabetical order.
39
+ # If it is an empty array or <tt>false</tt>, no headers will be shown.
40
+ # If it is non empty, headers will be sorted in the order specified.
41
+ # <tt>:all</tt> can be used as a placeholder for all attributes not
42
+ # explicitly named.
43
+ # +locale+::
44
+ # In a Rails environment, it will automatically take the current locale
45
+ # and will use it to translate the columns to friendly headers.
46
+ # Methods will be translated from
47
+ # <tt>[:activerecord, :attributes, <model>]</tt>. If the translation
48
+ # is missing, then a simple humanize will be called.
49
+ #
50
+ # 2. *csv_options*
51
+ # Accepts all options listed in <tt>FasterCSV::DEFAULT_OPTIONS</tt>.
52
+ #
53
+ def to_csv(options = {}, csv_options = {}, &block)
54
+ return '' if empty?
55
+ csv_converter = ToCSV::Converter.new(self, options, csv_options, &block)
56
+ csv_converter.to_csv
57
+ end
58
+ end
59
+
@@ -1,169 +1,175 @@
1
- module ToCSV
2
- class Converter
3
-
4
- def initialize(data, options = {}, csv_options = {}, &block)
5
- @opts = options.to_options.reverse_merge({
6
- :byte_order_marker => ToCSV.byte_order_marker,
7
- :locale => ToCSV.locale || ::I18n.locale,
8
- :primary_key => ToCSV.primary_key,
9
- :timestamps => ToCSV.timestamps
10
- })
11
-
12
- @opts[:only] = Array(@opts[:only]).map(&:to_s)
13
- @opts[:except] = Array(@opts[:except]).map(&:to_s)
14
- @opts[:methods] = Array(@opts[:methods]).map(&:to_s)
15
-
16
- @data = data
17
- @block = block
18
- @csv_options = csv_options.to_options.reverse_merge(ToCSV.csv_options)
19
- end
20
-
21
- def to_csv
22
- build_headers_and_rows
23
-
24
- output = ::FasterCSV.generate(@csv_options) do |csv|
25
- csv << @header_row if @header_row.try(:any?)
26
- @rows.each { |row| csv << row }
27
- end
28
-
29
- @opts[:byte_order_marker] ? "\xEF\xBB\xBF#{output}" : output
30
- end
31
-
32
- private
33
-
34
- def build_headers_and_rows
35
- send "headers_and_rows_from_#{ discover_data_type }"
36
- end
37
-
38
- def discover_data_type
39
- test_data = @data.first
40
- return 'ar_object' if instance_of_active_record? test_data
41
- return 'hash' if test_data.is_a? Hash
42
- return 'unidimensional_array' if test_data.is_a?(Array) && !test_data.first.is_a?(Array)
43
- return 'bidimensional_array' if test_data.is_a?(Array) && test_data.first.is_a?(Array) && test_data.first.size == 2
44
- 'simple_data'
45
- end
46
-
47
- def instance_of_active_record?(obj)
48
- obj.class.base_class.superclass == ActiveRecord::Base
49
- rescue Exception
50
- false
51
- end
52
-
53
- def headers_and_rows_from_simple_data
54
- @header_row = nil
55
- @rows = [@data.dup]
56
- end
57
-
58
- def headers_and_rows_from_hash
59
- @header_row = @data.first.keys if display_headers?
60
- @rows = @data.map(&:values)
61
- end
62
-
63
- def headers_and_rows_from_unidimensional_array
64
- @header_row = @data.first if display_headers?
65
- @rows = @data[1..-1]
66
- end
67
-
68
- def headers_and_rows_from_bidimensional_array
69
- @header_row = @data.first.map(&:first) if display_headers?
70
- @rows = @data.map { |array| array.map(&:last) }
71
- end
72
-
73
- def headers_and_rows_from_ar_object
74
- attributes = sort_attributes(filter_attributes(attribute_names))
75
- @header_row = human_attribute_names(attributes) if display_headers?
76
-
77
- @rows = if @block
78
- @data.map do |item|
79
- os = OpenStruct.new
80
- @block.call(os, item)
81
- marshal_dump = os.marshal_dump
82
- attributes.map { |attribute| marshal_dump[attribute.to_sym] || try_formatting_date(item.send(attribute)) }
83
- end
84
- else
85
- @data.map do |item|
86
- attributes.map { |attribute| try_formatting_date item.send(attribute) }
87
- end
88
- end
89
- end
90
-
91
- def display_headers?
92
- @opts[:headers].nil? || (Array(@opts[:headers]).any? && Array(@opts[:headers]).all? { |h| h != false })
93
- end
94
-
95
- def human_attribute_names(attributes)
96
- @opts[:locale] ? translate(attributes) : humanize(attributes)
97
- end
98
-
99
- def humanize(attributes)
100
- attributes.map(&:humanize)
101
- end
102
-
103
- def translate(attributes)
104
- ::I18n.with_options :locale => @opts[:locale], :scope => [:activerecord, :attributes, @data.first.class.to_s.underscore] do |locale|
105
- attributes.map { |attribute| locale.t(attribute, :default => attribute.humanize) }
106
- end
107
- end
108
-
109
- def try_formatting_date(value)
110
- is_a_date?(value) ? value.to_s : value
111
- end
112
-
113
- def is_a_date?(value)
114
- value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
115
- end
116
-
117
- def primary_key_filter(attributes)
118
- return attributes if @opts[:primary_key]
119
- attributes - Array(@data.first.class.primary_key.to_s)
120
- end
121
-
122
- def timestamps_filter(attributes)
123
- return attributes if @opts[:timestamps]
124
- return attributes if (@opts[:only] + @opts[:except]).any? { |attribute| timestamps.include? attribute }
125
- attributes - timestamps
126
- end
127
-
128
- def timestamps
129
- %w[ created_at updated_at created_on updated_on ]
130
- end
131
-
132
- def methods_filter(attributes)
133
- attributes | @opts[:methods]
134
- end
135
-
136
- def only_filter(attributes)
137
- return attributes if @opts[:only].empty?
138
- attributes & @opts[:only]
139
- end
140
-
141
- def except_filter(attributes)
142
- attributes - @opts[:except]
143
- end
144
-
145
- def attribute_names
146
- @data.first.attribute_names.map(&:to_s)
147
- end
148
-
149
- def filter_attributes(attributes)
150
- attributes = methods_filter(attributes)
151
- attributes = primary_key_filter(attributes)
152
- attributes = timestamps_filter(attributes)
153
- attributes = @opts[:only].any?? only_filter(attributes) : except_filter(attributes)
154
- attributes
155
- end
156
-
157
- def sort_attributes(attributes)
158
- attributes = attributes.map(&:to_s).sort
159
- return attributes if @opts[:headers].nil?
160
- headers = Array(@opts[:headers]).map(&:to_s)
161
- headers.delete_if { |attribute| attribute == 'false' }
162
- if index = headers.index('all')
163
- (headers & attributes).insert(index, (attributes - headers)).flatten
164
- else
165
- headers + (attributes - headers)
166
- end
167
- end
168
- end
169
- end
1
+ #encoding: utf-8
2
+
3
+ module ToCSV
4
+
5
+ CSVClass = RUBY_VERSION < '1.9' ? ::FasterCSV : ::CSV
6
+
7
+ class Converter
8
+
9
+ def initialize(data, options = {}, csv_options = {}, &block)
10
+ @opts = options.to_options.reverse_merge({
11
+ :byte_order_marker => ToCSV.byte_order_marker,
12
+ :locale => ToCSV.locale || ::I18n.locale,
13
+ :primary_key => ToCSV.primary_key,
14
+ :timestamps => ToCSV.timestamps
15
+ })
16
+
17
+ @opts[:only] = Array(@opts[:only]).map(&:to_s)
18
+ @opts[:except] = Array(@opts[:except]).map(&:to_s)
19
+ @opts[:methods] = Array(@opts[:methods]).map(&:to_s)
20
+
21
+ @data = data
22
+ @block = block
23
+ @csv_options = csv_options.to_options.reverse_merge(ToCSV.csv_options)
24
+ end
25
+
26
+ def to_csv
27
+ build_headers_and_rows
28
+
29
+ output = CSVClass.generate(@csv_options) do |csv|
30
+ csv << @header_row if @header_row.try(:any?)
31
+ @rows.each { |row| csv << row }
32
+ end
33
+
34
+ @opts[:byte_order_marker] ? "\xEF\xBB\xBF#{output}" : output
35
+ end
36
+
37
+ private
38
+
39
+ def build_headers_and_rows
40
+ send "headers_and_rows_from_#{ discover_data_type }"
41
+ end
42
+
43
+ def discover_data_type
44
+ test_data = @data.first
45
+ return 'ar_object' if instance_of_active_record? test_data
46
+ return 'hash' if test_data.is_a? Hash
47
+ return 'unidimensional_array' if test_data.is_a?(Array) && !test_data.first.is_a?(Array)
48
+ return 'bidimensional_array' if test_data.is_a?(Array) && test_data.first.is_a?(Array) && test_data.first.size == 2
49
+ 'simple_data'
50
+ end
51
+
52
+ def instance_of_active_record?(obj)
53
+ obj.class.base_class.superclass == ActiveRecord::Base
54
+ rescue Exception
55
+ false
56
+ end
57
+
58
+ def headers_and_rows_from_simple_data
59
+ @header_row = nil
60
+ @rows = [@data.dup]
61
+ end
62
+
63
+ def headers_and_rows_from_hash
64
+ @header_row = @data.first.keys if display_headers?
65
+ @rows = @data.map(&:values)
66
+ end
67
+
68
+ def headers_and_rows_from_unidimensional_array
69
+ @header_row = @data.first if display_headers?
70
+ @rows = @data[1..-1]
71
+ end
72
+
73
+ def headers_and_rows_from_bidimensional_array
74
+ @header_row = @data.first.map(&:first) if display_headers?
75
+ @rows = @data.map { |array| array.map(&:last) }
76
+ end
77
+
78
+ def headers_and_rows_from_ar_object
79
+ attributes = sort_attributes(filter_attributes(attribute_names))
80
+ @header_row = human_attribute_names(attributes) if display_headers?
81
+
82
+ @rows = if @block
83
+ @data.map do |item|
84
+ os = OpenStruct.new
85
+ @block.call(os, item)
86
+ marshal_dump = os.marshal_dump
87
+ attributes.map { |attribute| marshal_dump[attribute.to_sym] || try_formatting_date(item.send(attribute)) }
88
+ end
89
+ else
90
+ @data.map do |item|
91
+ attributes.map { |attribute| try_formatting_date item.send(attribute) }
92
+ end
93
+ end
94
+ end
95
+
96
+ def display_headers?
97
+ @opts[:headers].nil? || (Array(@opts[:headers]).any? && Array(@opts[:headers]).all? { |h| h != false })
98
+ end
99
+
100
+ def human_attribute_names(attributes)
101
+ @opts[:locale] ? translate(attributes) : humanize(attributes)
102
+ end
103
+
104
+ def humanize(attributes)
105
+ attributes.map(&:humanize)
106
+ end
107
+
108
+ def translate(attributes)
109
+ ::I18n.with_options :locale => @opts[:locale], :scope => [:activerecord, :attributes, @data.first.class.to_s.underscore] do |locale|
110
+ attributes.map { |attribute| locale.t(attribute, :default => attribute.humanize) }
111
+ end
112
+ end
113
+
114
+ def try_formatting_date(value)
115
+ is_a_date?(value) ? value.to_s(:db) : value
116
+ end
117
+
118
+ def is_a_date?(value)
119
+ value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
120
+ end
121
+
122
+ def primary_key_filter(attributes)
123
+ return attributes if @opts[:primary_key]
124
+ attributes - Array(@data.first.class.primary_key.to_s)
125
+ end
126
+
127
+ def timestamps_filter(attributes)
128
+ return attributes if @opts[:timestamps]
129
+ return attributes if (@opts[:only] + @opts[:except]).any? { |attribute| timestamps.include? attribute }
130
+ attributes - timestamps
131
+ end
132
+
133
+ def timestamps
134
+ %w[ created_at updated_at created_on updated_on ]
135
+ end
136
+
137
+ def methods_filter(attributes)
138
+ attributes | @opts[:methods]
139
+ end
140
+
141
+ def only_filter(attributes)
142
+ return attributes if @opts[:only].empty?
143
+ attributes & @opts[:only]
144
+ end
145
+
146
+ def except_filter(attributes)
147
+ attributes - @opts[:except]
148
+ end
149
+
150
+ def attribute_names
151
+ @data.first.attribute_names.map(&:to_s)
152
+ end
153
+
154
+ def filter_attributes(attributes)
155
+ attributes = methods_filter(attributes)
156
+ attributes = primary_key_filter(attributes)
157
+ attributes = timestamps_filter(attributes)
158
+ attributes = @opts[:only].any?? only_filter(attributes) : except_filter(attributes)
159
+ attributes
160
+ end
161
+
162
+ def sort_attributes(attributes)
163
+ attributes = attributes.map(&:to_s).sort
164
+ return attributes if @opts[:headers].nil?
165
+ headers = Array(@opts[:headers]).map(&:to_s)
166
+ headers.delete_if { |attribute| attribute == 'false' }
167
+ if index = headers.index('all')
168
+ (headers & attributes).insert(index, (attributes - headers)).flatten
169
+ else
170
+ headers + (attributes - headers)
171
+ end
172
+ end
173
+ end
174
+ end
175
+