ucdinfo 0.2__py3-none-any.whl

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.
Binary file
ucdinfo/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .ucd import *
2
+ from .ucd import __all__ as ucd_all
3
+
4
+ __all__ = [*ucd_all]
ucdinfo/_initold.py ADDED
@@ -0,0 +1,568 @@
1
+ #!/usr/bin/python3
2
+
3
+ """ucd
4
+
5
+ This module contains most of the ucd information for every character in Unicode.
6
+
7
+ SYNOPSIS:
8
+
9
+ from ucd import get_ucd, get_info, find_ucd, get_enums
10
+ print(get_ucd(0x0041, 'scx'))
11
+ print(get_info(0x0041))
12
+ print(get_enums("indic position"))
13
+ print(find_ucd("indic position", "Bottom")
14
+
15
+ Note cjk properties are not supported for space reasons.
16
+
17
+ If you want to use your own data file (perhaps the module data is stale) the use
18
+ the object interface:
19
+
20
+ from ucd import UCD
21
+ myucd = UCD(localfile="ucd.nounihan.flat.zip") # localfile falls back to bundled data
22
+ print(myucd.get(0x0041, 'scx'))
23
+
24
+ The second parameter specifies the property to be queried and can either be a property from
25
+ https://www.unicode.org/reports/tr42, especially section 4.4 Properties, or the start of a
26
+ full property name from that list.
27
+
28
+ For characters not yet in Unicode, data for additional characters can
29
+ be temporarily appended to the bundled data:
30
+
31
+ from ucd import get_ucd, loadxml
32
+ loadxml("extra-ucd.xml")
33
+
34
+ or, with the object interface:
35
+
36
+ from ucd import UCD
37
+ myucd = UCD().loadxml("extra-ucd.xml")
38
+
39
+ The named file must be coded in the same form as the "flat" UCD XML data, though the only
40
+ required character attributes are "cp" and anything needed by the calling process. For example:
41
+
42
+ <?xml version="1.0" encoding="utf-8" standalone="yes"?>
43
+ <ucd xmlns="http://www.unicode.org/ns/2003/ucd/1.0">
44
+ <description>Some additional characters</description>
45
+ <repertoire>
46
+ <char cp="10EC2" age="16.0" gc="Lo" bc="AL" na="ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW"></char>
47
+ <char cp="10EC3" age="16.0" gc="Lo" bc="AL" na="ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW"></char>
48
+ <char cp="10EC4" age="16.0" gc="Lo" bc="AL" na="ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW"></char>
49
+ </repertoire>
50
+ </ucd>
51
+
52
+ """
53
+
54
+ import array, pickle, pprint
55
+ import xml.etree.ElementTree as et
56
+ import os, bz2, zipfile, io, sys
57
+ import urllib.request
58
+ from datetime import datetime, timezone, timedelta
59
+ from email.utils import parsedate_to_datetime
60
+ import platformdirs
61
+
62
+ __all__ = ['get_ucd', 'find_ucd', 'get_enums', 'get_info']
63
+
64
+ FORMAT_VERSION = "1"
65
+
66
+ # Unicode data xml attributes
67
+ _binfieldnames = """AHex Alpha Bidi_C Bidi_M Cased CE CI Comp_Ex CWCF CWCM CWKCF CWL CWT CWU
68
+ Dash Dep DI Dia Ext Gr_Base Gr_Ext Gr_Link Hex Hyphen IDC Ideo IDS IDSB
69
+ IDST Join_C LOE Lower Math MCM NChar OAlpha ODI OGr_Ext OIDC OIDS OLower OMath
70
+ OUpper Pat_Syn Pat_WS PCM QMark Radical RI SD STerm Term UIdeo Upper VS
71
+ WSpace XIDC XIDS XO_NFC XO_NFD XO_NFKC XO_NFKD"""
72
+ _binmap = dict((x, i) for i, x in enumerate(_binfieldnames.split()))
73
+ _enumfieldnames = """age blk sc scx bc bpt ccc dt ea gc GCB hst InPC InSC jg jt lb
74
+ NFC_QC NFD_QC NFKC_QC NFKD_QC nt SB vo WB nv JSN"""
75
+ _cpfieldnames = """cf dm FC_NFKC lc NFKC_CF scf slc stc suc tc uc bmg bpb"""
76
+ _cpfields = set(_cpfieldnames.split())
77
+ _fields = ['_b0', 'age', 'na', 'JSN', 'gc', 'ccc', 'dt', 'dm', 'nt', 'nv',
78
+ 'bc', 'bpt', 'bpb', 'bmg', 'suc', 'slc', 'stc', 'uc', 'lc', 'tc',
79
+ 'scf', 'cf', 'jt', 'jg', 'ea', 'lb', 'sc', 'scx', 'NFKC_CF', 'FC_NFKC', 'InSC',
80
+ 'InPC', 'vo', 'blk']
81
+ _fieldmap = dict((x, i) for i, x in enumerate(_fields))
82
+
83
+ _property_aliases = {
84
+ "Numeric_Value": "nv",
85
+ "Bidi_Mirroring_Glyph": "bmg",
86
+ "Bidi_Paired_Bracket": "bpb",
87
+ "Case_Folding": "cf",
88
+ "Decomposition_Mapping": "dm",
89
+ "FC_NFKC_Closure": "FC_NFKC",
90
+ "Lowercase_Mapping": "lc",
91
+ "NFKC_Casefold": "NFKC_CF",
92
+ "NFKC_Simple_Casefold": "NFKC_SCF",
93
+ "Simple_Case_Folding": "scf",
94
+ "Simple_Lowercase_Mapping": "slc",
95
+ "Simple_Titlecase_Mapping": "stc",
96
+ "Simple_Uppercase_Mapping": "suc",
97
+ "Titlecase_Mapping": "tc",
98
+ "Uppercase_Mapping": "uc",
99
+ "ISO_Comment": "isc",
100
+ "Jamo_Short_Name": "JSN",
101
+ "Name": "na",
102
+ "Script_Extensions": "scx",
103
+ "Age": "age",
104
+ "Block": "blk",
105
+ "Script": "sc",
106
+ "Bidi_Class": "bc",
107
+ "Bidi_Paired_Bracket_Type": "bpt",
108
+ "Canonical_Combining_Class": "ccc",
109
+ "Decomposition_Type": "dt",
110
+ "East_Asian_Width": "ea",
111
+ "General_Category": "gc",
112
+ "Grapheme_Cluster_Break": "GCB",
113
+ "Hangul_Syllable_Type": "hst",
114
+ "Indic_Conjunct_Break": "InCB",
115
+ "Indic_Positional_Category": "InPC",
116
+ "Indic_Syllabic_Category": "InSC",
117
+ "Joining_Group": "jg",
118
+ "Joining_Type": "jt",
119
+ "Line_Break": "lb",
120
+ "NFC_Quick_Check": "NFC_QC",
121
+ "NFD_Quick_Check": "NFD_QC",
122
+ "NFKC_Quick_Check": "NFKC_QC",
123
+ "NFKD_Quick_Check": "NFKD_QC",
124
+ "Numeric_Type": "nt",
125
+ "Sentence_Break": "SB",
126
+ "Vertical_Orientation": "vo",
127
+ "Word_Break": "WB",
128
+ "ASCII_Hex_Digit": "AHex",
129
+ "Alphabetic": "Alpha",
130
+ "Bidi_Control": "Bidi_C",
131
+ "Bidi_Mirrored": "Bidi_M",
132
+ "Cased": "Cased",
133
+ "Composition_Exclusion": "CE",
134
+ "Case_Ignorable": "CI",
135
+ "Full_Composition_Exclusion": "Comp_Ex",
136
+ "Changes_When_Casefolded": "CWCF",
137
+ "Changes_When_Casemapped": "CWCM",
138
+ "Changes_When_NFKC_Casefolded": "CWKCF",
139
+ "Changes_When_Lowercased": "CWL",
140
+ "Changes_When_Titlecased": "CWT",
141
+ "Changes_When_Uppercased": "CWU",
142
+ "Dash": "Dash",
143
+ "Deprecated": "Dep",
144
+ "Default_Ignorable_Code_Point": "DI",
145
+ "Diacritic": "Dia",
146
+ "Extender": "Ext",
147
+ "Grapheme_Base": "Gr_Base",
148
+ "Grapheme_Extend": "Gr_Ext",
149
+ "Grapheme_Link": "Gr_Link",
150
+ "Hex_Digit": "Hex",
151
+ "Hyphen": "Hyphen",
152
+ "ID_Continue": "IDC",
153
+ "Ideographic": "Ideo",
154
+ "ID_Start": "IDS",
155
+ "IDS_Binary_Operator": "IDSB",
156
+ "IDS_Trinary_Operator": "IDST",
157
+ "IDS_Unary_Operator": "IDSU",
158
+ "Join_Control": "Join_C",
159
+ "Logical_Order_Exception": "LOE",
160
+ "Lowercase": "Lower",
161
+ "Math": "Math",
162
+ "Modifier_Combining_Mark": "MCM",
163
+ "Noncharacter_Code_Point": "NChar",
164
+ "Other_Alphabetic": "OAlpha",
165
+ "Other_Default_Ignorable_Code_Point": "ODI",
166
+ "Other_Grapheme_Extend": "OGr_Ext",
167
+ "Other_ID_Continue": "OIDC",
168
+ "Other_ID_Start": "OIDS",
169
+ "Other_Lowercase": "OLower",
170
+ "Other_Math": "OMath",
171
+ "Other_Uppercase": "OUpper",
172
+ "Pattern_Syntax": "Pat_Syn",
173
+ "Pattern_White_Space": "Pat_WS",
174
+ "Prepended_Concatenation_Mark": "PCM",
175
+ "Quotation_Mark": "QMark",
176
+ "Radical": "Radical",
177
+ "Regional_Indicator": "RI",
178
+ "Soft_Dotted": "SD",
179
+ "Sentence_Terminal": "STerm",
180
+ "Terminal_Punctuation": "Term",
181
+ "Unified_Ideograph": "UIdeo",
182
+ "Uppercase": "Upper",
183
+ "Variation_Selector": "VS",
184
+ "White_Space": "WSpace",
185
+ "XID_Continue": "XIDC",
186
+ "XID_Start": "XIDS",
187
+ "Expands_On_NFC": "XO_NFC",
188
+ "Expands_On_NFD": "XO_NFD",
189
+ "Expands_On_NFKC": "XO_NFKC",
190
+ "Expands_On_NFKD": "XO_NFKD",
191
+ }
192
+
193
+ _property_extras = {
194
+ # -- hand-added informal aliases --
195
+ "category": "gc",
196
+ }
197
+
198
+ # normalized (lowercased) name -> canonical key, built once from the
199
+ # canonical field names, the binary field names, and _property_aliases
200
+ _key_lookup = {}
201
+ for _k in _fields:
202
+ if _k != '_b0':
203
+ _key_lookup[_k.lower()] = _k
204
+ for _k in _binmap:
205
+ _key_lookup[_k.lower()] = _k
206
+ for _alias, _canon in _property_aliases.items():
207
+ _key_lookup[_alias.lower()] = _canon
208
+ for _alias, _canon in _property_extras.items():
209
+ _key_lookup[_alias.lower()] = _canon
210
+
211
+ _fieldnames = {v:k.replace("_", " ") for k, v in _property_aliases.items()}
212
+
213
+ def _rebuild_ucd(items, enums, ucd_version=None):
214
+ obj = list.__new__(UCD)
215
+ obj.extend(items)
216
+ obj.enums = enums
217
+ obj.ucd_version = ucd_version
218
+ return obj
219
+
220
+ def _userroot():
221
+ if sys.platform == 'win32':
222
+ import ctypes
223
+ try:
224
+ return ctypes.windll.shell32.IsUserAdmin() != 0
225
+ except:
226
+ return False
227
+ else:
228
+ return os.geteuid() == 0
229
+
230
+ def _varcache():
231
+ if sys.platform == "win32":
232
+ return os.path.join(os.environ.get("ProgramData", 'C:\\ProgramData'), "python_ucd", "Cache")
233
+ elif sys.platform == "darwin":
234
+ return "/Library/Caches/python_ucd"
235
+ else:
236
+ return "/var/cache/python_ucd"
237
+
238
+ def resolve_key(name):
239
+ """ Translate the property name through the property aliases
240
+ using fuzzy matching. Return the name itself on failure or
241
+ ambiguous match. """
242
+ normalized = name.strip().lower().replace(" ", "_")
243
+ if normalized in _key_lookup:
244
+ return _key_lookup[normalized]
245
+ matches = {v for k, v in _key_lookup.items() if k.startswith(normalized)}
246
+ return matches.pop() if len(matches) == 1 else name
247
+
248
+
249
+ class _Codepoint(tuple):
250
+ """Represents the complete information for a particular codepoint"""
251
+ def __new__(cls, *a, **kw):
252
+ if len(a) == 1 and len(a[0]) == len(_fields):
253
+ return tuple.__new__(cls, a[0])
254
+ if len(kw):
255
+ a = [0] * len(_fields)
256
+ for k, v in kw.items():
257
+ if k in _binmap and v == "Y":
258
+ #i = _fieldmap['_b'+str(_binmap[k][0])]
259
+ a[_fieldmap['_b0']] += (1 << _binmap[k])
260
+ elif k in _fieldmap:
261
+ a[_fieldmap[k]] = v
262
+ return tuple.__new__(cls, a)
263
+
264
+ def __getitem__(self, key):
265
+ if key in _fieldmap and key != "_b0":
266
+ return super(_Codepoint, self).__getitem__(_fieldmap[key])
267
+ elif key in _binmap:
268
+ return True if (super(_Codepoint, self).__getitem__(_fieldmap['_b0']) >> _binmap[key]) & 1 else False
269
+ else:
270
+ raise KeyError("Unknown key: {}".format(key))
271
+
272
+ def __contains__(self, key):
273
+ return key in _fieldmap or key in _binmap
274
+
275
+ def asdict(self, enums):
276
+ ''' Returns a dictionary with nice keys and nice values '''
277
+ res = {}
278
+ for k, v in _fieldmap.items():
279
+ val = super().__getitem__(v)
280
+ if k == "_b0":
281
+ for bk, bv in _binmap.items():
282
+ if (val >> bv) & 1 != 0:
283
+ res[bk] = True
284
+ elif val:
285
+ res[_fieldnames[k]] = enums[k][val] if k in enums else val
286
+ return res
287
+
288
+
289
+ class UCD(list):
290
+ _remote_url = "http://www.unicode.org/Public/latest/ucdxml/ucd.all.flat.zip"
291
+
292
+ def save(self, localfile):
293
+ if localfile.endswith(".bz2"):
294
+ with bz2.open(localfile, "wb") as outf:
295
+ pickle.dump(self, outf, protocol=4)
296
+ elif localfile.endswith(".pickle"):
297
+ with open(localfile, "wb") as outf:
298
+ pickle.dump(self, outf, protocol=4)
299
+ else:
300
+ raise ValueError("localfile must end in .bz2 or .pickle")
301
+
302
+ @classmethod
303
+ def _cache_filename(cls):
304
+ return f"ucdata_pickle_{FORMAT}.bz2"
305
+
306
+ @classmethod
307
+ def _bundled_path(cls):
308
+ try:
309
+ from importlib.resources import files
310
+ p = files("ucd") / "data" / cls._cache_filename()
311
+ return str(p) if p.is_file() else None
312
+ except (ModuleNotFoundError, FileNotFoundError, TypeError):
313
+ return Noe
314
+
315
+ @classmethod
316
+ def _cache_path(cls):
317
+ fname = cls._cache_filename()
318
+ if _userroot():
319
+ cache_dirs = [_varcache()]
320
+ else:
321
+ cache_dirs = [platformdirs.user_cache_dir("python_ucd")]
322
+ cache_dirs.append("/var/cache/python_ucd")
323
+ read_path = None
324
+ for d in cache_dirs:
325
+ p = os.path.join(d, fname)
326
+ if os.path.exists(p):
327
+ read_path = p
328
+ break
329
+ write_dir = cache_dirs[0]
330
+ try:
331
+ os.makedirs(cache_dir, exist_ok=True)
332
+ except OSError:
333
+ pass
334
+ write_path = os.path.join(cache_dir, fname)
335
+ return read_path, write_path
336
+
337
+ @classmethod
338
+ def _cleanup_old_caches(cls, cache_dir):
339
+ keep = cls._cache_filename()
340
+ patterns = ["ucdata_pickle_*.bz2", "ucdata_pickle.bz2"]
341
+ for pat in patterns:
342
+ patpath = os.path.join(cache_dir, pat)
343
+ for p in glob.glob(patpath):
344
+ base = os.path.basename(p)
345
+ if base == keep:
346
+ continue
347
+ try:
348
+ os.remove(path)
349
+ except OSError:
350
+ pass
351
+
352
+ @classmethod
353
+ def test_update(cls, cache_period):
354
+ """cache_period is in days. Returns True if the cache needs
355
+ updating. If the cache file is younger than cache_period, assumes
356
+ it's current (no network call). Otherwise does a HEAD request; if
357
+ the remote isn't newer, touches the cache file's mtime to reset
358
+ the clock and returns False."""
359
+ cache_path, _ = cls._cache_path()
360
+ if not os.path.exists(cache_path):
361
+ return True
362
+
363
+ mtime = datetime.fromtimestamp(os.path.getmtime(cache_path), tz=timezone.utc)
364
+ if datetime.now(timezone.utc) - mtime < timedelta(days=cache_period):
365
+ return False
366
+
367
+ req = urllib.request.Request(cls._remote_url, method="HEAD")
368
+ try:
369
+ with urllib.request.urlopen(req) as resp:
370
+ remote_lm = resp.headers.get("Last-Modified")
371
+ except urllib.error.URLError:
372
+ return False
373
+
374
+ if remote_lm and parsedate_to_datetime(remote_lm) > mtime:
375
+ return True
376
+
377
+ try:
378
+ os.utime(cache_path, None)
379
+ except PermissionError:
380
+ pass
381
+ return False
382
+
383
+ @classmethod
384
+ def force_update(cls):
385
+ """Unconditionally fetch remote data, save to cache and clean up stale files"""
386
+ obj = cls.build_from_remote()
387
+ _, write_path = cls._cache_path()
388
+ try:
389
+ obj.save(write_path)
390
+ cls._cleanup_old_caches(os.path.direname(write_path))
391
+ except OSError:
392
+ pass
393
+ return obj
394
+
395
+ def _loadxml(self, fh, enums=None):
396
+ if enums is None:
397
+ enums = {}
398
+ for k, v in self.enums.items():
399
+ enums[k] = {x: i for i, x in enumerate(v)}
400
+ for (ev, e) in et.iterparse(fh, events=['start']):
401
+ if ev == 'start' and e.tag.endswith('char'):
402
+ d = dict(e.attrib)
403
+ if 'cp' in d:
404
+ firstcp = d.pop('cp')
405
+ lastcp = firstcp
406
+ elif 'first-cp' in d:
407
+ firstcp = d.pop('first-cp')
408
+ lastcp = d.pop('last-cp')
409
+ for n in _cpfields:
410
+ if n not in d or d[n] == "#":
411
+ d[n] = ""
412
+ d[n] = "".join(chr(int(x, 16)) for x in d[n].split())
413
+ for n, v in enums.items():
414
+ if n in d:
415
+ try:
416
+ d[n] = v[d[n]]
417
+ except KeyError:
418
+ # add new allowed value to field:
419
+ i = len(self.enums[n])
420
+ self.enums[n].append(d[n])
421
+ enums[n][d[n]] = i
422
+ d[n] = i
423
+ dat = _Codepoint(**d)
424
+ firsti = int(firstcp, 16)
425
+ lasti = int(lastcp, 16)
426
+ if lasti >= len(self):
427
+ self.extend([None] * (lasti - len(self) + 1))
428
+ for i in range(firsti, lasti+1):
429
+ self[i] = dat
430
+ return self
431
+
432
+ def _preproc(self, filename):
433
+ enums = {}
434
+ for e in _enumfieldnames.split():
435
+ enums[e] = {}
436
+ for (ev, e) in et.iterparse(filename, events=['start']):
437
+ if e.tag.endswith('char'):
438
+ for n, v in enums.items():
439
+ val = e.get(n, None)
440
+ if val is not None:
441
+ v.setdefault(val, len(v))
442
+ self.enums = {}
443
+ for k, v in enums.items():
444
+ self.enums[k] = sorted(v.keys(), key=lambda x:v[x])
445
+ return enums
446
+
447
+ def loadxml(self, filename):
448
+ """ Loads an additional UCDXML-formatted data file; commonly used for pipeline
449
+ characters prior to inclusion in a Unicode release """
450
+ with open(filename) as inf:
451
+ self._loadxml(inf)
452
+ return self
453
+
454
+ def get(self, cp, key):
455
+ """ Looks up a codepoint and returns the value for a given key. This
456
+ includes mapping enums back to their strings"""
457
+ v = self[cp]
458
+ if v is None:
459
+ raise KeyError("Undefined codepoint {:04X}".format(cp))
460
+ key = resolve_key(key)
461
+ if key not in v:
462
+ raise KeyError(f"Unknown or ambiguous property: {key}")
463
+ if key == "na":
464
+ return v[key].replace("#", "{:04X}".format(cp))
465
+ return self.enumstr(key, v[key])
466
+
467
+ def get_info(self, cp):
468
+ """ Returns a dictionary of nicely named properties and values where
469
+ the value is set """
470
+ v = self[cp]
471
+ res = v.asdict(self.enums)
472
+ return res
473
+
474
+ def enumstr(self, key, v):
475
+ """ Returns the string for an enum value given enum name and value """
476
+ key = resolve_key(key)
477
+ if key in self.enums:
478
+ m = self.enums[key]
479
+ return m[v] if v < len(m) else v
480
+ return v
481
+
482
+ def findall(self, key, val):
483
+ """ Returns a list of all the codepoints whose key value is value. Value
484
+ may be an enum name """
485
+ key = resolve_key(key)
486
+ if key in self.enums:
487
+ try:
488
+ enumval = self.enums[key].index(val)
489
+ except ValueError:
490
+ return []
491
+ else:
492
+ enumval = val
493
+ return [cp for cp in range(len(self)) if self[cp] is not None and key in self[cp] and self[cp][key] == enumval]
494
+
495
+
496
+ local_ucd = None
497
+ def _get_local_ucd():
498
+ global local_ucd
499
+ if local_ucd is None:
500
+ local_ucd = UCD()
501
+ return local_ucd
502
+
503
+ def loadxml(filename):
504
+ """ Ensures the global ucd is loaded into memory """
505
+ _get_local_ucd().loadxml(filename)
506
+
507
+ def get_ucd(cp, key):
508
+ """ Given codepoint and key, returns the property value. Key may be the
509
+ identifier or a partial full name from ucd/PropertyAliases.xml """
510
+ return _get_local_ucd().get(cp, key)
511
+
512
+ def get_info(cp):
513
+ """ Given a codepoint returns a nice dictionary of properties """
514
+ return _get_local_ucd().get_info(cp)
515
+
516
+ def find_ucd(key, val):
517
+ """ Returns a list of codepoints whose property key is the given val """
518
+ return _get_local_ucd().findall(key, val)
519
+
520
+ def get_enums(key):
521
+ """ Returns a list of property value names, suitable for passing to find_ucd,
522
+ for a given property key """
523
+ u = _get_local_ucd()
524
+ key = resolve_key(key)
525
+ if key in u.enums:
526
+ return u.enums[key]
527
+ else:
528
+ return []
529
+
530
+ def main():
531
+ import argparse
532
+
533
+ parser = argparse.ArgumentParser()
534
+ parser.add_argument("usv",nargs="?",help="USV")
535
+ parser.add_argument("-p","--property",help="property to query")
536
+ parser.add_argument("-v","--value",help="property value for find_ucd")
537
+ parser.add_argument("-e","--enum",help="Enum property to list values")
538
+ parser.add_argument("-x","--extend",help="XML file to extend properties")
539
+ parser.add_argument("-r","--reload",action='store_true',help="Forces an update of the database from Unicode")
540
+ args = parser.parse_args()
541
+
542
+ if args.reload:
543
+ UCD.force_update()
544
+
545
+ if args.extend:
546
+ loadxml(args.extend)
547
+
548
+ cp = None
549
+ if args.usv:
550
+ try:
551
+ cp = int(args.usv, 16)
552
+ except ValueError:
553
+ pass
554
+
555
+ if cp is not None:
556
+ if args.property:
557
+ print(get_ucd(cp, args.property))
558
+ else:
559
+ pprint.pprint(get_info(cp))
560
+ elif args.enum is not None:
561
+ print("\n".join(get_enums(args.enum)))
562
+ elif args.value and args.property:
563
+ print(" ".join("%04X" % x for x in find_ucd(args.property, args.value)))
564
+ else:
565
+ print("I don't know what to do. Try ucdinfo 0041 as a demo")
566
+
567
+ if __name__ == "__main__":
568
+ main()