Skip to content

Commit 264ff2d

Browse files
committed
PEP8 and formatting
Using autopep8 1.6.0 (pycodestyle: 2.8.0)
1 parent 59d9da8 commit 264ff2d

File tree

4 files changed

+83
-43
lines changed

4 files changed

+83
-43
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Quickstart:
1515
git clone https://github.com/jowave/vcard2to3.git
1616
cd vcard2to3
1717
./vcard2to3.py your_file.vcf
18-
18+
1919
The output will be a file `your_file.vcf.converted`.
2020

2121
To show available command-line arguments run:
@@ -51,7 +51,7 @@ To show available command-line arguments run:
5151
-p, --prune_empty remove vcards which have only FN but no additional
5252
fields
5353

54-
## vcard_merge.py
54+
## vcard\_merge.py
5555

5656
Merge and sort vcards. With `vcard_merge.py` you can remove duplicate contacts. The contacts are sorted and merged by `FN` and duplicate lines within one contact are omitted.
5757
You may have to manually edit the result.

process.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/bin/sh
2+
23
out=$2
34
tmp=$1.tmp
45
if [ -e $tmp ]; then
56
echo "'$tmp' exists. Aborting."
67
exit
78
fi
8-
if [ -z "$out"]; then
9+
if [ -z "$out"]; then
910
out=$1.processed;
1011
fi
1112
./vcard2to3.py --remove '.*@chat\.facebook\.com' --remove_card 'FN:New contact' --remove_dollar --prune_empty $1 $tmp

vcard2to3.py

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@
55
import re
66
import quopri
77

8+
89
class VCard:
910
BEGIN = 'BEGIN:VCARD'
1011
END = 'END:VCARD'
1112
N = re.compile('^N[:;]')
1213
FN = re.compile('^FN[:;]')
1314
NICKNAME = re.compile('NICKNAME[:;]')
15+
1416
def __init__(self, prune_empty=False):
1517
self.reset()
1618
self._prune_empty = prune_empty
19+
1720
def reset(self):
1821
self.lines = []
1922
self._omit = False
2023
self._n = None
2124
self._fn = None
25+
2226
def add(self, line, idx=None):
2327
if idx is not None:
2428
self.lines.insert(idx, line)
@@ -28,10 +32,13 @@ def add(self, line, idx=None):
2832
self._n = line
2933
if VCard.FN.match(line):
3034
self._fn = line
35+
3136
def omit(self):
3237
self._omit = True
38+
3339
def valid(self):
3440
return self._n != None and self._fn != None
41+
3542
def repair(self):
3643
if self.valid():
3744
return
@@ -50,16 +57,21 @@ def repair(self):
5057
# Arbitrary "convertion": whitespace in FN <-> ';' in N
5158
# N and FN have precedence over NICKNAME
5259
if n_idx >= 0 and self._fn is None:
53-
self.add(self.lines[n_idx].replace('N', 'FN', 1).replace(';', ' ', 1), n_idx+1)
60+
self.add(self.lines[n_idx].replace(
61+
'N', 'FN', 1).replace(';', ' ', 1), n_idx+1)
5462
elif fn_idx >= 0 and self._n is None:
55-
self.add(';'.join(self.lines[fn_idx].replace('FN', 'N', 1).split())+'\n', fn_idx+1)
63+
self.add(';'.join(self.lines[fn_idx].replace(
64+
'FN', 'N', 1).split())+'\n', fn_idx+1)
5665
elif nick_idx >= 0 and (not self._prune_empty or len(self.lines) > 4):
5766
# no N or FN but NICKNAME found, use it
5867
# note that this loosens the vCard2.1 spec. (NICKNAME unsupported)
5968
if self._n is None:
60-
self.add(self.lines[nick_idx].replace('NICKNAME', 'N', 1), nick_idx)
69+
self.add(self.lines[nick_idx].replace(
70+
'NICKNAME', 'N', 1), nick_idx)
6171
if self._fn is None:
62-
self.add(self.lines[nick_idx].replace('NICKNAME', 'FN', 1), nick_idx)
72+
self.add(self.lines[nick_idx].replace(
73+
'NICKNAME', 'FN', 1), nick_idx)
74+
6375
def write(self, to):
6476
if self._omit:
6577
return
@@ -70,11 +82,14 @@ def write(self, to):
7082
for line in self.lines:
7183
to.write(line)
7284

