Move files
This commit is contained in:
parent
3cf28379e8
commit
00cc8225c5
56 changed files with 31 additions and 49 deletions
11
great_ai/utilities/__init__.py
Normal file
11
great_ai/utilities/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from .chunk import chunk
|
||||
from .clean import clean
|
||||
from .config_file import ConfigFile, ParseError
|
||||
from .evaluate_ranking import evaluate_ranking
|
||||
from .get_sentences import get_sentences
|
||||
from .language import english_name_of_language, is_english, predict_language
|
||||
from .logger import get_logger
|
||||
from .match_names import match_names
|
||||
from .parallel_map import WorkerException, parallel_map, threaded_parallel_map
|
||||
from .unchunk import unchunk
|
||||
from .unique import unique
|
||||
17
great_ai/utilities/chunk.py
Normal file
17
great_ai/utilities/chunk.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from typing import Iterable, List, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def chunk(values: Iterable[T], chunk_size: int) -> Iterable[T]:
|
||||
assert chunk_size >= 1
|
||||
|
||||
result: List[T] = []
|
||||
for v in values:
|
||||
result.append(v)
|
||||
if len(result) == chunk_size:
|
||||
yield result
|
||||
result = []
|
||||
|
||||
if len(result) > 0:
|
||||
yield result
|
||||
68
great_ai/utilities/clean.py
Normal file
68
great_ai/utilities/clean.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import html
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
import unidecode
|
||||
|
||||
from .data import left_regular_punctuations, right_regular_punctuations
|
||||
from .external.pylatexenc.latex2text import LatexNodes2Text
|
||||
from .logger import get_logger
|
||||
|
||||
logger = get_logger("clean")
|
||||
latex = LatexNodes2Text()
|
||||
|
||||
|
||||
joined_left_punctuations = "".join(left_regular_punctuations).replace("]", r"\]")
|
||||
joined_right_punctuations = "".join(right_regular_punctuations).replace("[", r"\[")
|
||||
|
||||
|
||||
def clean(
|
||||
text: str,
|
||||
ignore_xml: bool = False,
|
||||
ignore_latex: bool = False,
|
||||
remove_brackets: bool = False,
|
||||
convert_to_ascii: bool = False,
|
||||
) -> str:
|
||||
if not ignore_xml:
|
||||
text = re.sub(r"<[^>]*>", " ", text)
|
||||
text = html.unescape(text)
|
||||
|
||||
if not ignore_latex:
|
||||
text = text.replace("%", "\\%") # escape LaTeX comments before parsing as LaTeX
|
||||
|
||||
try:
|
||||
text = latex.latex_to_text(text, tolerant_parsing=True, strict_braces=False)
|
||||
text = text.replace("%s", " ")
|
||||
except:
|
||||
logger.exception("Latex parsing error")
|
||||
|
||||
if convert_to_ascii:
|
||||
text = unicodedata.normalize("NFKD", text)
|
||||
|
||||
try:
|
||||
text.encode("ASCII", errors="strict")
|
||||
except UnicodeEncodeError:
|
||||
text = "".join([c for c in text if not unicodedata.combining(c)])
|
||||
text = unidecode.unidecode(text)
|
||||
|
||||
text = re.sub(
|
||||
r"\b[a-zA-Z](?:[\t ]+[a-zA-Z]\b)+", lambda m: re.sub(r"[\t ]", "", m[0]), text
|
||||
) # A R T I C L E => ARTICLE
|
||||
|
||||
if remove_brackets:
|
||||
text = re.sub(r"\[[^\]]*\]", " ", text)
|
||||
|
||||
# fix hypens: break- word => break-word
|
||||
text = re.sub(r"(\S)-\s+", r"\1-", text)
|
||||
text = re.sub(r"\s+-(\S)", r"-\1", text)
|
||||
|
||||
# collapse whitespace
|
||||
text = re.sub(r"\s+", " ", text)
|
||||
|
||||
# fix punctuation
|
||||
text = re.sub(rf" ([{joined_left_punctuations}])", r"\1", text)
|
||||
text = re.sub(rf"([{joined_right_punctuations}]) ", r"\1", text)
|
||||
|
||||
text = text.strip()
|
||||
|
||||
return text
|
||||
2
great_ai/utilities/config_file/__init__.py
Normal file
2
great_ai/utilities/config_file/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .config_file import ConfigFile
|
||||
from .parse_error import ParseError
|
||||
91
great_ai/utilities/config_file/config_file.py
Normal file
91
great_ai/utilities/config_file/config_file.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, Tuple, Union
|
||||
|
||||
from ..logger import get_logger
|
||||
from .parse_error import ParseError
|
||||
from .pattern import pattern
|
||||
|
||||
ENVIRONMENT_VARIABLE_KEY_PREFIX = "ENV"
|
||||
|
||||
logger = get_logger("ConfigFile")
|
||||
|
||||
|
||||
class ConfigFile:
|
||||
def __init__(self, path: Union[Path, str]) -> None:
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path)
|
||||
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(path.absolute())
|
||||
|
||||
self._path = path
|
||||
self._key_values: Dict[str, str] = {}
|
||||
|
||||
self._parse()
|
||||
|
||||
def _parse(self):
|
||||
with open(self._path, encoding="utf-8") as f:
|
||||
lines: str = f.read()
|
||||
|
||||
matches = pattern.findall(lines)
|
||||
for key, *values in matches:
|
||||
try:
|
||||
value = next(v for v in values if v)
|
||||
except StopIteration:
|
||||
raise ParseError(
|
||||
f"Cannot parse config file ({self._path.absolute()}), error at key `{key}`"
|
||||
)
|
||||
|
||||
already_exists = key in self._key_values
|
||||
if already_exists and not value.startswith(
|
||||
f"{ENVIRONMENT_VARIABLE_KEY_PREFIX}:"
|
||||
):
|
||||
raise KeyError(
|
||||
f"Key `{key}` has been already defined and its value is `{self._key_values[key]}`"
|
||||
)
|
||||
|
||||
if value.startswith(f"{ENVIRONMENT_VARIABLE_KEY_PREFIX}:"):
|
||||
_, value = value.split(":")
|
||||
if value not in os.environ:
|
||||
issue = f'The value of `{key}` contains the "{ENVIRONMENT_VARIABLE_KEY_PREFIX}` prefix but `{value}` is not defined as an environment variable'
|
||||
if already_exists:
|
||||
logger.warning(
|
||||
f"{issue}, using the default value defined above (`{self._key_values[key]}`)"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
raise KeyError(
|
||||
f"{issue} and no default value has been provided"
|
||||
)
|
||||
else:
|
||||
value = os.environ[value]
|
||||
|
||||
self._key_values[key] = value
|
||||
|
||||
def __getattr__(self, key: str) -> str:
|
||||
if key in self._key_values:
|
||||
return self._key_values[key]
|
||||
raise KeyError(
|
||||
f"Key `{key}` is not found in configuration file ({self._path.absolute()})"
|
||||
)
|
||||
|
||||
__getitem__ = __getattr__
|
||||
|
||||
def __iter__(self) -> Iterable[Tuple[str, str]]:
|
||||
return iter(self._key_values)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._key_values)
|
||||
|
||||
def keys(self):
|
||||
return self._key_values.keys()
|
||||
|
||||
def values(self):
|
||||
return self._key_values.values()
|
||||
|
||||
def items(self):
|
||||
return self._key_values.items()
|
||||
|
||||
def __repr__(self):
|
||||
return f"{type(self).__name__}(path={self._path}) {self._key_values}"
|
||||
2
great_ai/utilities/config_file/parse_error.py
Normal file
2
great_ai/utilities/config_file/parse_error.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
class ParseError(Exception):
|
||||
pass
|
||||
20
great_ai/utilities/config_file/pattern.py
Normal file
20
great_ai/utilities/config_file/pattern.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import re
|
||||
|
||||
pattern = re.compile(
|
||||
r"""
|
||||
(?:^|\n) # new key-value pairs must start on a new line
|
||||
\s* # leading whitespace is allowed
|
||||
(?!\#) # the key cannot start with a `#` symbol
|
||||
(\w+?) # then comes the key
|
||||
\s*=\s* # the key and value are separated by an equal sign
|
||||
(?: # then comes the value
|
||||
"([^"]*)" # the value can be surrounded by quotes: "value"
|
||||
| '([^']*)' # the value can be surrounded by quotes: 'value'
|
||||
| `([^`]*)` # the value can be surrounded by quotes: `value`
|
||||
| ([^#\n]*?) # or it is bare, in that case, the trailing whitespace is ignored
|
||||
)
|
||||
\s*(?:\#.*)? # comments can be added with the `#` symbol
|
||||
(?=\n|$) # a key-value pairs are separated by new lines
|
||||
""",
|
||||
flags=re.UNICODE | re.VERBOSE,
|
||||
)
|
||||
6
great_ai/utilities/data/__init__.py
Normal file
6
great_ai/utilities/data/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .american_spellings import american_spellings
|
||||
from .punctuations import (
|
||||
left_regular_punctuations,
|
||||
right_regular_punctuations,
|
||||
sentence_ending_punctuations,
|
||||
)
|
||||
1711
great_ai/utilities/data/american_spellings.py
Normal file
1711
great_ai/utilities/data/american_spellings.py
Normal file
File diff suppressed because it is too large
Load diff
22
great_ai/utilities/data/punctuations.py
Normal file
22
great_ai/utilities/data/punctuations.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
sentence_ending_punctuations = [".", "?", "!", ":", ";"]
|
||||
|
||||
# punctuations that usually have no space between them and the character to their left
|
||||
left_regular_punctuations = [
|
||||
*sentence_ending_punctuations,
|
||||
",",
|
||||
"'",
|
||||
"%",
|
||||
")",
|
||||
"}",
|
||||
"]",
|
||||
]
|
||||
|
||||
# punctuations that usually have no space between them and the character to their right
|
||||
right_regular_punctuations = [
|
||||
"(",
|
||||
"{",
|
||||
"[",
|
||||
"$",
|
||||
"€",
|
||||
"#",
|
||||
]
|
||||
2
great_ai/utilities/evaluate_ranking/__init__.py
Normal file
2
great_ai/utilities/evaluate_ranking/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .draw_f1_iso_lines import draw_f1_iso_lines
|
||||
from .evaluate_ranking import evaluate_ranking
|
||||
28
great_ai/utilities/evaluate_ranking/draw_f1_iso_lines.py
Normal file
28
great_ai/utilities/evaluate_ranking/draw_f1_iso_lines.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from typing import Optional
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
|
||||
def draw_f1_iso_lines(
|
||||
resolution: int = 1000,
|
||||
min: float = 0.2,
|
||||
max: float = 0.8,
|
||||
steps: int = 4,
|
||||
axes: Optional[Axes] = None,
|
||||
) -> None:
|
||||
if axes is None:
|
||||
axes = plt.axes()
|
||||
|
||||
for f_score in np.linspace(min, max, num=steps):
|
||||
x = np.linspace(f_score / (2 - f_score), 1, num=resolution)
|
||||
y = f_score * x / (2 * x - f_score)
|
||||
|
||||
axes.plot(x[y >= 0], y[y >= 0], color="gray", alpha=0.2)
|
||||
|
||||
axes.annotate(
|
||||
f"f1={f_score:0.1f}",
|
||||
backgroundcolor="w",
|
||||
xy=(0.9, y[int(resolution * 0.9)] + 0.02),
|
||||
)
|
||||
90
great_ai/utilities/evaluate_ranking/evaluate_ranking.py
Normal file
90
great_ai/utilities/evaluate_ranking/evaluate_ranking.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from sklearn.metrics import average_precision_score, precision_recall_curve
|
||||
|
||||
from ..unique import unique
|
||||
from .draw_f1_iso_lines import draw_f1_iso_lines
|
||||
|
||||
|
||||
def evaluate_ranking(
|
||||
expected: List[Union[str, float]],
|
||||
actual_scores: List[float],
|
||||
target_recall: float,
|
||||
title: Optional[str] = "",
|
||||
disable_interpolation: bool = False,
|
||||
axes: Optional[plt.Axes] = None,
|
||||
output_svg: Optional[Path] = None,
|
||||
reverse_order: bool = False,
|
||||
plot: bool = True,
|
||||
) -> Dict[Union[str, float], float]:
|
||||
assert 0 <= target_recall <= 1
|
||||
|
||||
if plot and axes is None:
|
||||
fig = plt.figure(figsize=(20, 10))
|
||||
fig.patch.set_facecolor("white")
|
||||
ax = plt.axes()
|
||||
else:
|
||||
ax = axes
|
||||
|
||||
classes = sorted(unique(expected), reverse=reverse_order)
|
||||
str_classes = [str(c) for c in classes]
|
||||
|
||||
with matplotlib.rc_context({"font.size": 20}):
|
||||
if plot:
|
||||
ax.set_xmargin(0)
|
||||
|
||||
draw_f1_iso_lines(axes=ax)
|
||||
|
||||
results: Dict[Union[str, float], float] = {}
|
||||
for i in range(len(classes) - 1):
|
||||
binarized_expected = [
|
||||
(v < classes[i]) if reverse_order else (v > classes[i])
|
||||
for v in expected
|
||||
]
|
||||
precision, recall, _ = precision_recall_curve(
|
||||
binarized_expected, actual_scores
|
||||
)
|
||||
|
||||
if not disable_interpolation:
|
||||
for j in range(1, len(precision)):
|
||||
precision[j] = max(precision[j - 1], precision[j])
|
||||
|
||||
closest_recall_index = np.argmin(np.abs(recall - target_recall))
|
||||
precision_at_closest_recall = precision[closest_recall_index]
|
||||
average_precision = average_precision_score(
|
||||
binarized_expected, actual_scores
|
||||
)
|
||||
results[classes[i]] = precision_at_closest_recall
|
||||
|
||||
if plot:
|
||||
ax.plot(
|
||||
recall,
|
||||
precision,
|
||||
label=f"{'|'.join(str_classes[:i + 1])} ↔ {'|'.join(str_classes[i+1:])} (P@{target_recall:.2f}={precision_at_closest_recall:.2f}, AP={average_precision:.2f})",
|
||||
)
|
||||
|
||||
if plot:
|
||||
ax.legend(loc="upper right")
|
||||
ax.axvline(x=target_recall, linestyle="--", color="#55c6bb", linewidth=2.0)
|
||||
|
||||
if title is None:
|
||||
title = "Ranking evaluation"
|
||||
|
||||
ax.set_title(f'{title} ({" < ".join(str_classes)})', pad=20)
|
||||
|
||||
ax.set_xlabel("Recall")
|
||||
ax.set_ylabel("Precision")
|
||||
|
||||
ax.set_xticks([target_recall] + sorted(ax.get_xticks()))
|
||||
|
||||
if plot and output_svg is None:
|
||||
if axes is None:
|
||||
plt.show()
|
||||
elif output_svg:
|
||||
plt.savefig(output_svg, format="svg")
|
||||
|
||||
return results
|
||||
0
great_ai/utilities/external/__init__.py
vendored
Normal file
0
great_ai/utilities/external/__init__.py
vendored
Normal file
1
great_ai/utilities/external/pylatexenc/README.md
vendored
Normal file
1
great_ai/utilities/external/pylatexenc/README.md
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
https://github.com/phfaist/pylatexenc
|
||||
37
great_ai/utilities/external/pylatexenc/__init__.py
vendored
Normal file
37
great_ai/utilities/external/pylatexenc/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2015 Philippe Faist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
"""
|
||||
Utilities for LaTeX to/from Unicode Text conversion.
|
||||
|
||||
Main Site:
|
||||
|
||||
https://github.com/phfaist/pylatexenc/
|
||||
|
||||
"""
|
||||
|
||||
from .version import version_str as _version_str
|
||||
|
||||
__version__ = _version_str
|
||||
161
great_ai/utilities/external/pylatexenc/_util.py
vendored
Normal file
161
great_ai/utilities/external/pylatexenc/_util.py
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2019 Philippe Faist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
# Internal module. Internal API may move, disappear or otherwise change at any
|
||||
# time and without notice.
|
||||
|
||||
|
||||
try:
|
||||
# Python >= 3.3
|
||||
from collections.abc import MutableMapping
|
||||
except ImportError:
|
||||
from collections import MutableMapping
|
||||
|
||||
import bisect
|
||||
import warnings
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def pylatexenc_deprecated_ver(ver, msg, stacklevel=2):
|
||||
warnings.warn(
|
||||
"Deprecated (pylatexenc {}): {} ".format(ver, msg.strip()),
|
||||
DeprecationWarning,
|
||||
stacklevel=stacklevel + 1,
|
||||
)
|
||||
|
||||
|
||||
def pylatexenc_deprecated_2(msg, stacklevel=2):
|
||||
warnings.warn(
|
||||
(
|
||||
"Deprecated (pylatexenc 2.0): {} "
|
||||
"[see https://pylatexenc.readthedocs.io/en/latest/new-in-pylatexenc-2/]"
|
||||
).format(msg.strip()),
|
||||
DeprecationWarning,
|
||||
stacklevel=stacklevel + 1,
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class LazyDict(MutableMapping):
|
||||
r"""
|
||||
A lazy dictionary that loads its data when it is first queried.
|
||||
|
||||
This is used to store the legacy
|
||||
:py:data:`pylatexenc.latexwalker.default_macro_dict` as well as
|
||||
:py:data:`pylatexenc.latex2text.default_macro_dict` etc. Such that these
|
||||
"dictionaries" are still exposed at the module-level, but the data is loaded
|
||||
only if they are actually queried.
|
||||
"""
|
||||
|
||||
def __init__(self, generate_dict_fn):
|
||||
self._full_dict = None
|
||||
self._generate_dict_fn = generate_dict_fn
|
||||
|
||||
def _ensure_instance(self):
|
||||
if self._full_dict is not None:
|
||||
return
|
||||
self._full_dict = self._generate_dict_fn()
|
||||
|
||||
def __getitem__(self, key):
|
||||
self._ensure_instance()
|
||||
return self._full_dict.__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
self._ensure_instance()
|
||||
return self._full_dict.__setitem__(key, val)
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._ensure_instance()
|
||||
return self._full_dict.__delitem__(key)
|
||||
|
||||
def __iter__(self):
|
||||
self._ensure_instance()
|
||||
return iter(self._full_dict)
|
||||
|
||||
def __len__(self):
|
||||
self._ensure_instance()
|
||||
return len(self._full_dict)
|
||||
|
||||
def copy(self):
|
||||
self._ensure_instance()
|
||||
return self._full_dict.copy()
|
||||
|
||||
def clear(self):
|
||||
self._ensure_instance()
|
||||
return self._full_dict.clear()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class LineNumbersCalculator(object):
|
||||
r"""
|
||||
Utility to calculate line numbers.
|
||||
"""
|
||||
|
||||
def __init__(self, s):
|
||||
super(LineNumbersCalculator, self).__init__()
|
||||
|
||||
def find_all_new_lines(x):
|
||||
# first line starts at the beginning of the string
|
||||
yield 0
|
||||
k = 0
|
||||
while k < len(x):
|
||||
k = x.find("\n", k)
|
||||
if k == -1:
|
||||
return
|
||||
k += 1
|
||||
# s[k] is the character after the newline, i.e., the 0-th column
|
||||
# of the new line
|
||||
yield k
|
||||
|
||||
self._pos_new_lines = list(find_all_new_lines(s))
|
||||
|
||||
def pos_to_lineno_colno(self, pos, as_dict=False):
|
||||
r"""
|
||||
Return the line and column number corresponding to the given `pos`.
|
||||
|
||||
Return a tuple `(lineno, colno)` giving line number and column number.
|
||||
Line numbers start at 1 and column number start at zero, i.e., the
|
||||
beginning of the document (`pos=0`) has line and column number `(1,0)`.
|
||||
If `as_dict=True`, then a dictionary with keys 'lineno', 'colno' is
|
||||
returned instead of a tuple.
|
||||
"""
|
||||
|
||||
# find line number in list
|
||||
|
||||
# line_no is the index of the last item in self._pos_new_lines that is <= pos.
|
||||
line_no = bisect.bisect_right(self._pos_new_lines, pos) - 1
|
||||
assert line_no >= 0 and line_no < len(self._pos_new_lines)
|
||||
|
||||
col_no = pos - self._pos_new_lines[line_no]
|
||||
# 1+... so that line and column numbers start at 1
|
||||
if as_dict:
|
||||
return {"lineno": 1 + line_no, "colno": col_no}
|
||||
return (1 + line_no, col_no)
|
||||
1578
great_ai/utilities/external/pylatexenc/latex2text/__init__.py
vendored
Normal file
1578
great_ai/utilities/external/pylatexenc/latex2text/__init__.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
325
great_ai/utilities/external/pylatexenc/latex2text/__main__.py
vendored
Normal file
325
great_ai/utilities/external/pylatexenc/latex2text/__main__.py
vendored
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018 Philippe Faist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import fileinput
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .. import latexwalker
|
||||
from ..latex2text import LatexNodes2Text, _strict_latex_spaces_predef
|
||||
from ..version import version_str
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
parser = argparse.ArgumentParser(prog="latex2text", add_help=False)
|
||||
|
||||
codegroup = parser.add_argument_group("Input options")
|
||||
|
||||
codegroup.add_argument(
|
||||
"--code",
|
||||
"-c",
|
||||
action="store",
|
||||
default=None,
|
||||
metavar="LATEX_CODE",
|
||||
help="Convert the given LATEX_CODE to unicode text instead of reading "
|
||||
"from FILE or standard input. You cannot specify FILEs if you use this "
|
||||
"option, and any standard input is ignored.",
|
||||
)
|
||||
|
||||
codegroup.add_argument(
|
||||
"files",
|
||||
metavar="FILE",
|
||||
nargs="*",
|
||||
help="Input files to read LaTeX code from. If no FILE(s) is/are specified, "
|
||||
"LaTeX code is read from standard input unless --code is specified",
|
||||
)
|
||||
|
||||
group = parser.add_argument_group("LatexWalker options")
|
||||
|
||||
group.add_argument(
|
||||
"--parser-keep-inline-math",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="parser_keep_inline_math",
|
||||
default=None,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-parser-keep-inline-math",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="parser_keep_inline_math",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--tolerant-parsing",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="tolerant_parsing",
|
||||
default=True,
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-tolerant-parsing",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="tolerant_parsing",
|
||||
help="Tolerate syntax errors when parsing, and attempt to continue (default yes)",
|
||||
)
|
||||
|
||||
# I'm not sure this flag is useful and if it should be exposed at all.
|
||||
# Accept it, but make it hidden.
|
||||
parser.add_argument(
|
||||
"--strict-braces",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="strict_braces",
|
||||
default=False,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-strict-braces",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="strict_braces",
|
||||
# help="Report errors for mismatching LaTeX braces (default no)"
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
group = parser.add_argument_group("LatexNodes2Text options")
|
||||
|
||||
group.add_argument(
|
||||
"--text-keep-inline-math",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="text_keep_inline_math",
|
||||
default=None,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-text-keep-inline-math",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="text_keep_inline_math",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--math-mode",
|
||||
action="store",
|
||||
dest="math_mode",
|
||||
choices=["text", "with-delimiters", "verbatim", "remove"],
|
||||
default="text",
|
||||
help="How to handle chunks of math mode LaTeX code. 'text' = convert "
|
||||
"to text like the rest; 'with-delimiters' = same as 'text' but retain "
|
||||
"the original math mode delimiters; 'verbatim' = keep verbatim LaTeX code; "
|
||||
"'remove' = remove from input entirely",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--fill-text",
|
||||
dest="fill_text",
|
||||
action="store",
|
||||
nargs="?",
|
||||
default=-1,
|
||||
help="Attempt to wrap text to the given width, or 80 columns if option is "
|
||||
"specified with no argument",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--keep-comments",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="keep_comments",
|
||||
default=False,
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-keep-comments",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="keep_comments",
|
||||
help="Keep LaTeX comments in text output (default no)",
|
||||
)
|
||||
|
||||
class ListWithHiddenItems(list):
|
||||
def __init__(self, thelist, hiddenitems):
|
||||
super(ListWithHiddenItems, self).__init__(thelist)
|
||||
self.hiddenitems = hiddenitems
|
||||
|
||||
def __contains__(self, value):
|
||||
return (
|
||||
super(ListWithHiddenItems, self).__contains__(value)
|
||||
or value in self.hiddenitems
|
||||
)
|
||||
|
||||
strict_latex_spaces_choices = ListWithHiddenItems(
|
||||
# the list
|
||||
["off", "on"]
|
||||
+ list(k for k in _strict_latex_spaces_predef.keys() if k != "default"),
|
||||
# hidden items: Value is accepted, but not shown in list of choices
|
||||
["default"],
|
||||
)
|
||||
group.add_argument(
|
||||
"--strict-latex-spaces",
|
||||
choices=strict_latex_spaces_choices,
|
||||
dest="strict_latex_spaces",
|
||||
default="macros",
|
||||
help="How to handle whitespace. See documentation for the class "
|
||||
"LatexNodes2Text().",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--keep-braced-groups",
|
||||
action="store_const",
|
||||
const=True,
|
||||
dest="keep_braced_groups",
|
||||
default=False,
|
||||
)
|
||||
group.add_argument(
|
||||
"--no-keep-braced-groups",
|
||||
action="store_const",
|
||||
const=False,
|
||||
dest="keep_braced_groups",
|
||||
help="Keep LaTeX {braced groups} in text output (default no)",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--keep-braced-groups-minlen",
|
||||
type=int,
|
||||
default=2,
|
||||
dest="keep_braced_groups_minlen",
|
||||
help="Only apply --keep-braced-groups to groups that contain at least "
|
||||
"this many characters",
|
||||
)
|
||||
|
||||
group = parser.add_argument_group("General options")
|
||||
|
||||
group.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
dest="logging_level",
|
||||
action="store_const",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO,
|
||||
help="Suppress warning messages",
|
||||
)
|
||||
group.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
dest="logging_level",
|
||||
action="store_const",
|
||||
const=logging.DEBUG,
|
||||
help="Verbose output",
|
||||
)
|
||||
group.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="pylatexenc {}".format(version_str),
|
||||
help="Show version information and exit",
|
||||
)
|
||||
group.add_argument(
|
||||
"--help", action="help", help="Show this help information and exit"
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(args.logging_level)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if (
|
||||
args.parser_keep_inline_math is not None
|
||||
or args.text_keep_inline_math is not None
|
||||
):
|
||||
logger.warning(
|
||||
"Options --parser-keep-inline-math and --text-keep-inline-math are "
|
||||
"deprecated and no longer have any effect. Please use "
|
||||
"--math-mode=... instead."
|
||||
)
|
||||
|
||||
latex = ""
|
||||
if args.code:
|
||||
if args.files:
|
||||
logger.error(
|
||||
"Cannot specify both FILEs and --code option. "
|
||||
"Use --help option for more information."
|
||||
)
|
||||
sys.exit(1)
|
||||
latex = args.code
|
||||
else:
|
||||
for line in fileinput.input(files=args.files):
|
||||
latex += line
|
||||
|
||||
if args.fill_text != -1:
|
||||
if args.fill_text is not None and len(args.fill_text):
|
||||
fill_text = int(args.fill_text)
|
||||
else:
|
||||
fill_text = True
|
||||
else:
|
||||
fill_text = None
|
||||
|
||||
lw = latexwalker.LatexWalker(
|
||||
latex, tolerant_parsing=args.tolerant_parsing, strict_braces=args.strict_braces
|
||||
)
|
||||
|
||||
(nodelist, pos, len_) = lw.get_latex_nodes()
|
||||
|
||||
ln2t = LatexNodes2Text(
|
||||
math_mode=args.math_mode,
|
||||
keep_comments=args.keep_comments,
|
||||
strict_latex_spaces=args.strict_latex_spaces,
|
||||
keep_braced_groups=args.keep_braced_groups,
|
||||
keep_braced_groups_minlen=args.keep_braced_groups_minlen,
|
||||
fill_text=fill_text,
|
||||
)
|
||||
|
||||
print(ln2t.nodelist_to_text(nodelist))
|
||||
|
||||
|
||||
def run_main():
|
||||
|
||||
try:
|
||||
|
||||
main()
|
||||
|
||||
except SystemExit:
|
||||
raise
|
||||
except: # lgtm [py/catch-base-exception]
|
||||
import pdb
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
pdb.post_mortem()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
main()
|
||||
# run_main() # debug
|
||||
1784
great_ai/utilities/external/pylatexenc/latex2text/_defaultspecs.py
vendored
Normal file
1784
great_ai/utilities/external/pylatexenc/latex2text/_defaultspecs.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue