virginity 0.3.31
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/virginity.rb +6 -0
- data/lib/virginity/api_extensions.rb +87 -0
- data/lib/virginity/api_extensions/fields_to_json.rb +82 -0
- data/lib/virginity/api_extensions/fields_to_xml.rb +151 -0
- data/lib/virginity/bnf.rb +84 -0
- data/lib/virginity/dir_info.rb +93 -0
- data/lib/virginity/dir_info/content_line.rb +146 -0
- data/lib/virginity/dir_info/line_folding.rb +60 -0
- data/lib/virginity/dir_info/param.rb +208 -0
- data/lib/virginity/dir_info/query.rb +144 -0
- data/lib/virginity/encoding_decoding.rb +177 -0
- data/lib/virginity/encodings.rb +36 -0
- data/lib/virginity/fixes.rb +230 -0
- data/lib/virginity/vcard.rb +244 -0
- data/lib/virginity/vcard/base_field.rb +126 -0
- data/lib/virginity/vcard/categories.rb +57 -0
- data/lib/virginity/vcard/cleaning.rb +364 -0
- data/lib/virginity/vcard/field.rb +22 -0
- data/lib/virginity/vcard/field/params.rb +93 -0
- data/lib/virginity/vcard/field_values.rb +10 -0
- data/lib/virginity/vcard/field_values/binary.rb +22 -0
- data/lib/virginity/vcard/field_values/boolean.rb +14 -0
- data/lib/virginity/vcard/field_values/case_insensitive_value.rb +13 -0
- data/lib/virginity/vcard/field_values/date.rb +16 -0
- data/lib/virginity/vcard/field_values/integer.rb +15 -0
- data/lib/virginity/vcard/field_values/optional_structured_text.rb +35 -0
- data/lib/virginity/vcard/field_values/separated_text.rb +59 -0
- data/lib/virginity/vcard/field_values/structured_text.rb +71 -0
- data/lib/virginity/vcard/field_values/text.rb +23 -0
- data/lib/virginity/vcard/field_values/uri.rb +15 -0
- data/lib/virginity/vcard/fields.rb +284 -0
- data/lib/virginity/vcard/fields_osx.rb +95 -0
- data/lib/virginity/vcard/fields_soocial.rb +45 -0
- data/lib/virginity/vcard/name_handler.rb +151 -0
- data/lib/virginity/vcard/patching.rb +262 -0
- data/lib/virginity/vcard21.rb +2 -0
- data/lib/virginity/vcard21/base.rb +30 -0
- data/lib/virginity/vcard21/parser.rb +359 -0
- data/lib/virginity/vcard21/reader.rb +103 -0
- data/lib/virginity/vcard21/writer.rb +139 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d5ded8c5c157bd494a6ea4644503ec864cdc4e32
|
4
|
+
data.tar.gz: 3b6e0534423dda14a021437c9ca323ef47dd5814
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4b0415cde8eac73ea8af7c6b67fbbcec313a034a0b895a00df27e5b5516bdbf2e0b721a89b559461c6100395dc968464e1e4b4bee89bb3b66653dd0b96f226d
|
7
|
+
data.tar.gz: 3fe5472499124c8bd04e6647e91d86ef29d8152e9f47f301c8fa622be9c5f07fae01e87be91e891e67c79dda9d25b84ab75f0fdbf1881c3901d848d5b56314cd
|
data/lib/virginity.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'virginity'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
|
5
|
+
# FIXME move this to the API
|
6
|
+
module Virginity
|
7
|
+
class ContentLine
|
8
|
+
|
9
|
+
# api_id, a SHA1 hash of the whole line
|
10
|
+
def api_id
|
11
|
+
Digest::SHA1.hexdigest(to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
require 'virginity/api_extensions/fields_to_xml'
|
18
|
+
|
19
|
+
|
20
|
+
module Virginity
|
21
|
+
class Param
|
22
|
+
def as_json(options = {})
|
23
|
+
{ key => value }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module StructuredTextAsJson
|
28
|
+
def as_json(options = {})
|
29
|
+
hash = {}
|
30
|
+
components.each do |c|
|
31
|
+
hash[c.to_s] = send(c)
|
32
|
+
end
|
33
|
+
field_as_json(hash, options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module SeparatedTextAsJson
|
38
|
+
def as_json(options = {})
|
39
|
+
field_as_json({ :values => [values] }, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Values
|
44
|
+
module Binary
|
45
|
+
def as_json(options = {})
|
46
|
+
field_as_json({ :binary => binary }, options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class BaseField < ContentLine
|
52
|
+
def group_and_params_as_json(options = {})
|
53
|
+
{ :group => group, :params => params }
|
54
|
+
end
|
55
|
+
|
56
|
+
def field_as_json(hash, options = {})
|
57
|
+
group_and_params_as_json(options).merge(hash).delete_if { |k,v| v.nil? }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
class Email < BaseField
|
64
|
+
def as_json(options = {})
|
65
|
+
field_as_json({ :address => address }, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Tel < BaseField
|
70
|
+
def as_json(options = {})
|
71
|
+
field_as_json({ :number => number }, options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Adr < BaseField
|
76
|
+
include StructuredTextAsJson
|
77
|
+
end
|
78
|
+
|
79
|
+
class Name < BaseField
|
80
|
+
include StructuredTextAsJson
|
81
|
+
end
|
82
|
+
|
83
|
+
class Separated < BaseField
|
84
|
+
include SeparatedTextAsJson
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
class Email < BaseField
|
4
|
+
def as_json(options = {})
|
5
|
+
{ :email => {
|
6
|
+
:id => api_id,
|
7
|
+
:params => params,
|
8
|
+
:address => address
|
9
|
+
}}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
class Tel < BaseField
|
15
|
+
def as_json(options = {})
|
16
|
+
{ :tel => {
|
17
|
+
:id => api_id,
|
18
|
+
:params => params,
|
19
|
+
:number => number
|
20
|
+
}}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class Url < BaseField
|
26
|
+
def as_json(options = {})
|
27
|
+
{ :url => {
|
28
|
+
:id => api_id,
|
29
|
+
:text => text
|
30
|
+
}}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class Adr < BaseField
|
36
|
+
def as_json(options = {})
|
37
|
+
hash = { :adr => {
|
38
|
+
:id => api_id,
|
39
|
+
:params => params,
|
40
|
+
}}
|
41
|
+
components.each do |component|
|
42
|
+
hash[component] = send(component))
|
43
|
+
end
|
44
|
+
hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
class Org < BaseField
|
50
|
+
def as_json(options = {})
|
51
|
+
{ :org => {
|
52
|
+
:id => api_id,
|
53
|
+
:name => orgname,
|
54
|
+
:unit1 => unit1,
|
55
|
+
:unit2 => unit2
|
56
|
+
}}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
class Impp < BaseField
|
62
|
+
def as_json(options = {})
|
63
|
+
{ :impp => {
|
64
|
+
:id => api_id,
|
65
|
+
:scheme => scheme,
|
66
|
+
:address => address,
|
67
|
+
:text => text
|
68
|
+
}}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
class Note < BaseField
|
74
|
+
def as_json(options = {})
|
75
|
+
{ :note => {
|
76
|
+
:id => api_id,
|
77
|
+
:text => text
|
78
|
+
}}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
|
4
|
+
class BaseField < ContentLine
|
5
|
+
def params_to_xml!(params, builder)
|
6
|
+
builder.params(:type => "array") do
|
7
|
+
params.each do |p|
|
8
|
+
builder.tag!(p.key, p.value, :type => "string")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def extra_fields_to_xml(fields, builder)
|
14
|
+
fields.each_pair { |k,v| builder.tag!(k, v) } unless fields.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
class BaseField < ContentLine
|
20
|
+
def params_to_xml
|
21
|
+
s = ""
|
22
|
+
unless params.empty?
|
23
|
+
s << "<params>"
|
24
|
+
params.each do |p|
|
25
|
+
s << xml_element(p.key, p.value)
|
26
|
+
end
|
27
|
+
s << "</params>"
|
28
|
+
end
|
29
|
+
s
|
30
|
+
end
|
31
|
+
|
32
|
+
# def params_to_xml
|
33
|
+
# return "" if params.empty?
|
34
|
+
# "<params>#{params.map {|p| xml_element(p.key, p.value) }.join}</params>"
|
35
|
+
# end
|
36
|
+
|
37
|
+
def value_to_xml
|
38
|
+
xml_element(@value, @value.strip)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_xml
|
42
|
+
s = "<#{name.downcase}>"
|
43
|
+
s << xml_element("group", group) unless group.nil?
|
44
|
+
s << params_to_xml
|
45
|
+
s << value_to_xml
|
46
|
+
s << "</#{name.downcase}>"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Email < BaseField
|
52
|
+
def to_xml(options = {})
|
53
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
54
|
+
xml.email(:index => api_id ) do
|
55
|
+
xml.id api_id, :type => "string"
|
56
|
+
# params_to_xml!(params, xml)
|
57
|
+
xml.address address
|
58
|
+
extra_fields_to_xml(options[:include], xml)
|
59
|
+
end
|
60
|
+
xml.target!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class Tel < BaseField
|
66
|
+
def to_xml(options = {})
|
67
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
68
|
+
xml.telephone(:index => api_id ) do
|
69
|
+
xml.id api_id, :type => "string"
|
70
|
+
# params_to_xml!(params, xml)
|
71
|
+
xml.number number
|
72
|
+
extra_fields_to_xml(options[:include], xml)
|
73
|
+
end
|
74
|
+
xml.target!
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
class Url < BaseField
|
80
|
+
def to_xml(options = {})
|
81
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
82
|
+
xml.url(:index => api_id) do
|
83
|
+
xml.id api_id, :type => "string"
|
84
|
+
xml.text text
|
85
|
+
extra_fields_to_xml(options[:include], xml)
|
86
|
+
end
|
87
|
+
xml.target!
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
class Adr < BaseField
|
93
|
+
def to_xml(options = {})
|
94
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
95
|
+
xml.address(:index => api_id) do
|
96
|
+
xml.id api_id, :type => "string"
|
97
|
+
# params_to_xml!(params, xml)
|
98
|
+
components.each do |component|
|
99
|
+
xml.tag!(component, send(component))
|
100
|
+
end
|
101
|
+
extra_fields_to_xml(options[:include], xml)
|
102
|
+
end
|
103
|
+
xml.target!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class Org < BaseField
|
109
|
+
def to_xml(options = {})
|
110
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
111
|
+
xml.organisation :index => api_id do
|
112
|
+
xml.id api_id, :type => "string"
|
113
|
+
xml.name orgname
|
114
|
+
xml.unit1 unit1
|
115
|
+
xml.unit2 unit2
|
116
|
+
extra_fields_to_xml(options[:include], xml)
|
117
|
+
end
|
118
|
+
xml.target!
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
class Impp < BaseField
|
124
|
+
def to_xml(options = {})
|
125
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
126
|
+
xml.impp(:index => api_id ) do
|
127
|
+
xml.id api_id, :type => "string"
|
128
|
+
xml.scheme scheme
|
129
|
+
xml.address address
|
130
|
+
xml.value text
|
131
|
+
extra_fields_to_xml(options[:include], xml)
|
132
|
+
end
|
133
|
+
xml.target!
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
class TextField < BaseField
|
139
|
+
def to_xml(options = {})
|
140
|
+
xml = options[:builder] || Builder::XmlMarkup.new(options)
|
141
|
+
xml.note(:index => api_id) do
|
142
|
+
xml.id api_id, :type => "string"
|
143
|
+
xml.text text
|
144
|
+
extra_fields_to_xml(options[:include], xml)
|
145
|
+
end
|
146
|
+
xml.target!
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
module Rfc882
|
4
|
+
# rfc822 has a slightly different definition of quoted string compared to rfc2425... it's better
|
5
|
+
QTEXT = /[^\"\\\n]/ # <any CHAR excepting <"> "\" & CR, and including linear-white-space
|
6
|
+
QUOTED_PAIR = /\\./
|
7
|
+
QUOTED_STRING = /(\"((#{QTEXT}|#{QUOTED_PAIR})*)\")/
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module Rfc2234 #:nodoc: # Augmented BNF for Syntax Specifications: ABNF
|
12
|
+
CRLF = /\r\n/
|
13
|
+
DIGIT = /\d/
|
14
|
+
WSP = /\s/ # whitespace
|
15
|
+
end
|
16
|
+
|
17
|
+
module Rfc2425
|
18
|
+
end
|
19
|
+
|
20
|
+
# Contains regular expression strings for the EBNF of rfc 2425.
|
21
|
+
module Bnf #:nodoc:
|
22
|
+
include Rfc882
|
23
|
+
include Rfc2234
|
24
|
+
include Rfc2425
|
25
|
+
# 1*(ALPHA / DIGIT / "-")
|
26
|
+
# added underscore '_' because it's produced by Notes - X-LOTUS-CHILD_UID
|
27
|
+
# added a slash '/' so that it will match lines like: "X-messaging/xmpp-All:someone@gmail.com"
|
28
|
+
# added a space ' ' so that it will match lines like: "X-GOOGLE TALK;TYPE=WORK:janklaassen"
|
29
|
+
NAME = '[a-zA-Z0-9][a-zA-Z0-9\-\_\/\ ]*'
|
30
|
+
|
31
|
+
# <"> <Any character except CTLs, DQUOTE> <">
|
32
|
+
# CTL = <any ASCII control ; ( 0- 37, 0.- 31.)
|
33
|
+
# character and DEL> ; ( 177, 127.)
|
34
|
+
# DQOUTE = '"'
|
35
|
+
# QSTR = '"([^"]*)"'
|
36
|
+
QSTR = QUOTED_STRING # use the definition from rfc822
|
37
|
+
|
38
|
+
# *<Any character except CTLs, DQUOTE, ";", ":", ",">
|
39
|
+
PTEXT = '([^";:,]+)'
|
40
|
+
|
41
|
+
# param-value = ptext / quoted-string
|
42
|
+
PVALUE = "(?:#{QSTR}|#{PTEXT})"
|
43
|
+
|
44
|
+
# param = name "=" param-value *("," param-value)
|
45
|
+
PARAM = ";(#{NAME})=((?:#{PVALUE})?(?:,#{PVALUE})*)"
|
46
|
+
|
47
|
+
# V3.0: contentline = [group "."] name *(";" param) ":" value
|
48
|
+
# V2.1: contentline = *( group "." ) name *(";" param) ":" value
|
49
|
+
#
|
50
|
+
#LINE = "((?:#{NAME}\\.)*)?(#{NAME})([^:]*)\:(.*)"
|
51
|
+
# tcmalloc (used by ree) having memory issues with that one:
|
52
|
+
#LINE = "^((?:#{NAME}\\.)*)?(#{NAME})((?:#{PARAM})*):(.*)$"
|
53
|
+
#LINE = "^((?:#{NAME}\\.)*)?(#{NAME})((?:#{PARAM})*):"
|
54
|
+
# We do not accept the V2.1 syntax.
|
55
|
+
LINE = "^(#{NAME}\\.)?(#{NAME})((?:#{PARAM})*):"
|
56
|
+
|
57
|
+
# date = date-fullyear ["-"] date-month ["-"] date-mday
|
58
|
+
# date-fullyear = 4 DIGIT
|
59
|
+
# date-month = 2 DIGIT
|
60
|
+
# date-mday = 2 DIGIT
|
61
|
+
DATE = '(\d\d\d\d)-?(\d\d)-?(\d\d)'
|
62
|
+
|
63
|
+
# time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
|
64
|
+
# time-hour = 2 DIGIT
|
65
|
+
# time-minute = 2 DIGIT
|
66
|
+
# time-second = 2 DIGIT
|
67
|
+
# time-secfrac = "," 1*DIGIT
|
68
|
+
# time-zone = "Z" / time-numzone
|
69
|
+
# time-numzome = sign time-hour [":"] time-minute
|
70
|
+
TIME = '(\d\d):?(\d\d):?(\d\d)(\.\d+)?(Z|[-+]\d\d:?\d\d)?'
|
71
|
+
|
72
|
+
# integer = (["+"] / "-") 1*DIGIT
|
73
|
+
INTEGER = '[-+]?\d+'
|
74
|
+
|
75
|
+
# QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
|
76
|
+
# ; Any character except CTLs and DQUOTE
|
77
|
+
QSAFECHAR = '[ \t\x21\x23-\x7e\x80-\xff]'
|
78
|
+
|
79
|
+
# SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII
|
80
|
+
# ; Any character except CTLs, DQUOTE, ";", ":", ","
|
81
|
+
SAFECHAR = '[ \t\x21\x23-\x2b\x2d-\x39\x3c-\x7e\x80-\xff]'
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'virginity/bnf'
|
2
|
+
require 'virginity/dir_info/line_folding'
|
3
|
+
require 'virginity/dir_info/content_line'
|
4
|
+
require 'virginity/dir_info/query'
|
5
|
+
|
6
|
+
require 'virginity/vcard21/reader'
|
7
|
+
|
8
|
+
$KCODE = 'U' unless defined? Encoding::UTF_8
|
9
|
+
|
10
|
+
module Virginity
|
11
|
+
|
12
|
+
# see rfc 2425, MIME Content-Type for Directory Information.
|
13
|
+
#
|
14
|
+
# Basically a DirectoryInformation-object is a collection of lines (see ContentLine)
|
15
|
+
class DirectoryInformation
|
16
|
+
include Query
|
17
|
+
extend Virginity::Vcard21::Reader
|
18
|
+
extend Encodings
|
19
|
+
attr_reader :lines
|
20
|
+
|
21
|
+
# decode directory information text
|
22
|
+
# TODO accept an array of lines as the argument, make a special from_string(string="")
|
23
|
+
def initialize(string = "")
|
24
|
+
raise "expected a string but found #{string.inspect}, a #{string.class}" unless string.is_a? String
|
25
|
+
@lines = LineFolding::unfold_and_split(string).map do |line|
|
26
|
+
ContentLine.parse(line)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# string representation
|
31
|
+
def encode
|
32
|
+
LineFolding::fold(unfolded)
|
33
|
+
end
|
34
|
+
alias_method :to_s, :encode
|
35
|
+
|
36
|
+
# replace _all_ lines
|
37
|
+
def lines=(replace_lines)
|
38
|
+
@lines = replace_lines.to_a.map { |line| ContentLine.parse(line.to_s) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class}:#{object_id}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(*lines_to_delete)
|
46
|
+
lines_to_delete.compact.map { |line| lines.delete(line) }.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
# remove only this exact content line identified by its object-id
|
50
|
+
def delete_content_line(cl)
|
51
|
+
lines.delete_if { |line| line.object_id == cl.object_id }
|
52
|
+
end
|
53
|
+
|
54
|
+
# append a line
|
55
|
+
def <<(line)
|
56
|
+
lines << ContentLine.parse(line.to_s)
|
57
|
+
end
|
58
|
+
alias_method :push, :<<
|
59
|
+
|
60
|
+
# string representation that is not folded (see LineFolding)
|
61
|
+
LF = "\n"
|
62
|
+
def unfolded
|
63
|
+
@lines.join(LF) << LF
|
64
|
+
end
|
65
|
+
|
66
|
+
def pretty_print(q)
|
67
|
+
q.text unfolded
|
68
|
+
end
|
69
|
+
|
70
|
+
# equallity is defined as having the same lines
|
71
|
+
def ==(other)
|
72
|
+
return false if other.class != self.class
|
73
|
+
# checking for lines.size is an optimisation
|
74
|
+
@lines.size == other.lines.size and @lines.sort == other.lines.sort
|
75
|
+
end
|
76
|
+
|
77
|
+
# eql? is defined as being of the same class (not a descendent class like Vcard) and having the same lines
|
78
|
+
def eql?(other)
|
79
|
+
self.class == other.class and self == other
|
80
|
+
end
|
81
|
+
|
82
|
+
# are all @lines also present in other?
|
83
|
+
def subset_of?(other)
|
84
|
+
@lines.all? do |line|
|
85
|
+
other.first_match(line.to_s)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def superset_of?(other)
|
90
|
+
other.subset_of?(self)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|