85+
7386
class QuotedPrintableDecoder:
7487
# Match 'QUOTED-PRINTABLE' with optional preceding or following 'CHARSET'.
7588
# Note: the value of CHARSET is ignored, decoding is always done using the 'encoding' constructor parameter.
76-
quoted = re.compile('.*((;CHARSET=.+)?;ENCODING=QUOTED-PRINTABLE(;CHARSET=.+?)?):')
77-
def __init__(self, encoding = 'UTF-8'):
89+
quoted = re.compile(
90+
'.*((;CHARSET=.+)?;ENCODING=QUOTED-PRINTABLE(;CHARSET=.+?)?):')
91+
92+
def __init__(self, encoding='UTF-8'):
7893
self.encoding = encoding
7994
self._consumed_lines = ''
8095
pass
@@ -83,11 +98,12 @@ def __call__(self, line):
8398
return self.decode(line)
8499

85100
def decode(self, line):
86-
line = self._consumed_lines + line # add potentially stored previous lines
101+
line = self._consumed_lines + line # add potentially stored previous lines
87102
self._consumed_lines = ''
88103
m = QuotedPrintableDecoder.quoted.match(line)
89104
if m:
90-
line = line[:m.start(1)] + line[m.end(1):] # remove the matched group 1 from line
105+
# remove the matched group 1 from line
106+
line = line[:m.start(1)] + line[m.end(1):]
91107
decoded_line = quopri.decodestring(line).decode(self.encoding)
92108
# Escape newlines, but preserve the last one (which must be '\n', since we read the file in universal newliens mode)
93109
decoded_line = decoded_line[:-1].replace('\r\n', '\\n')
@@ -106,22 +122,35 @@ def consume_incomplete(self, line):
106122

107123

108124
class Replacer:
109-
type_param_re = re.compile('^(TEL|EMAIL|ADR|URL|LABEL|IMPP);([^:=]+:)') # Regex to create 'TYPE=' paramter, see also Replacer.type_lc. In the second group match everything up to ':', but don't match if '=' or another ':' is found.
125+
# Regex to create 'TYPE=' paramter, see also Replacer.type_lc.
126+
# In the second group match everything up to ':', but don't match if '=' or another ':' is found.
127+
type_param_re = re.compile('^(TEL|EMAIL|ADR|URL|LABEL|IMPP);([^:=]+:)')
110128

