csv_madness 0.0.9 → 0.0.10
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.
Potentially problematic release.
This version of csv_madness might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.document +5 -0
- data/CHANGELOG.markdown +4 -0
- data/Gemfile +2 -7
- data/Rakefile +12 -78
- data/VERSION +1 -1
- data/lib/csv_madness/builder.rb +91 -9
- data/lib/csv_madness/column.rb +30 -0
- data/lib/csv_madness/column_methods/core_methods.rb +6 -0
- data/lib/csv_madness/column_methods/default_value_methods.rb +7 -0
- data/lib/csv_madness/column_methods/error_methods.rb +19 -0
- data/lib/csv_madness/column_methods/fetch_methods.rb +11 -0
- data/lib/csv_madness/column_methods/typing_methods.rb +24 -0
- data/lib/csv_madness/column_set.rb +5 -0
- data/lib/csv_madness/column_type.rb +74 -0
- data/lib/csv_madness/column_types/date.rb +14 -0
- data/lib/csv_madness/column_types/float.rb +25 -0
- data/lib/csv_madness/column_types/integer.rb +17 -0
- data/lib/csv_madness/column_types/string.rb +9 -0
- data/lib/csv_madness/data_accessor_module.rb +79 -79
- data/lib/csv_madness/gem_api.rb +11 -2
- data/lib/csv_madness/index_set.rb +36 -0
- data/lib/csv_madness/indexer.rb +105 -0
- data/lib/csv_madness/record.rb +68 -42
- data/lib/csv_madness/record_filter.rb +68 -0
- data/lib/csv_madness/record_methods/method_methods.rb +57 -0
- data/lib/csv_madness/sheet.rb +160 -76
- data/lib/csv_madness/sheet_methods/class_methods.rb +11 -4
- data/lib/csv_madness/sheet_methods/column_methods.rb +95 -107
- data/lib/csv_madness/sheet_methods/file_methods.rb +9 -6
- data/lib/csv_madness/sheet_methods/indexing.rb +104 -23
- data/lib/csv_madness/sheet_methods/record_methods.rb +123 -47
- data/lib/csv_madness/sheet_methods/sorting_methods.rb +10 -0
- data/lib/csv_madness/utils/const_proc.rb +19 -0
- data/lib/csv_madness/utils/counter.rb +24 -0
- data/lib/csv_madness/utils/counter_proc.rb +31 -0
- data/lib/csv_madness/utils/looker_upper.rb +36 -0
- data/lib/csv_madness.rb +1 -0
- data/test/csv/nil_headers.csv +5 -0
- data/test/csv/out/.gitkeep +0 -0
- data/test/csv/pokemon.csv +9 -0
- data/test/csv/test_column_types.csv +2 -2
- data/test/helper.rb +9 -6
- data/test/test_builder.rb +36 -0
- data/test/test_csv_madness.rb +28 -18
- data/test/test_index_set.rb +30 -0
- data/test/test_indexer.rb +50 -0
- data/test/test_reloading_spreadsheet.rb +4 -2
- data/test/test_sheet.rb +164 -27
- data/test/test_utils.rb +99 -0
- metadata +66 -38
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
# Each record within the spreadsheet is extended by a module with accessor functions
|
|
2
|
-
# which allow the user to treat column names as methods. When columns are added, dropped, or re-ordered,
|
|
3
|
-
# the accessor module needs to be updated to reflect the change.
|
|
4
|
-
class DataAccessorModule < Module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
1
|
+
# # Each record within the spreadsheet is extended by a module with accessor functions
|
|
2
|
+
# # which allow the user to treat column names as methods. When columns are added, dropped, or re-ordered,
|
|
3
|
+
# # the accessor module needs to be updated to reflect the change.
|
|
4
|
+
# class DataAccessorModule < Module
|
|
5
|
+
# # mapping : keys = column symbol
|
|
6
|
+
# # values = column index
|
|
7
|
+
# attr_accessor :column_accessors_map
|
|
8
|
+
#
|
|
9
|
+
# def initialize( mapping )
|
|
10
|
+
# puts "Got mapping #{mapping.inspect}"
|
|
11
|
+
# @column_accessors_map = mapping
|
|
12
|
+
# remap_accessors
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# def install_column_accessors( *columns )
|
|
16
|
+
# for column in columns.flatten
|
|
17
|
+
# unless is_valid_ruby_method?( column )
|
|
18
|
+
# warn( "#{column.inspect} is not a valid ruby method" )
|
|
19
|
+
# require "data_accessor_module" # TODO: WTF?
|
|
20
|
+
# column = "csv_column_#{index}"
|
|
21
|
+
# warn( " ----> renaming to #{column.inspect}" )
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# if self.respond_to?( column )
|
|
25
|
+
# puts "Column already has accessors"
|
|
26
|
+
# else
|
|
27
|
+
# eval <<-EOF
|
|
28
|
+
# self.send( :define_method, :#{column} ) do
|
|
29
|
+
# self.csv_data[ self.column_accessors_map[ :#{column} ] ]
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# self.send( :define_method, :#{column}= ) do |val|
|
|
33
|
+
# self.csv_data[ self.column_accessors_map[ :#{column} ] ] = val
|
|
34
|
+
# end
|
|
35
|
+
# EOF
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# # But if instead of hardcoding the index, the index was pulled from the spreadsheet mapping...
|
|
41
|
+
# # a titch slower to look up? But a change to the mapping automatically updates the method
|
|
42
|
+
# def remove_column_accessors( *columns )
|
|
43
|
+
# for column in columns.flatten
|
|
44
|
+
# for method_sym in [column, :"#{column}="]
|
|
45
|
+
# self.send( :remove_method, method_sym ) if self.respond_to?( method_sym )
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# def remove_all_column_accessors
|
|
51
|
+
# # 2016-11-24 : was this necessary for anything
|
|
52
|
+
# # @column_accessors ||= []
|
|
53
|
+
# remove_column_accessors( @column_accessors_map.keys )
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# # Partly obsoleted.
|
|
57
|
+
# def remap_accessors( *args )
|
|
58
|
+
# #
|
|
59
|
+
#
|
|
60
|
+
#
|
|
61
|
+
# remove_all_column_accessors
|
|
62
|
+
#
|
|
63
|
+
# if args.length == 1
|
|
64
|
+
# debugger
|
|
65
|
+
# @column_accessors_map = args.first
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# install_column_accessors( @column_accessors_map.keys )
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# # Symbol.inspect doesn't output " quotes when given a sym
|
|
72
|
+
# # that expresses a valid method name or a valid identifier.
|
|
73
|
+
# def is_valid_ruby_method?( str_or_sym )
|
|
74
|
+
# sym = str_or_sym.to_sym
|
|
75
|
+
# test_string = sym.inspect
|
|
76
|
+
#
|
|
77
|
+
# test_string.match( /^:[@"]/ ).nil?
|
|
78
|
+
# end
|
|
79
|
+
# end
|
data/lib/csv_madness/gem_api.rb
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
module CsvMadness
|
|
2
2
|
module GemAPI
|
|
3
|
+
# Loads a sheet from a csv file. If given a relative path, will look in
|
|
4
|
+
# default folders first.
|
|
3
5
|
def load( csv, opts = {} )
|
|
4
6
|
CsvMadness::Sheet.from( csv, opts )
|
|
5
7
|
end
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
def add_search_path( path )
|
|
11
|
+
CsvMadness::Sheet.add_search_path( path )
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Define a builder using a block, then run
|
|
15
|
+
# the given objects through it to create a
|
|
16
|
+
# Sheet.
|
|
7
17
|
def build( objects, &block )
|
|
8
|
-
|
|
9
|
-
builder.build( objects )
|
|
18
|
+
CsvMadness::Builder.new(&block).build( objects )
|
|
10
19
|
end
|
|
11
20
|
end
|
|
12
21
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
# Used by indexer to store indexing data. With a regular set, two sets
|
|
3
|
+
# are considered equal if they hold the same objects, which is a problem
|
|
4
|
+
# for the @sets_containing_item data (needed for unindexing)
|
|
5
|
+
class IndexSet < Set
|
|
6
|
+
attr_reader :index_key, :index_value
|
|
7
|
+
|
|
8
|
+
def initialize( k, v )
|
|
9
|
+
super()
|
|
10
|
+
@index_key = k
|
|
11
|
+
@index_value = v
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Set
|
|
17
|
+
def inner_hash
|
|
18
|
+
@hash
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Set2 < Set
|
|
24
|
+
def eql?( rhs )
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
set = Set.new
|
|
30
|
+
set2 = Set2.new
|
|
31
|
+
set3 = Set2.new
|
|
32
|
+
|
|
33
|
+
set.add( set2 )
|
|
34
|
+
set.add( set3 )
|
|
35
|
+
set.length
|
|
36
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
class Indexer
|
|
3
|
+
# Just columns where every value is unique? Or fast results when several records have one value?
|
|
4
|
+
|
|
5
|
+
# :indexes => {
|
|
6
|
+
# :col1 => {
|
|
7
|
+
# :val1 => Set( Record1 ),
|
|
8
|
+
# :val2 => Set( Record2 )
|
|
9
|
+
# }
|
|
10
|
+
#
|
|
11
|
+
# :col2 => {
|
|
12
|
+
# :val1 => Set( Record0, Record2 ),
|
|
13
|
+
# :val2 => Set( Record1, Record3, Record7 )
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# But when a cell changes value, (say, R1.col2 switches from :val1 to :val2)
|
|
17
|
+
# :sets_containing_item => {
|
|
18
|
+
#
|
|
19
|
+
# }
|
|
20
|
+
# This could be a general-purpose module. So I'm dropping the restriction
|
|
21
|
+
# that the index has to be keyed to a symbol. Should be simple enough to
|
|
22
|
+
# manage on the caller's end, if that's needed
|
|
23
|
+
def initialize
|
|
24
|
+
# Maintaining this information is necessary to remove items
|
|
25
|
+
# from the indexes quickly.
|
|
26
|
+
@sets_containing_the_item = {} # key: a item, value: a set of set sets which live in @indexes
|
|
27
|
+
@indexes = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# If the item has previously been indexed in this index_name under another
|
|
32
|
+
# value, it must be removed
|
|
33
|
+
def index( item, index_key, index_val )
|
|
34
|
+
@sets_containing_the_item[item] ||= Set.new
|
|
35
|
+
|
|
36
|
+
@indexes[index_key] ||= {}
|
|
37
|
+
@indexes[index_key][index_val] ||= IndexSet.new( index_key, index_val )
|
|
38
|
+
|
|
39
|
+
unindex_item_from_one_index( item, index_key )
|
|
40
|
+
|
|
41
|
+
added_set = @indexes[index_key][index_val]
|
|
42
|
+
added_set.add( item )
|
|
43
|
+
|
|
44
|
+
@sets_containing_the_item[item].add( added_set )
|
|
45
|
+
|
|
46
|
+
item
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# remove the item from all
|
|
50
|
+
def unindex( item )
|
|
51
|
+
if @sets_containing_the_item.has_key?( item )
|
|
52
|
+
for item_set in @sets_containing_the_item[item]
|
|
53
|
+
item_set.delete( item )
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@sets_containing_the_item.delete( item )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
item
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unindex_item_from_one_index( item, index_key )
|
|
63
|
+
indx = @indexes[index_key]
|
|
64
|
+
|
|
65
|
+
unless indx.fwf_blank?
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# returns an array of items whose index_val matches the given value
|
|
71
|
+
def lookup( index_key, index_val )
|
|
72
|
+
if @indexes.has_key?( index_key ) && @indexes[index_key].has_key?( index_val )
|
|
73
|
+
@indexes[index_key][index_val].to_a
|
|
74
|
+
else
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# worrying cases:
|
|
80
|
+
#
|
|
81
|
+
# If the old index_name name doesn't exist and new index_name name doesn't exist,
|
|
82
|
+
# it's a null operation
|
|
83
|
+
#
|
|
84
|
+
# If the new_index_name already exists, will raise an error rather than
|
|
85
|
+
# accidentally discarding the index.
|
|
86
|
+
def rename_index( old_index_key, new_index_key )
|
|
87
|
+
raise IndexExistsError.new( "Index exists: #{new_index_key}" ) if @indexes.has_key?( new_index_key )
|
|
88
|
+
|
|
89
|
+
@indexes[new_index_key] = @indexes.delete( old_index_key )
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# deletes all indexing information for the given index
|
|
93
|
+
def drop_index( index_key )
|
|
94
|
+
# returns
|
|
95
|
+
sets_to_remove = @indexes.delete( index_key )
|
|
96
|
+
|
|
97
|
+
unless set_to_remove.fwf_blank?
|
|
98
|
+
# ci => key => a set of items
|
|
99
|
+
@sets_containing_the_item.each do |item, set|
|
|
100
|
+
set.delete( set_to_remove )
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/csv_madness/record.rb
CHANGED
|
@@ -7,50 +7,58 @@ module CsvMadness
|
|
|
7
7
|
# future. I'd like to be able to address by row and by
|
|
8
8
|
# symbol.
|
|
9
9
|
class Record
|
|
10
|
-
|
|
11
|
-
attr_reader :column_accessors_map
|
|
10
|
+
include RecordMethods::MethodMethods
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import_record_data( data )
|
|
16
|
-
end
|
|
12
|
+
attr_accessor :data
|
|
13
|
+
attr_reader :spreadsheet
|
|
17
14
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
when Symbol
|
|
23
|
-
@csv_data[key.to_s]
|
|
24
|
-
end
|
|
15
|
+
def initialize( _data, sheet ) # , mapping )
|
|
16
|
+
@spreadsheet = sheet
|
|
17
|
+
import_record_data( _data )
|
|
18
|
+
convert_types
|
|
25
19
|
end
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
# Experimental approach to getter/setter methods. Seems easier than
|
|
22
|
+
# constantly rewriting an accessor module every time the columns get
|
|
23
|
+
# manipulated.
|
|
24
|
+
#
|
|
25
|
+
# Should raise a method_missing if the get/set method isn't in
|
|
26
|
+
# the index mapping.
|
|
27
|
+
def method_missing( method, *args )
|
|
28
|
+
self.spreadsheet.call_record_method( self, method, args )
|
|
34
29
|
end
|
|
35
30
|
|
|
36
|
-
def columns
|
|
37
|
-
self.class.spreadsheet.columns
|
|
38
|
-
end
|
|
39
31
|
|
|
40
|
-
def
|
|
41
|
-
self.spreadsheet.
|
|
32
|
+
def [] key
|
|
33
|
+
self.spreadsheet.get_cell( self, key )
|
|
42
34
|
end
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
|
|
37
|
+
def []= key, val
|
|
38
|
+
self.spreadsheet.set_cell( self, key, val )
|
|
39
|
+
# case key
|
|
40
|
+
# when String, Integer
|
|
41
|
+
# @csv_data[key] = val
|
|
42
|
+
# when Symbol
|
|
43
|
+
# @csv_data[key.to_s] = val
|
|
44
|
+
# end
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
def
|
|
49
|
-
|
|
47
|
+
def columns
|
|
48
|
+
self.spreadsheet.columns
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def
|
|
53
|
-
|
|
51
|
+
# def self.spreadsheet= sheet
|
|
52
|
+
# @spreadsheet = sheet
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# def self.spreadsheet
|
|
56
|
+
# @spreadsheet
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
def to_csv( hash_opts = {}, **keyword_opts )
|
|
60
|
+
opts = hash_opts.merge(keyword_opts)
|
|
61
|
+
self.columns.map{|col| self.send(col) }.to_csv( **opts )
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
def to_hash
|
|
@@ -58,35 +66,53 @@ module CsvMadness
|
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def to_a
|
|
61
|
-
self.
|
|
69
|
+
self.data
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
def blank?( col )
|
|
65
73
|
(self.send( col.to_sym ).to_s || "").strip.length == 0
|
|
66
74
|
end
|
|
67
75
|
|
|
76
|
+
def convert_types
|
|
77
|
+
self.spreadsheet.convert_types( self ) if self.spreadsheet
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def apply_update_hash( h )
|
|
81
|
+
for key, val in h
|
|
82
|
+
self.send( :"#{key}=", val )
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def inspect
|
|
89
|
+
cols = self.spreadsheet.columns
|
|
90
|
+
cols.zip( self.data ).inspect
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
68
95
|
protected
|
|
69
|
-
def import_record_data(
|
|
70
|
-
case
|
|
96
|
+
def import_record_data( _data )
|
|
97
|
+
case _data
|
|
71
98
|
when Array
|
|
72
|
-
|
|
99
|
+
self.data = _data
|
|
73
100
|
when Hash
|
|
74
101
|
fields = self.columns.map do |col|
|
|
75
|
-
|
|
102
|
+
_data[col]
|
|
76
103
|
end
|
|
77
104
|
# for col in self.columns
|
|
78
105
|
# fields << data[col]
|
|
79
106
|
# end
|
|
80
107
|
|
|
81
|
-
|
|
108
|
+
self.data = fields # CSV::Row.new( self.columns, fields )
|
|
82
109
|
when CSV::Row
|
|
83
|
-
|
|
110
|
+
self.data = _data.to_a.map(&:last)
|
|
111
|
+
when Record
|
|
112
|
+
self.data = _data.data.clone
|
|
84
113
|
else
|
|
85
|
-
raise "
|
|
86
|
-
csv_data = data.csv_data.clone
|
|
114
|
+
raise "Don't know how to handle this input"
|
|
87
115
|
end
|
|
88
|
-
|
|
89
|
-
@csv_data = csv_data
|
|
90
116
|
end
|
|
91
117
|
end
|
|
92
118
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
class RecordFilter
|
|
3
|
+
def initialize( &block )
|
|
4
|
+
@filters = []
|
|
5
|
+
yield self if block_given?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# if the block returns true, the record passes filters, no subsequent filters will be accepted
|
|
9
|
+
def accept( &block )
|
|
10
|
+
@filters << [:accept, block]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# if it returns true, the record fails the filter and is cut from the spreadsheet. Subsequent filters are ignored
|
|
14
|
+
def reject( &block )
|
|
15
|
+
@filters << [:reject, block]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def accept_column( col, val )
|
|
20
|
+
@filters << [:accept_column, col, val]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reject_column( col, val )
|
|
24
|
+
@filters << [:reject_column, col, val]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def filter( records )
|
|
28
|
+
filtered_records = []
|
|
29
|
+
|
|
30
|
+
for r in records
|
|
31
|
+
accepted = false
|
|
32
|
+
rejected = false
|
|
33
|
+
|
|
34
|
+
for filter in @filters
|
|
35
|
+
next if accepted || rejected
|
|
36
|
+
|
|
37
|
+
case filter.first
|
|
38
|
+
when :accept, :accept_column
|
|
39
|
+
accepted = apply_filter( filter, record )
|
|
40
|
+
when :reject, :reject_column
|
|
41
|
+
rejected = apply_filter( filter, record )
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
filtered_records << record if accepted
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
filtered_records
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def apply_filter( filter, record )
|
|
52
|
+
mode, col, val = filter
|
|
53
|
+
procedure = col if col.is_a?(Proc)
|
|
54
|
+
|
|
55
|
+
case mode
|
|
56
|
+
when :accept, :reject
|
|
57
|
+
procedure.call( record )
|
|
58
|
+
when :accept_column, :reject_column
|
|
59
|
+
case val
|
|
60
|
+
when String, Numeric, Date
|
|
61
|
+
record.send( col ) == val
|
|
62
|
+
when Regexp
|
|
63
|
+
record.send( col ) =~ val
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module RecordMethods
|
|
3
|
+
module MethodMethods
|
|
4
|
+
alias :respond_to_naturally? :respond_to?
|
|
5
|
+
alias :natural_methods :methods
|
|
6
|
+
|
|
7
|
+
def extra_method?( method )
|
|
8
|
+
self.spreadsheet.extra_methods.keys.include?( method )
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def column_method?( method )
|
|
12
|
+
column_setter_method?( method ) || column_getter_method?( method )
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def column_setter_method?( method )
|
|
16
|
+
if method[-1] == "="
|
|
17
|
+
self.spreadsheet.has_column?( method[0..-2].to_sym )
|
|
18
|
+
else
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def column_getter_method?( method )
|
|
24
|
+
self.spreadsheet.has_column?( method )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def respond_to?( method )
|
|
28
|
+
self.methods.include?( method )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def methods
|
|
32
|
+
self.natural_methods +
|
|
33
|
+
self.spreadsheet.extra_methods.keys +
|
|
34
|
+
self.spreadsheet.columns +
|
|
35
|
+
self.spreadsheet.columns.map{ |col| to_setter_method( col ) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_setter_method( method )
|
|
39
|
+
if method[-1] != "="
|
|
40
|
+
"#{method}=".to_sym
|
|
41
|
+
else
|
|
42
|
+
method.to_sym
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_getter_method( method )
|
|
47
|
+
if method[-1] == "="
|
|
48
|
+
method[0..-2].to_sym
|
|
49
|
+
else
|
|
50
|
+
method.to_sym
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|