2017-03-12 12:07:28 +00:00
|
|
|
# coding: utf8
|
2017-03-15 16:35:57 +00:00
|
|
|
from __future__ import unicode_literals, print_function
|
2015-12-07 05:01:28 +00:00
|
|
|
import os
|
2015-09-30 18:10:15 +00:00
|
|
|
import io
|
2015-01-05 06:54:13 +00:00
|
|
|
import json
|
2014-09-25 16:26:22 +00:00
|
|
|
import re
|
2015-12-29 15:55:03 +00:00
|
|
|
import os.path
|
2016-09-24 18:26:17 +00:00
|
|
|
import pathlib
|
2017-03-16 16:08:58 +00:00
|
|
|
import sys
|
2017-03-16 08:58:41 +00:00
|
|
|
|
2016-01-16 09:00:57 +00:00
|
|
|
import six
|
2017-03-15 16:35:57 +00:00
|
|
|
import textwrap
|
|
|
|
|
2016-01-13 18:46:17 +00:00
|
|
|
from .attrs import TAG, HEAD, DEP, ENT_IOB, ENT_TYPE
|
2015-12-29 15:55:03 +00:00
|
|
|
|
2016-09-24 20:08:43 +00:00
|
|
|
try:
|
2016-09-24 20:17:01 +00:00
|
|
|
basestring
|
2016-09-24 20:08:43 +00:00
|
|
|
except NameError:
|
2016-09-24 20:17:01 +00:00
|
|
|
basestring = str
|
2015-12-29 15:55:03 +00:00
|
|
|
|
2016-10-09 10:24:24 +00:00
|
|
|
|
2016-03-25 17:54:45 +00:00
|
|
|
LANGUAGES = {}
|
2016-09-24 18:26:17 +00:00
|
|
|
_data_path = pathlib.Path(__file__).parent / 'data'
|
2016-03-25 17:54:45 +00:00
|
|
|
|
|
|
|
|
2016-03-26 10:44:53 +00:00
|
|
|
def set_lang_class(name, cls):
|
2016-03-25 17:54:45 +00:00
|
|
|
global LANGUAGES
|
|
|
|
LANGUAGES[name] = cls
|
|
|
|
|
|
|
|
|
2016-03-26 10:44:53 +00:00
|
|
|
def get_lang_class(name):
|
2016-05-16 23:40:31 +00:00
|
|
|
lang = re.split('[^a-zA-Z0-9]', name, 1)[0]
|
2016-03-25 17:54:45 +00:00
|
|
|
if lang not in LANGUAGES:
|
|
|
|
raise RuntimeError('Language not supported: %s' % lang)
|
|
|
|
return LANGUAGES[lang]
|
|
|
|
|
|
|
|
|
2017-01-09 22:40:26 +00:00
|
|
|
def get_data_path(require_exists=True):
|
|
|
|
if not require_exists:
|
|
|
|
return _data_path
|
|
|
|
else:
|
|
|
|
return _data_path if _data_path.exists() else None
|
2016-09-24 18:26:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
def set_data_path(path):
|
|
|
|
global _data_path
|
|
|
|
if isinstance(path, basestring):
|
|
|
|
path = pathlib.Path(path)
|
|
|
|
_data_path = path
|
|
|
|
|
|
|
|
|
2016-09-25 12:49:53 +00:00
|
|
|
def or_(val1, val2):
|
|
|
|
if val1 is not None:
|
|
|
|
return val1
|
|
|
|
elif callable(val2):
|
|
|
|
return val2()
|
|
|
|
else:
|
|
|
|
return val2
|
|
|
|
|
|
|
|
|
2016-09-24 18:26:17 +00:00
|
|
|
def match_best_version(target_name, target_version, path):
|
|
|
|
path = path if not isinstance(path, basestring) else pathlib.Path(path)
|
2017-01-09 13:10:05 +00:00
|
|
|
if path is None or not path.exists():
|
2016-10-15 12:47:29 +00:00
|
|
|
return None
|
2016-09-24 18:26:17 +00:00
|
|
|
matches = []
|
|
|
|
for data_name in path.iterdir():
|
|
|
|
name, version = split_data_name(data_name.parts[-1])
|
|
|
|
if name == target_name and constraint_match(target_version, version):
|
|
|
|
matches.append((tuple(float(v) for v in version.split('.')), data_name))
|
|
|
|
if matches:
|
|
|
|
return pathlib.Path(max(matches)[1])
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def split_data_name(name):
|
|
|
|
return name.split('-', 1) if '-' in name else (name, '')
|
|
|
|
|
|
|
|
|
2017-03-16 08:58:41 +00:00
|
|
|
def constraint_match(constraint_string, version):
|
|
|
|
# From http://github.com/spacy-io/sputnik
|
|
|
|
if not constraint_string:
|
|
|
|
return True
|
|
|
|
|
|
|
|
constraints = [c.strip() for c in constraint_string.split(',') if c.strip()]
|
|
|
|
|
|
|
|
for c in constraints:
|
|
|
|
if not re.match(r'[><=][=]?\d+(\.\d+)*', c):
|
|
|
|
raise ValueError('invalid constraint: %s' % c)
|
|
|
|
|
|
|
|
return all(semver.match(version, c) for c in constraints)
|
|
|
|
|
|
|
|
|
2016-09-24 18:26:17 +00:00
|
|
|
def read_regex(path):
|
|
|
|
path = path if not isinstance(path, basestring) else pathlib.Path(path)
|
|
|
|
with path.open() as file_:
|
|
|
|
entries = file_.read().split('\n')
|
|
|
|
expression = '|'.join(['^' + re.escape(piece) for piece in entries if piece.strip()])
|
|
|
|
return re.compile(expression)
|
|
|
|
|
|
|
|
|
2016-09-25 12:49:53 +00:00
|
|
|
def compile_prefix_regex(entries):
|
2017-01-08 19:33:28 +00:00
|
|
|
if '(' in entries:
|
|
|
|
# Handle deprecated data
|
|
|
|
expression = '|'.join(['^' + re.escape(piece) for piece in entries if piece.strip()])
|
|
|
|
return re.compile(expression)
|
|
|
|
else:
|
|
|
|
expression = '|'.join(['^' + piece for piece in entries if piece.strip()])
|
|
|
|
return re.compile(expression)
|
2016-09-24 18:26:17 +00:00
|
|
|
|
|
|
|
|
2016-09-25 12:49:53 +00:00
|
|
|
def compile_suffix_regex(entries):
|
2016-09-24 18:26:17 +00:00
|
|
|
expression = '|'.join([piece + '$' for piece in entries if piece.strip()])
|
|
|
|
return re.compile(expression)
|
|
|
|
|
|
|
|
|
2016-09-25 12:49:53 +00:00
|
|
|
def compile_infix_regex(entries):
|
2016-09-24 18:26:17 +00:00
|
|
|
expression = '|'.join([piece for piece in entries if piece.strip()])
|
|
|
|
return re.compile(expression)
|
|
|
|
|
|
|
|
|
2015-10-07 08:25:35 +00:00
|
|
|
def normalize_slice(length, start, stop, step=None):
|
|
|
|
if not (step is None or step == 1):
|
|
|
|
raise ValueError("Stepped slices not supported in Span objects."
|
|
|
|
"Try: list(tokens)[start:stop:step] instead.")
|
|
|
|
if start is None:
|
|
|
|
start = 0
|
|
|
|
elif start < 0:
|
|
|
|
start += length
|
|
|
|
start = min(length, max(0, start))
|
|
|
|
|
|
|
|
if stop is None:
|
|
|
|
stop = length
|
|
|
|
elif stop < 0:
|
|
|
|
stop += length
|
|
|
|
stop = min(length, max(start, stop))
|
|
|
|
|
|
|
|
assert 0 <= start <= stop <= length
|
|
|
|
return start, stop
|
|
|
|
|
|
|
|
|
2014-09-25 16:26:22 +00:00
|
|
|
def utf8open(loc, mode='r'):
|
2015-09-30 18:10:15 +00:00
|
|
|
return io.open(loc, mode, encoding='utf8')
|
2016-09-25 12:49:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def check_renamed_kwargs(renamed, kwargs):
|
|
|
|
for old, new in renamed.items():
|
|
|
|
if old in kwargs:
|
|
|
|
raise TypeError("Keyword argument %s now renamed to %s" % (old, new))
|
2017-03-15 16:35:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def print_msg(*text, **kwargs):
|
|
|
|
"""Print formatted message. Each positional argument is rendered as newline-
|
|
|
|
separated paragraph. If kwarg 'title' exist, title is printed above the text
|
|
|
|
and highlighted (using ANSI escape sequences manually to avoid unnecessary
|
|
|
|
dependency)."""
|
|
|
|
|
|
|
|
message = '\n\n'.join([_wrap_text(t) for t in text])
|
|
|
|
tpl_msg = '\n{msg}\n'
|
|
|
|
tpl_title = '\n\033[93m{msg}\033[0m'
|
|
|
|
|
|
|
|
if 'title' in kwargs and kwargs['title']:
|
|
|
|
title = _wrap_text(kwargs['title'])
|
|
|
|
print(tpl_title.format(msg=title))
|
|
|
|
print(tpl_msg.format(msg=message))
|
|
|
|
|
|
|
|
|
|
|
|
def _wrap_text(text):
|
|
|
|
"""Wrap text at given width using textwrap module. Indent should consist of
|
|
|
|
spaces. Its length is deducted from wrap width to ensure exact wrapping."""
|
|
|
|
|
|
|
|
wrap_max = 80
|
|
|
|
indent = ' '
|
|
|
|
wrap_width = wrap_max - len(indent)
|
|
|
|
return textwrap.fill(text, width=wrap_width, initial_indent=indent,
|
|
|
|
subsequent_indent=indent)
|
2017-03-16 16:08:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
def sys_exit(*messages, **kwargs):
|
|
|
|
"""Performs SystemExit. For modules used from the command line, like
|
|
|
|
download and link. To print message, use the same arguments as for
|
|
|
|
print_msg()."""
|
|
|
|
|
|
|
|
if messages:
|
|
|
|
print_msg(*messages, **kwargs)
|
|
|
|
sys.exit(0)
|