111129
def __init__(self):
112-
self.replace_filters = [] # array of 2-tuples. Each tuple consists of regular expression object and replacement. Replacement may be a string or a function, see https://docs.python.org/3/library/re.html#re.sub
113-
self.replace_filters.append( (re.compile('^VERSION:.*'), 'VERSION:3.0') )
114-
#self.replace_filters.append( (re.compile('^PHOTO;ENCODING=BASE64;JPEG:'), 'PHOTO:data:image/jpeg;base64,') ) # Version 4.0
115-
self.replace_filters.append( (re.compile('^PHOTO;ENCODING=BASE64;JPEG:'), 'PHOTO;ENCODING=b;TYPE=JPEG:')) # Version 3.0
116-
self.replace_filters.append( (re.compile(';X-INTERNET([;:])'), '\\1') ) # remove non standard X-INTERNET (not needed for EMAIL anyway)
117-
self.replace_filters.append( (re.compile('^X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;([^;]+);.*'), 'NICKNAME:\\1') )
118-
self.replace_filters.append( (re.compile('^X-JABBER(;?.*):(.+)'), 'IMPP\\1:xmpp:\\2') ) # Version 4.0
119-
self.replace_filters.append( (re.compile('^X-ICQ(;?.*):(.+)'), 'IMPP\\1:icq:\\2') ) # Version 4.0
120-
self.replace_filters.append( (Replacer.type_param_re, Replacer.type_lc) )
121-
self.replace_filters.append( (re.compile(';PREF([;:])'), ';TYPE=PREF\\1') ) # Version 3.0
122-
#self.replace_filters.append( (re.compile(';PREF([;:])'), ';PREF=1\\1') ) # Version 4.0
123-
self.replace_filters.append( (re.compile('^EMAIL:([^@]+@jabber.*)'), 'IMPP;xmpp:\\1') )
124-
self.replace_filters.append( (re.compile('^TEL;TYPE=x-mobil:(.*)'), 'TEL;TYPE=cell:\\1') ) # see #9
130+
# array of 2-tuples.
131+
# Each tuple consists of regular expression object and replacement.
132+
# Replacement may be a string or a function, see https://docs.python.org/3/library/re.html#re.sub
133+
self.replace_filters = []
134+
self.replace_filters.append((re.compile('^VERSION:.*'), 'VERSION:3.0'))
135+
# self.replace_filters.append( (re.compile('^PHOTO;ENCODING=BASE64;JPEG:'), 'PHOTO:data:image/jpeg;base64,') ) # Version 4.0
136+
self.replace_filters.append((re.compile(
137+
'^PHOTO;ENCODING=BASE64;JPEG:'), 'PHOTO;ENCODING=b;TYPE=JPEG:')) # Version 3.0
138+
# remove non standard X-INTERNET (not needed for EMAIL anyway)
139+
self.replace_filters.append((re.compile(';X-INTERNET([;:])'), '\\1'))
140+
self.replace_filters.append((re.compile(
141+
'^X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;([^;]+);.*'), 'NICKNAME:\\1'))
142+
self.replace_filters.append(
143+
(re.compile('^X-JABBER(;?.*):(.+)'), 'IMPP\\1:xmpp:\\2')) # Version 4.0
144+
self.replace_filters.append(
145+
(re.compile('^X-ICQ(;?.*):(.+)'), 'IMPP\\1:icq:\\2')) # Version 4.0
146+
self.replace_filters.append((Replacer.type_param_re, Replacer.type_lc))
147+
self.replace_filters.append(
148+
(re.compile(';PREF([;:])'), ';TYPE=PREF\\1')) # Version 3.0
149+
# self.replace_filters.append( (re.compile(';PREF([;:])'), ';PREF=1\\1') ) # Version 4.0
150+
self.replace_filters.append(
151+
(re.compile('^EMAIL:([^@]+@jabber.*)'), 'IMPP;xmpp:\\1'))
152+
self.replace_filters.append(
153+
(re.compile('^TEL;TYPE=x-mobil:(.*)'), 'TEL;TYPE=cell:\\1')) # see #9
125154

126155
def type_lc(matchobj):
127156
# Example:
@@ -130,7 +159,6 @@ def type_lc(matchobj):
130159
# TEL;TYPE=cell,voice:+49123456789
131160
return matchobj.group(1)+';TYPE='+matchobj.group(2).lower().replace(";", ",")
132161

133-
134162
def __call__(self, line):
135163
return self.replace(line)
136164

@@ -157,17 +185,23 @@ def remove(self, line):
157185
return False
158186

159187

160-
161188
def main(argv):
162-
parser = argparse.ArgumentParser(description='Convert VCard 2.1 to VCard 3.0.')
189+
parser = argparse.ArgumentParser(
190+
description='Convert VCard 2.1 to VCard 3.0.')
163191
parser.add_argument('infile')
164192
parser.add_argument('outfile', nargs='?')
165-
parser.add_argument('--in_encoding', default=sys.getdefaultencoding(), help='the encoding of the input file (default: platform dependent)')
166-
parser.add_argument('--out_encoding', default=sys.getdefaultencoding(), help='the encoding for the output file (default: platform dependent)')
167-
parser.add_argument('-r', '--remove', action='append', help='remove lines matching regex REMOVE, can be given multiple times')
168-
parser.add_argument('--remove_card', action='append', help='remove vcards for which any line matches regex REMOVE, can be given multiple times')
169-
parser.add_argument('--remove_dollar', action='store_true', help='remove "$" in N and FN values')
170-
parser.add_argument('-p', '--prune_empty', action='store_true', help='remove vcards which have only FN but no additional fields')
193+
parser.add_argument('--in_encoding', default=sys.getdefaultencoding(),
194+
help='the encoding of the input file (default: platform dependent)')
195+
parser.add_argument('--out_encoding', default=sys.getdefaultencoding(),
196+
help='the encoding for the output file (default: platform dependent)')
197+
parser.add_argument('-r', '--remove', action='append',
198+
help='remove lines matching regex REMOVE, can be given multiple times')
199+
parser.add_argument('--remove_card', action='append',
200+
help='remove vcards for which any line matches regex REMOVE, can be given multiple times')
201+
parser.add_argument('--remove_dollar', action='store_true',
202+
help='remove "$" in N and FN values')
203+
parser.add_argument('-p', '--prune_empty', action='store_true',
204+
help='remove vcards which have only FN but no additional fields')
171205
args = parser.parse_args(argv)
172206

