ruport 0.4.23 → 0.4.99
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/AUTHORS +16 -8
- data/CHANGELOG +30 -1
- data/README +144 -114
- data/Rakefile +12 -4
- data/TODO +4 -7
- data/bin/rope +21 -28
- data/examples/line_graph.rb +36 -0
- data/examples/sample_invoice_report.rb +1 -1
- data/examples/simple_graph.rb +8 -0
- data/lib/SVG/Graph/Bar.rb +137 -0
- data/lib/SVG/Graph/BarBase.rb +140 -0
- data/lib/SVG/Graph/BarHorizontal.rb +136 -0
- data/lib/SVG/Graph/Graph.rb +977 -0
- data/lib/SVG/Graph/Line.rb +444 -0
- data/lib/SVG/Graph/Pie.rb +394 -0
- data/lib/SVG/Graph/Plot.rb +494 -0
- data/lib/SVG/Graph/Schedule.rb +373 -0
- data/lib/SVG/Graph/TimeSeries.rb +241 -0
- data/lib/ruport.rb +2 -2
- data/lib/ruport/config.rb +47 -3
- data/lib/ruport/data/collection.rb +17 -1
- data/lib/ruport/data/record.rb +101 -8
- data/lib/ruport/data/set.rb +81 -2
- data/lib/ruport/data/set.rb.rej +147 -0
- data/lib/ruport/data/set.rb~ +73 -0
- data/lib/ruport/data/table.rb +127 -2
- data/lib/ruport/data/taggable.rb +21 -2
- data/lib/ruport/format.rb +36 -44
- data/lib/ruport/format/engine.rb +21 -1
- data/lib/ruport/format/plugin.rb +64 -1
- data/lib/ruport/mailer.rb +70 -36
- data/lib/ruport/meta_tools.rb +15 -6
- data/lib/ruport/query.rb +1 -1
- data/lib/ruport/rails/reportable.rb +23 -1
- data/lib/ruport/report.rb +11 -11
- data/lib/ruport/report/invoice.rb +16 -0
- data/lib/ruport/system_extensions.rb +3 -55
- data/test/{tc_database.rb → _test_database.rb} +0 -0
- data/test/{tc_config.rb → test_config.rb} +0 -0
- data/test/{tc_format.rb → test_format.rb} +1 -0
- data/test/{tc_format_engine.rb → test_format_engine.rb} +14 -2
- data/test/test_graph.rb +101 -0
- data/test/{tc_invoice.rb → test_invoice.rb} +7 -1
- data/test/test_mailer.rb +108 -0
- data/test/test_meta_tools.rb +14 -0
- data/test/{tc_plugin.rb → test_plugin.rb} +12 -1
- data/test/{tc_query.rb → test_query.rb} +0 -0
- data/test/{tc_record.rb → test_record.rb} +9 -0
- data/test/{tc_report.rb → test_report.rb} +2 -1
- data/test/{tc_ruport.rb → test_ruport.rb} +0 -0
- data/test/test_set.rb +118 -0
- data/test/test_set.rb.rej +16 -0
- data/test/{tc_set.rb → test_set.rb~} +17 -0
- data/test/{tc_sql_split.rb → test_sql_split.rb} +0 -0
- data/test/{tc_table.rb → test_table.rb} +15 -0
- data/test/{tc_taggable.rb → test_taggable.rb} +0 -0
- data/test/unit.log +361 -0
- metadata +52 -30
- data/examples/bar.pdf +0 -193
- data/examples/f.log +0 -5
- data/examples/foo.pdf +0 -193
- data/lib/ruport/format/document.rb +0 -78
- data/lib/ruport/format/open_node.rb +0 -38
- data/test/tc_data_row.rb +0 -132
- data/test/tc_data_set.rb +0 -386
- data/test/tc_document.rb +0 -42
- data/test/tc_element.rb +0 -18
- data/test/tc_page.rb +0 -42
- data/test/tc_section.rb +0 -45
- data/test/ts_all.rb +0 -12
- data/test/ts_format.rb +0 -7
data/lib/ruport.rb
CHANGED
@@ -14,7 +14,7 @@ module Ruport
|
|
14
14
|
|
15
15
|
#begin; require 'rubygems'; rescue LoadError; nil end
|
16
16
|
|
17
|
-
VERSION = "0.4.
|
17
|
+
VERSION = "0.4.99"
|
18
18
|
|
19
19
|
# Ruports logging and error interface.
|
20
20
|
# Can generate warnings or raise fatal errors
|
@@ -67,6 +67,6 @@ module Ruport
|
|
67
67
|
end
|
68
68
|
|
69
69
|
|
70
|
-
%w[config meta_tools report format query data].each { |lib|
|
70
|
+
%w[config meta_tools report format query data mailer].each { |lib|
|
71
71
|
require "ruport/#{lib}"
|
72
72
|
}
|
data/lib/ruport/config.rb
CHANGED
@@ -58,46 +58,89 @@ module Ruport
|
|
58
58
|
#
|
59
59
|
module Config
|
60
60
|
module_function
|
61
|
-
|
62
|
-
|
61
|
+
|
62
|
+
|
63
|
+
# create or retrieve a database source configuration.
|
64
|
+
#
|
65
|
+
# setting a source
|
66
|
+
#
|
67
|
+
# source :default, :user => "root", :password => "clyde",
|
68
|
+
# :dsn => "dbi:mysql:blinkybase"
|
69
|
+
#
|
70
|
+
# retrieving a source
|
71
|
+
#
|
72
|
+
# db = source(:default) #=> <OpenStruct ..>
|
73
|
+
# db.dsn #=> "dbi:mysql:blinkybase"
|
74
|
+
def source(*args)
|
63
75
|
return sources[args.first] if args.length == 1
|
64
76
|
sources[args.first] = OpenStruct.new(*args[1..-1])
|
65
77
|
check_source(sources[args.first],args.first)
|
66
78
|
end
|
67
79
|
|
80
|
+
# create or retrieve a mailer configuration
|
81
|
+
#
|
82
|
+
# creating a mailer config
|
83
|
+
#
|
84
|
+
# mailer :alternate, :host => "mail.test.com",
|
85
|
+
# :address => "test@test.com",
|
86
|
+
# :user => "test", :password => "blinky"
|
87
|
+
# :auth_type => :cram_md5
|
88
|
+
#
|
89
|
+
# retreiving a mailer config
|
90
|
+
#
|
91
|
+
# mail_conf = mailer(:alternate) #=> <OpenStruct ..>
|
92
|
+
# mail_conf.address #=> test@test.com
|
68
93
|
def mailer(*args)
|
94
|
+
return mailers[args.first] if args.length == 1
|
69
95
|
mailers[args.first] = OpenStruct.new(*args[1..-1])
|
70
96
|
check_mailer(mailers[args.first],args.first)
|
71
97
|
end
|
72
98
|
|
99
|
+
|
100
|
+
# Sets the logger to use the specified file.
|
101
|
+
#
|
102
|
+
# log_file "foo.log"
|
73
103
|
def log_file(file)
|
74
104
|
@logger = Logger.new(file)
|
75
105
|
end
|
76
106
|
|
77
|
-
#
|
107
|
+
# Same as Config.log_file, but accessor style
|
78
108
|
def log_file=(file)
|
79
109
|
log_file(file)
|
80
110
|
end
|
81
111
|
|
112
|
+
# Returns the source which is labeled :default
|
82
113
|
def default_source
|
83
114
|
sources[:default]
|
84
115
|
end
|
85
116
|
|
117
|
+
# Returns the mailer which is labeled :default
|
86
118
|
def default_mailer
|
87
119
|
mailers[:default]
|
88
120
|
end
|
89
121
|
|
122
|
+
# Returns an array of database source configs
|
90
123
|
def sources; @sources ||= {}; end
|
91
124
|
|
125
|
+
# Returns an array of mailer configs
|
92
126
|
def mailers; @mailers ||= {}; end
|
93
127
|
|
128
|
+
# Returns the currently active logger
|
94
129
|
def logger; @logger; end
|
95
130
|
|
131
|
+
# Forces all messages marked :log_only to surface
|
96
132
|
def enable_paranoia; @paranoid = true; end
|
133
|
+
|
134
|
+
# Disables the printing of :log_only messages to STDERR
|
97
135
|
def disable_paranoia; @paranoid = false; end
|
136
|
+
|
137
|
+
# Sets paranoid status
|
98
138
|
def paranoid=(val); @paranoid = val; end
|
139
|
+
|
140
|
+
# Checks to see if paranoia is enabled
|
99
141
|
def paranoid?; !!@paranoid; end
|
100
142
|
|
143
|
+
# Verifies that you have provided a DSN for your source
|
101
144
|
def check_source(settings,label)
|
102
145
|
unless settings.dsn
|
103
146
|
Ruport.complain(
|
@@ -108,6 +151,7 @@ module Ruport
|
|
108
151
|
end
|
109
152
|
end
|
110
153
|
|
154
|
+
# Verifies that you have provided a host for your mailer
|
111
155
|
def check_mailer(settings, label)
|
112
156
|
unless settings.host
|
113
157
|
Ruport.complain(
|
@@ -1,4 +1,12 @@
|
|
1
|
+
# The Ruport Data Collections.
|
2
|
+
# Authors: Gregory Brown / Dudley Flanders
|
3
|
+
#
|
4
|
+
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
+
# Copyright 2006 by respective content owners, all rights reserved.
|
6
|
+
|
1
7
|
module Ruport::Data
|
8
|
+
|
9
|
+
# This is the base class for Ruport's Data structures.
|
2
10
|
class Collection
|
3
11
|
require "forwardable"
|
4
12
|
extend Forwardable
|
@@ -9,18 +17,26 @@ module Ruport::Data
|
|
9
17
|
@data = data.dup if data
|
10
18
|
end
|
11
19
|
|
20
|
+
# Simple formatting tool which allows you to quickly generate a formatted
|
21
|
+
# table from a Collection object, eg
|
22
|
+
#
|
23
|
+
# my_collection.as(:csv) #=> "1,2,3\n4,5,6"
|
12
24
|
def as(type)
|
13
25
|
Ruport::Format.table :data => self, :plugin => type
|
14
26
|
end
|
15
27
|
|
28
|
+
# Converts any Collection object to a Data::Set
|
16
29
|
def to_set
|
17
30
|
Set.new :data => data
|
18
31
|
end
|
19
|
-
|
32
|
+
|
33
|
+
# Converts any Collection object to a Data::Table
|
20
34
|
def to_table(options={})
|
21
35
|
Table.new({:data => data.map { |r| r.to_a }}.merge(options))
|
22
36
|
end
|
23
37
|
|
38
|
+
# Provides a shortcut for the as() method by converting as(:format_name)
|
39
|
+
# into to_format_name
|
24
40
|
def method_missing(id,*args)
|
25
41
|
return as($1.to_sym) if id.to_s =~ /^to_(.*)/
|
26
42
|
super
|
data/lib/ruport/data/record.rb
CHANGED
@@ -1,10 +1,42 @@
|
|
1
|
+
# The Ruport Data Collections.
|
2
|
+
# Authors: Gregory Brown / Dudley Flanders
|
3
|
+
#
|
4
|
+
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
+
# Copyright 2006 by respective content owners, all rights reserved.
|
1
6
|
module Ruport::Data
|
7
|
+
|
8
|
+
# Data::Records are the work horse of Ruport's Data model. These can behave
|
9
|
+
# as array like, hash like, or struct like objects. They are used as the base
|
10
|
+
# record for both Tables and Sets in Ruport.
|
2
11
|
class Record
|
3
12
|
require "forwardable"
|
4
13
|
extend Forwardable
|
5
14
|
include Enumerable
|
6
15
|
include Taggable
|
7
16
|
|
17
|
+
# Creates a new Record object. If the <tt>:attributes</tt> keyword is
|
18
|
+
# specified, Hash-like and Struct-like access will be enabled. Otherwise,
|
19
|
+
# Record elements may be accessed ordinally, like an Array.
|
20
|
+
#
|
21
|
+
# Records accept either Hashes or Arrays as their data.
|
22
|
+
#
|
23
|
+
# a = Record.new [1,2,3]
|
24
|
+
# a[1] #=> 2
|
25
|
+
#
|
26
|
+
# b = Record.new [1,2,3], :attributes => %w[a b c]
|
27
|
+
# b[1] #=> 2
|
28
|
+
# b['a'] #=> 1
|
29
|
+
# b.c #=> 3
|
30
|
+
#
|
31
|
+
# c = Record.new {"a" => 1, "c" => 3, "b" => 2}, :attributes => %w[a b c]
|
32
|
+
# b[1] #=> 2
|
33
|
+
# b['a'] #=> 1
|
34
|
+
# b.c #=> 3
|
35
|
+
#
|
36
|
+
# c = Record.new { "a" => 1, "c" => 3, "b" => 2 }
|
37
|
+
# b[1] #=> ? (without attributes, you cannot rely on order)
|
38
|
+
# b['a'] #=> 1
|
39
|
+
# b.c #=> 3
|
8
40
|
def initialize(data,options={})
|
9
41
|
if data.kind_of?(Hash)
|
10
42
|
if options[:attributes]
|
@@ -18,26 +50,50 @@ module Ruport::Data
|
|
18
50
|
@attributes = options[:attributes]
|
19
51
|
end
|
20
52
|
end
|
21
|
-
|
53
|
+
|
54
|
+
# The underlying data which is being stored in the record
|
22
55
|
attr_reader :data
|
56
|
+
|
23
57
|
def_delegators :@data,:each, :length
|
24
58
|
|
59
|
+
# Allows either array or hash_like indexing
|
60
|
+
#
|
61
|
+
# my_record[1]
|
62
|
+
# my_record["foo"]
|
63
|
+
#
|
64
|
+
# Also, this provides a feature via method_missing which allows
|
65
|
+
# my_record.foo
|
25
66
|
def [](index)
|
26
67
|
if index.kind_of? Integer
|
68
|
+
raise "Invalid index" unless index < @data.length
|
27
69
|
@data[index]
|
28
70
|
else
|
71
|
+
raise "Invalid index" unless @attributes.index(index)
|
29
72
|
@data[@attributes.index(index)]
|
30
73
|
end
|
31
74
|
end
|
32
75
|
|
76
|
+
|
77
|
+
# Allows setting a value at an index
|
78
|
+
#
|
79
|
+
# my_record[1] = "foo"
|
80
|
+
# my_record["bar"] = "baz"
|
81
|
+
#
|
82
|
+
# And via method_missing
|
83
|
+
# my_record.ghost = "blinky"
|
33
84
|
def []=(index, value)
|
34
85
|
if index.kind_of? Integer
|
86
|
+
raise "Invalid index" unless index < @data.length
|
35
87
|
@data[index] = value
|
36
88
|
else
|
89
|
+
raise "Invalid index" unless @attributes.index(index)
|
37
90
|
@data[attributes.index(index)] = value
|
38
91
|
end
|
39
92
|
end
|
40
93
|
|
94
|
+
|
95
|
+
# If attributes and data are equivalent, then == evaluates to true.
|
96
|
+
# Otherwise, == returns false
|
41
97
|
def ==(other)
|
42
98
|
return false if @attributes && !other.attributes
|
43
99
|
return false if other.attributes && !@attributes
|
@@ -45,20 +101,49 @@ module Ruport::Data
|
|
45
101
|
end
|
46
102
|
|
47
103
|
alias_method :eql?, :==
|
48
|
-
|
104
|
+
|
105
|
+
# Makes an array out of the data wrapped by Record
|
106
|
+
#
|
107
|
+
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
108
|
+
# a.to_a #=> [1,2]
|
49
109
|
def to_a; @data.dup; end
|
50
|
-
|
110
|
+
|
111
|
+
# Makes a hash out of the data wrapped by Record
|
112
|
+
# Only works if attributes are specified
|
113
|
+
#
|
114
|
+
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
115
|
+
# a.to_h #=> {"a" => 1, "b" => 2}
|
51
116
|
def to_h; Hash[*@attributes.zip(data).flatten] end
|
52
|
-
|
117
|
+
|
118
|
+
# Returns a copy of the list of attribute names associated with this Record.
|
119
|
+
#
|
120
|
+
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
121
|
+
# a.attributes #=> ["a","b"]
|
53
122
|
def attributes; @attributes && @attributes.dup; end
|
54
123
|
|
124
|
+
# Sets the attribute list for this Record
|
125
|
+
#
|
126
|
+
# my_record.attributes = %w[foo bar baz]
|
55
127
|
def attributes=(a); @attributes=a; end
|
56
128
|
|
129
|
+
# Allows you to change the order of or reduce the number of columns in a
|
130
|
+
# Record. Example:
|
131
|
+
#
|
132
|
+
# a = Data::Record.new([1,2,3,4],:attributes => %w[a b c d])
|
133
|
+
# b = a.reorder("a","d","b")
|
134
|
+
# b.attributes #=> ["a","d","b"]
|
135
|
+
# b.data #=> [1,4,2]
|
57
136
|
def reorder(*indices)
|
58
137
|
dup.reorder! *indices
|
59
138
|
end
|
60
|
-
|
139
|
+
|
140
|
+
# Same as Record#reorder but is destructive
|
61
141
|
def reorder!(*indices)
|
142
|
+
indices = indices[0] if indices[0].kind_of?(Array)
|
143
|
+
indices.each do |i|
|
144
|
+
self[i] rescue raise ArgumentError,
|
145
|
+
"you may have specified an invalid column"
|
146
|
+
end
|
62
147
|
@data = indices.map { |i| self[i] }
|
63
148
|
if @attributes
|
64
149
|
if indices.all? { |e| e.kind_of? Integer }
|
@@ -68,18 +153,26 @@ module Ruport::Data
|
|
68
153
|
end
|
69
154
|
end; self
|
70
155
|
end
|
71
|
-
|
156
|
+
|
157
|
+
# Makes a fresh copy of the Record
|
72
158
|
def dup
|
73
|
-
self.class.new(@data,:attributes => attributes)
|
159
|
+
copy = self.class.new(@data,:attributes => attributes)
|
160
|
+
copy.tags = self.tags.dup
|
161
|
+
return copy
|
74
162
|
end
|
75
163
|
|
76
164
|
#FIXME: This does not take into account frozen / tainted state
|
77
165
|
alias_method :clone, :dup
|
78
|
-
|
166
|
+
|
167
|
+
# provides a unique hash value
|
79
168
|
def hash
|
80
169
|
(attributes.to_a + data.to_a).hash
|
81
170
|
end
|
82
171
|
|
172
|
+
# provides accessor style methods for attribute access, Example
|
173
|
+
#
|
174
|
+
# my_record.foo = 2
|
175
|
+
# my_record.foo #=> 2
|
83
176
|
def method_missing(id,*args)
|
84
177
|
id = id.to_s.gsub(/=$/,"")
|
85
178
|
if @attributes.include?(id)
|
data/lib/ruport/data/set.rb
CHANGED
@@ -1,41 +1,120 @@
|
|
1
|
+
# The Ruport Data Collections.
|
2
|
+
# Authors: Gregory Brown / Dudley Flanders
|
3
|
+
#
|
4
|
+
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
+
# Copyright 2006 by respective content owners, all rights reserved.
|
1
6
|
require 'set'
|
2
7
|
|
3
8
|
module Ruport::Data
|
9
|
+
|
10
|
+
# This class is one of the core classes for building and working with data
|
11
|
+
# in Ruport. The idea is to get your data into a standard form, regardless
|
12
|
+
# of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
|
13
|
+
#
|
14
|
+
# Set is intended to be used as the data store for unstructured data -
|
15
|
+
# Ruport::Data::Table is an alternate intermediary data store intended
|
16
|
+
# for structured, tabular data.
|
17
|
+
#
|
18
|
+
# Once your data is in a Ruport::Data::Set object, it can be manipulated
|
19
|
+
# to suit your needs, then used to build a report.
|
4
20
|
class Set < Collection
|
5
21
|
|
22
|
+
# Creates a new set containing the elements of options[:data].
|
23
|
+
#
|
24
|
+
# Set.new :data => [%w[one two three] %w[1 2 3] %w[I II III]]
|
6
25
|
def initialize(options={})
|
7
26
|
@data = ::Set.new
|
8
27
|
options[:data].each {|e| self << e} if options[:data]
|
9
28
|
end
|
10
29
|
|
11
|
-
|
30
|
+
# Adds the given object to the set and returns self.
|
31
|
+
# set = Set.new :data => [%w[one two three]]
|
32
|
+
# set << [5,6,7]
|
33
|
+
def add(other)
|
12
34
|
case other
|
13
35
|
when Record
|
14
36
|
@data << other
|
15
37
|
when Array
|
16
38
|
@data << Record.new(other)
|
17
39
|
end
|
40
|
+
self
|
18
41
|
end
|
42
|
+
alias_method :<<, :add
|
19
43
|
|
44
|
+
# Produces a shallow copy of the set: the same data elements are
|
45
|
+
# referenced by both the old and new sets.
|
46
|
+
#
|
47
|
+
# set = Set.new :data => [%w[one two three]]
|
48
|
+
# set2 = set.dup
|
49
|
+
# set == set2 #=> true
|
50
|
+
# set << [8,9,10]
|
51
|
+
# set == set2 #=> false
|
52
|
+
def dup
|
53
|
+
a = self.class.new(:data=>@data)
|
54
|
+
a.tags = tags.dup
|
55
|
+
return a
|
56
|
+
end
|
57
|
+
alias_method :clone, :dup
|
58
|
+
|
59
|
+
# Equality. Two sets are equal if they contain the same set of objects.
|
60
|
+
# s1 = Set.new :data => [[1,2,3]]
|
61
|
+
# s2 = Set.new :data => [[1,2,3]]
|
62
|
+
# s1 == s2 #=> true
|
20
63
|
def ==(other)
|
21
64
|
@data == other.data
|
22
65
|
end
|
23
66
|
|
67
|
+
# Union. Returns a new set containing the union of the objects contained in
|
68
|
+
# the two sets.
|
69
|
+
#
|
70
|
+
# s1 = Set.new :data => [[1,2,3]]
|
71
|
+
# s2 = Set.new :data => [[4,5,6]]
|
72
|
+
# s3 = s1 | s2
|
73
|
+
# s4 = Set.new :data => [[1,2,3], [4,5,6]]
|
74
|
+
# s3 == s4 #=> true
|
24
75
|
def |(other)
|
25
76
|
Set.new :data => (@data | other.data)
|
26
77
|
end
|
27
78
|
alias_method :union, :|
|
79
|
+
alias_method :+, :|
|
28
80
|
|
81
|
+
# Intersection. Returns a new set containing the objects common to the two
|
82
|
+
# sets.
|
83
|
+
#
|
84
|
+
# s1 = Set.new :data => [%w[a b c],[1,2,3]]
|
85
|
+
# s2 = Set.new :data => [%w[a b c],[4,5,6]]
|
86
|
+
# s3 = s1 & s2
|
87
|
+
# s4 = Set.new :data => [%w[a b c]]
|
88
|
+
# s3 == s4 #=> true
|
29
89
|
def &(other)
|
30
90
|
Set.new :data => (@data & other.data)
|
31
91
|
end
|
32
92
|
alias_method :intersection, :&
|
33
93
|
|
34
|
-
#
|
94
|
+
# Difference. Returns a new set containing those objects present in this
|
95
|
+
# set but not the other.
|
96
|
+
#
|
97
|
+
# s1 = Set.new :data => [%w[a b c],[1,2,3]]
|
98
|
+
# s2 = Set.new :data => [%w[a b c],[4,5,6]]
|
99
|
+
# s3 = s1 - s2
|
100
|
+
# s4 = Set.new :data => [[1, 2, 3]]
|
101
|
+
# s3 == s4 #=> true
|
35
102
|
def -(other)
|
36
103
|
Set.new :data => (@data - other.data)
|
37
104
|
end
|
38
105
|
alias_method :difference, :-
|
106
|
+
|
107
|
+
# Exclusion. Returns a new set containing those objects in this set or the
|
108
|
+
# other set but not in both.
|
109
|
+
#
|
110
|
+
# s1 = Set.new :data => [%w[a b c],[1,2,3]]
|
111
|
+
# s2 = Set.new :data => [%w[a b c],[4,5,6]]
|
112
|
+
# s3 = s1 ^ s2
|
113
|
+
# s4 = Set.new :data => [[1, 2, 3],[4,5,6]]
|
114
|
+
# s3 == s4 #=> true
|
115
|
+
def ^(other)
|
116
|
+
Set.new :data => (@data ^ other.data)
|
117
|
+
end
|
39
118
|
|
40
119
|
def_delegators :@data, :each
|
41
120
|
end
|