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