173207
if args.outfile:
@@ -179,7 +213,8 @@ def main(argv):
179213
decoder = QuotedPrintableDecoder(args.in_encoding)
180214
replace = Replacer()
181215
if args.remove_dollar:
182-
replace.replace_filters.append( (re.compile('^(N|FN):([^$]+)\$'), '\\1:\\2') )
216+
replace.replace_filters.append(
217+
(re.compile('^(N|FN):([^$]+)\$'), '\\1:\\2'))
183218
remove_line = Remover(args.remove if args.remove else None)
184219
remove_card = Remover(args.remove_card if args.remove_card else None)
185220

@@ -204,5 +239,6 @@ def main(argv):
204239
if line.startswith(VCard.END):
205240
vcard.write(outfile)
206241

242+
207243
if __name__ == "__main__":
208244
main(sys.argv[1:])

vcard_merge.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import sys
77

88
# FN is required: VCard implementation assumes it is present
9+
10+
911
class VCard:
1012
BEGIN = 'BEGIN:VCARD'
1113
END = 'END:VCARD'
@@ -24,13 +26,13 @@ def add(self, line):
2426
if VCard.VERSION.match(line):
2527
self._version = line
2628
elif VCard.FN.match(line):
27-
self._properties.append([ line ])
29+
self._properties.append([line])
2830
self._fn_idx = len(self._properties)-1
29-
elif line.startswith('\u0020') or line.startswith('\u0009'):
31+
elif line.startswith('\u0020') or line.startswith('\u0009'):
3032
# line continuation: https://tools.ietf.org/html/rfc6350#section-3.2
3133
self._properties[-1].append(line)
3234
else:
33-
self._properties.append([ line ])
35+
self._properties.append([line])
3436

3537
def merge(self, vcard):
3638
# omit other vcard FN
@@ -44,8 +46,8 @@ def write(self, to):
4446
to.write(VCard.BEGIN+'\n')
4547
to.write(self._version)
4648

47-
self._properties.sort(key = VCard._key)
48-
self._fn_idx = 0 # FN is now first property
49+
self._properties.sort(key=VCard._key)
50+
self._fn_idx = 0 # FN is now first property
4951
prev = None
5052
for p in self._properties:
5153
if (VCard._different(prev, p)):
@@ -70,7 +72,7 @@ def _key(a):
7072
l = len(a)
7173
if l > 1:
7274
# multilines come last
73-
return 'ZZ'+str(l);
75+
return 'ZZ'+str(l)
7476
if VCard.FN.match(a[0]):
7577
# FN comes first
7678
return '0'+str(a)
@@ -82,14 +84,14 @@ def _key(a):
8284
def fn_str(self):
8385
return str(self._properties[self._fn_idx])
8486

87+
8588
def main(argv):
8689
parser = argparse.ArgumentParser(description='Merge and sort vcards.')
8790
parser.add_argument('infile')
8891
parser.add_argument('outfile', nargs='?')
8992
parser.add_argument('-v', '--verbose', action='store_true')
9093
args = parser.parse_args(argv)
9194

92-
9395
entries = []
9496
with open(args.infile) as infile:
9597
for line in infile:
@@ -100,7 +102,7 @@ def main(argv):
100102

101103
if args.verbose:
102104
print('input entries :', len(entries))
103-
entries.sort(key = VCard.fn_str)
105+
entries.sort(key=VCard.fn_str)
104106

105107
# merge
106108
entries_merged = []
@@ -131,5 +133,6 @@ def main(argv):
131133
if args.verbose:
132134
print('output entries:', len(entries))
133135

136+
134137
if __name__ == "__main__":
135138
main(sys.argv[1:])

0 commit comments

Comments
 (0)