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
@@ -0,0 +1,95 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
class RelatedNames < BaseField
|
4
|
+
include Params::Type
|
5
|
+
include FieldValues::Text
|
6
|
+
register_for "X-ABRELATEDNAMES"
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
class XAbLabel < BaseField
|
11
|
+
ABLABEL = "X-ABLabel"
|
12
|
+
register_for ABLABEL
|
13
|
+
SAFE_LABELS = %w(HOME WORK FAX CELL PREF MAIN PAGER INTERNET VOICE)
|
14
|
+
STRANGELABEL_MATCHER = /_\$!<(.*)>!\$_/
|
15
|
+
STRANGE_LABELS = {}
|
16
|
+
%w(HomePage Other Assistant Father Mother Parent Brother Sister Child Friend Spouse Partner Manager Anniversary).each do |v|
|
17
|
+
STRANGE_LABELS[v.upcase] = v
|
18
|
+
end
|
19
|
+
STRANGE_LABELS.freeze
|
20
|
+
#SAFECHARS = /^(.[^\"\;\:\,])*$/
|
21
|
+
#ALREADYQOUTED = /^\"(.*?)\"$/
|
22
|
+
|
23
|
+
def self.from_param(param, options = {})
|
24
|
+
raise TypeError, "expected a Param with key == \"TYPE\"" unless param.key.upcase == "TYPE"
|
25
|
+
from_text(param.value, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_param
|
29
|
+
Param.new("TYPE", text)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from_text(t, options = {})
|
33
|
+
new(ABLABEL).tap do |label|
|
34
|
+
label.text = t
|
35
|
+
label.group = options[:group]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.types_to_convert_to_xablabel(field)
|
40
|
+
field.params('TYPE').reject do |type|
|
41
|
+
SAFE_LABELS.include?(type.value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# returns an array of XAbLables
|
46
|
+
def self.from_field(field)
|
47
|
+
types_to_convert_to_xablabel(field).map { |t| from_param(t) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def text
|
51
|
+
if match = STRANGELABEL_MATCHER.match(@value)
|
52
|
+
match[1].upcase
|
53
|
+
else
|
54
|
+
@value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def text=(text)
|
59
|
+
if x = STRANGE_LABELS[text.upcase]
|
60
|
+
@value = "_$!<#{x}>!$_"
|
61
|
+
else
|
62
|
+
@value = text
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
class XAbDate < BaseField
|
69
|
+
include FieldValues::Text
|
70
|
+
include FieldValues::DateValue
|
71
|
+
include Params::Type
|
72
|
+
register_for "X-ABDATE"
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class XAbAdr < BaseField
|
77
|
+
include FieldValues::Text
|
78
|
+
ABADR = "X-ABADR"
|
79
|
+
register_for ABADR
|
80
|
+
|
81
|
+
def self.from_param(param)
|
82
|
+
raise TypeError unless param.is_a?(Param) and param.key.downcase == "x-format"
|
83
|
+
from_text(param.value)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.from_text(t)
|
87
|
+
new(ABADR, EncodingDecoding::encode_text(t))
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_param
|
91
|
+
Param.new("x-format", text)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Virginity
|
2
|
+
|
3
|
+
class XSoocialCustom < BaseField
|
4
|
+
PARTS = %w(key_name value)
|
5
|
+
include FieldValues::StructuredText.define(PARTS)
|
6
|
+
register_for "X-SOOCIAL-CUSTOM"
|
7
|
+
|
8
|
+
def value
|
9
|
+
rewrite_old_kv!
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def key_name
|
14
|
+
rewrite_old_kv!
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def text
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def text=(txt)
|
23
|
+
self.value = txt
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
rewrite_old_kv!
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def rewrite_old_kv!
|
32
|
+
if name = params('NAME')[0]
|
33
|
+
self.key_name, self.value = name.value, EncodingDecoding.decode_text(raw_value)
|
34
|
+
params.delete_if { |p| p.key == "NAME" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
class XSoocialRemovedCategory < BaseField
|
41
|
+
include FieldValues::Text
|
42
|
+
register_for "X-SOOCIAL-REMOVED-CATEGORY"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require "virginity/vcard/fields"
|
2
|
+
|
3
|
+
module Virginity
|
4
|
+
|
5
|
+
class Vcard < DirectoryInformation
|
6
|
+
# A Vcard-wrapper that deals with the the fields N, FN and NICKNAME.
|
7
|
+
#
|
8
|
+
# You will probably use it like this:
|
9
|
+
# v = Vcard.new
|
10
|
+
# v.name.given = "Bert"
|
11
|
+
# puts v
|
12
|
+
#
|
13
|
+
# There are undocumented methods for getting and setting: prefix, given, additional, family, and suffix.
|
14
|
+
class NameHandler
|
15
|
+
# takes a Vcard object or a String
|
16
|
+
def initialize(vcard)
|
17
|
+
if vcard.is_a? Vcard
|
18
|
+
@vcard = vcard
|
19
|
+
else
|
20
|
+
@vcard = Vcard.from_vcard(vcard.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# formatted name
|
25
|
+
def to_s
|
26
|
+
fn.text
|
27
|
+
end
|
28
|
+
alias_method :formatted, :to_s
|
29
|
+
|
30
|
+
# regenerate the formatted name (that is the FN field)
|
31
|
+
def reset_formatted!
|
32
|
+
@vcard.delete(*@vcard.lines_with_name("FN"))
|
33
|
+
fn.text
|
34
|
+
end
|
35
|
+
|
36
|
+
# generate a FN field using the following fields
|
37
|
+
# n > nickname > org > email > impp > tel
|
38
|
+
def generate_fn(options = {})
|
39
|
+
nfield = n
|
40
|
+
g = nfield.given.empty? ? nil : nfield.given
|
41
|
+
f = nfield.family.empty? ? nil : nfield.family
|
42
|
+
unless [g, f].compact.empty?
|
43
|
+
if options[:include_nickname]
|
44
|
+
nick = @vcard.nicknames.empty? ? nil : "\"#{@vcard.nicknames.first.values.first}\""
|
45
|
+
[g, nick, f].compact.join(" ")
|
46
|
+
elsif options[:complete_name]
|
47
|
+
prefix = nfield.prefix.empty? ? nil : nfield.prefix
|
48
|
+
additional = nfield.additional.empty? ? nil : nfield.additional
|
49
|
+
suffix = nfield.suffix.empty? ? nil : nfield.suffix
|
50
|
+
[prefix, g, additional, f, suffix].compact.join(" ")
|
51
|
+
else
|
52
|
+
[g, f].compact.join(" ")
|
53
|
+
end
|
54
|
+
else
|
55
|
+
if not @vcard.nicknames.empty?
|
56
|
+
nicknames.first
|
57
|
+
elsif @vcard.organisations.first
|
58
|
+
@vcard.organisations.first.values.first
|
59
|
+
elsif @vcard.emails.first
|
60
|
+
@vcard.emails.first.address
|
61
|
+
elsif @vcard.impps.first
|
62
|
+
@vcard.impps.first.address
|
63
|
+
elsif @vcard.telephones.first
|
64
|
+
@vcard.telephones.first.number
|
65
|
+
else
|
66
|
+
""
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# generate the fn using the complete name including prefix, additional parts and suffix
|
72
|
+
def complete
|
73
|
+
generate_fn(:complete_name => true)
|
74
|
+
end
|
75
|
+
|
76
|
+
# add a fn if it's not there (since it is required by the vCard specs) and return it
|
77
|
+
def fn
|
78
|
+
@vcard.lines_with_name("FN").first || @vcard.add_field("FN:#{EncodingDecoding::encode_text(generate_fn)}")
|
79
|
+
end
|
80
|
+
|
81
|
+
# add a n if it's not there (since it is required by the vCard specs) and return it
|
82
|
+
def n
|
83
|
+
@vcard.lines_with_name("N").first || @vcard.add_field("N:;;;;")
|
84
|
+
end
|
85
|
+
|
86
|
+
Name::PARTS.each do |part|
|
87
|
+
class_eval <<-end_class_eval
|
88
|
+
def #{part}
|
89
|
+
n.#{part}
|
90
|
+
end
|
91
|
+
|
92
|
+
def #{part}=(value)
|
93
|
+
return value if n.#{part} == value
|
94
|
+
n.#{part} = value
|
95
|
+
reset_formatted!
|
96
|
+
value
|
97
|
+
end
|
98
|
+
end_class_eval
|
99
|
+
end
|
100
|
+
|
101
|
+
# are all parts of the N field empty? ("N:;;;;")
|
102
|
+
def empty?
|
103
|
+
Name::PARTS.all? { |part| send(part).empty? }
|
104
|
+
end
|
105
|
+
|
106
|
+
# an array with all nicknames
|
107
|
+
def nicknames
|
108
|
+
@vcard.nicknames.map {|n| n.values.to_a }.flatten
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_nickname(nick)
|
112
|
+
@vcard << SeparatedField.new("NICKNAME", EncodingDecoding::encode_text_list([nick]))
|
113
|
+
reset_formatted!
|
114
|
+
nick
|
115
|
+
end
|
116
|
+
|
117
|
+
def remove_nickname(nick)
|
118
|
+
@vcard.nicknames.each do |nickname|
|
119
|
+
nickname.values.delete(nick)
|
120
|
+
@vcard.delete nickname if nickname.raw_value.empty? # the singular 'value' is meant here, don't change it to values!
|
121
|
+
end
|
122
|
+
reset_formatted!
|
123
|
+
nick
|
124
|
+
end
|
125
|
+
|
126
|
+
def has_nickname?(nick)
|
127
|
+
@vcard.nicknames.any? { |nickname| nickname.values.include?(nick) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# merge this name with other_name; conflicting parts will raise a MergeError
|
131
|
+
#
|
132
|
+
# if the option :simple_name_resolving is true we choose the value in this name instead of raising an error. Parts that are not present in self will be filled in with the value from other_name
|
133
|
+
def merge_with!(other_name, options = {})
|
134
|
+
Name::PARTS.each do |part|
|
135
|
+
own, his = send(part).rstrip, other_name.send(part).rstrip
|
136
|
+
if own.empty?
|
137
|
+
send "#{part}=", his
|
138
|
+
elsif his.empty? or own == his
|
139
|
+
# then nothing needs to be done
|
140
|
+
else
|
141
|
+
# :simple_name_resolving means keep our own name, don't take over his. iow: do nothing
|
142
|
+
unless options[:simple_name_resolving]
|
143
|
+
raise MergeError, "#{part} name is different: '#{own}' and '#{his}'"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
self
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
module Virginity
|
3
|
+
class Vcard < DirectoryInformation
|
4
|
+
# before: TEL;TYPE=HOME:1234
|
5
|
+
# after: TEL;TYPE=HOME:1233
|
6
|
+
# --- clearly fixing a typo so: ---
|
7
|
+
# update("TEL:1234", :value => 1233)
|
8
|
+
|
9
|
+
# before: TEL;TYPE=HOME:1234
|
10
|
+
# after: TEL;TYPE=HOME,WORK:1234
|
11
|
+
# --- params changed ---
|
12
|
+
# update("TEL:1234", :add_param => "TYPE=WORK")
|
13
|
+
|
14
|
+
# before: TEL;TYPE=HOME:1234
|
15
|
+
# after: TEL;TYPE=WORK:1234
|
16
|
+
# --- params changed ---
|
17
|
+
# update("TEL:1234", :remove_param => "TYPE=HOME", :add_param => "TYPE=WORK")
|
18
|
+
|
19
|
+
# before: TEL;TYPE=HOME:1234
|
20
|
+
# after: TEL;TYPE=WORK:1233
|
21
|
+
# --- someone removed the home phone and added a new number for work ---
|
22
|
+
# remove("TEL:1234")
|
23
|
+
# add("TEL;TYPE=WORK:1233")
|
24
|
+
module Patching
|
25
|
+
class IllegalPatch < Error; end
|
26
|
+
|
27
|
+
def patch!(diff)
|
28
|
+
diff.apply(self)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.diff(before_card, after_card)
|
33
|
+
Diff.diff(before_card, after_card)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# the base class
|
38
|
+
class Patch
|
39
|
+
def self.query_for_line(line)
|
40
|
+
line.name + ":" + line.raw_value
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.query_from_string(query_string)
|
44
|
+
q = Field.parse(query_string)
|
45
|
+
# at this moment all our queries are like "name : raw_value"
|
46
|
+
query = { :name => q.name }
|
47
|
+
if q.respond_to? :values
|
48
|
+
query[:values] = q.values.to_a
|
49
|
+
elsif q.respond_to? :text
|
50
|
+
query[:text] = q.text
|
51
|
+
else
|
52
|
+
query[:raw_value] = q.raw_value
|
53
|
+
end
|
54
|
+
query
|
55
|
+
end
|
56
|
+
|
57
|
+
def field_from_query(query)
|
58
|
+
value = query.reject { |k, v| k == :name }
|
59
|
+
Field.named(query[:name]) do |f|
|
60
|
+
value.each do |k,v|
|
61
|
+
f.send(k+'=', v)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def apply(vcard)
|
67
|
+
raise "responsibility of subclass"
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.normalize_vcard!(vcard)
|
71
|
+
vcard.clean_orgs! # orgs are of variable length, but ordered, they need normalizing before we can compare them
|
72
|
+
# normalize categories and nicknames (actually every unpackable field)
|
73
|
+
vcard.fields.each do |field|
|
74
|
+
vcard.unpack_field!(field) if field.respond_to?(:unpacked)
|
75
|
+
field.clean!
|
76
|
+
field.params.sort!
|
77
|
+
end
|
78
|
+
# remove double lines
|
79
|
+
vcard.clean_same_value_fields!
|
80
|
+
vcard.add("N") unless vcard.lines_with_name("N").any?
|
81
|
+
vcard.clean_name!
|
82
|
+
vcard
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# a Diff is a collection of changes, I think we can even nest them which might be a cool way of combining them
|
88
|
+
class Diff < Patch
|
89
|
+
attr_reader :changes
|
90
|
+
extend Forwardable
|
91
|
+
def_delegators :@changes, :push, :<<, :empty?, :size
|
92
|
+
|
93
|
+
def initialize(*changes)
|
94
|
+
@changes = changes
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.diff(before_card, after_card)
|
98
|
+
patch = Diff.new
|
99
|
+
before, after = before_card.deep_copy, after_card.deep_copy
|
100
|
+
normalize_vcard!(before)
|
101
|
+
normalize_vcard!(after)
|
102
|
+
before.lines.each do |line|
|
103
|
+
# if the exact same line is in after then we can stop processing this line
|
104
|
+
# this will of course always happen for begin:vcard, end:vcard
|
105
|
+
next unless after.delete(line).empty?
|
106
|
+
q = query_from_string(line)
|
107
|
+
if x = after.find_first(q)
|
108
|
+
patch << Update.diff_lines(line, x, :query => q)
|
109
|
+
after.delete(x)
|
110
|
+
#else if "there is a line with just one or 2 characters difference in the value" or "only insignificant characters changed, like dashes in telephone numbers"
|
111
|
+
# patch << Update.diff_lines(line, x, :query => q)
|
112
|
+
# after.delete(x)
|
113
|
+
else
|
114
|
+
patch << Remove.new(q)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# what is left in after should be added
|
118
|
+
after.lines.each { |line| patch << Add.new(line.to_s) }
|
119
|
+
patch
|
120
|
+
end
|
121
|
+
|
122
|
+
def apply(vcard)
|
123
|
+
Patch::normalize_vcard!(vcard)
|
124
|
+
@changes.each { |change| change.apply(vcard) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
@changes.join("\n")
|
129
|
+
end
|
130
|
+
|
131
|
+
def pretty_print(q)
|
132
|
+
@changes.each do |change|
|
133
|
+
pp change
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# to add a field
|
140
|
+
class Add < Patch
|
141
|
+
attr_accessor :field
|
142
|
+
def initialize(line)
|
143
|
+
@field = Field.parse(line)
|
144
|
+
end
|
145
|
+
|
146
|
+
def apply(vcard)
|
147
|
+
vcard.add_field(@field)
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_s
|
151
|
+
"Add(#{@field.inspect})"
|
152
|
+
end
|
153
|
+
|
154
|
+
def pretty_print(q)
|
155
|
+
q.text "Add #{@field}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
# to remove fields matching a query
|
161
|
+
class Remove < Patch
|
162
|
+
attr_accessor :query
|
163
|
+
def initialize(query)
|
164
|
+
@query = query
|
165
|
+
end
|
166
|
+
|
167
|
+
def apply(vcard)
|
168
|
+
vcard.delete(*vcard.where(@query))
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
"Remove(#{@query.inspect})"
|
173
|
+
end
|
174
|
+
|
175
|
+
def pretty_print(q)
|
176
|
+
q.text "Remove #{@query}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# to update field matching the query
|
182
|
+
# for automatically generated diffs this will usually only entail updates to params
|
183
|
+
# there is an alternative action that will be executed if there is no field to update
|
184
|
+
class Update < Patch
|
185
|
+
attr_accessor :query, :updates, :alternative
|
186
|
+
def initialize(query, updates, alternative = nil)
|
187
|
+
@query = query
|
188
|
+
@updates = updates
|
189
|
+
@alternative = alternative
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.diff_lines(before, after, options = {})
|
193
|
+
Update.new(
|
194
|
+
options[:query] || Patch::query_from_string(before),
|
195
|
+
line_diff(before, after),
|
196
|
+
Add.new(after)
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
COLON = ":"
|
201
|
+
SEMICOLON = ";"
|
202
|
+
def update_field!(field)
|
203
|
+
@updates.each_pair do |key, value|
|
204
|
+
case key
|
205
|
+
when :value
|
206
|
+
field.raw_value = value
|
207
|
+
when :add_param
|
208
|
+
# params_from_string returns an array of params
|
209
|
+
field.params += Param::params_from_string(value + COLON)
|
210
|
+
when :remove_param
|
211
|
+
Param::params_from_string(value + COLON).each do |param_to_delete|
|
212
|
+
field.params.delete_if { |p| p == param_to_delete }
|
213
|
+
end
|
214
|
+
else
|
215
|
+
raise IllegalPatch, "#{key}, #{value}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply(vcard)
|
221
|
+
to_update = vcard.where(@query)
|
222
|
+
if to_update.empty?
|
223
|
+
# if the field has been deleted in the meantime on the server, the patch adds it again
|
224
|
+
# to_update << vcard.add_field(field_from_query(@query)) if to_update.empty?
|
225
|
+
@alternative.apply(vcard) unless @alternative.nil?
|
226
|
+
else
|
227
|
+
to_update.each { |field| update_field!(field) }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def to_s
|
232
|
+
s = "Update(#{@query.inspect}, #{@updates.map {|k,v| "#{k}(#{v.inspect})" }.join(", ")})"
|
233
|
+
if @alternative
|
234
|
+
s << " else { #{alternative} }"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def pretty_print(q)
|
239
|
+
q.text "Replace #{query} with #{@updates.map {|k,v| "#{k}(#{v.inspect})" }.join(", ")})"
|
240
|
+
if @alternative
|
241
|
+
q.text " else { #{alternative} }"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
protected
|
246
|
+
# before and after are content lines
|
247
|
+
def self.line_diff(before, after)
|
248
|
+
patch = {}
|
249
|
+
patch[:value] = after.raw_value unless before.raw_value == after.raw_value
|
250
|
+
unless (to_remove = before.params - after.params).empty?
|
251
|
+
patch[:remove_param] = Param.params_to_s(to_remove)
|
252
|
+
end
|
253
|
+
unless (to_add = after.params - before.params).empty?
|
254
|
+
patch[:add_param] = Param.params_to_s(to_add)
|
255
|
+
end
|
256
|
+
patch
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|