summaryrefslogtreecommitdiff
path: root/asciidoc/utils.py
blob: bca35cfc6d10434da187a06bbaa032511072fa65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import ast
import math
import os
import re
import time
from typing import List, Optional, Tuple, Union
import unicodedata


def userdir() -> Optional[str]:
    """
    Return user's home directory or None if it is not defined.
    """
    result = os.path.expanduser('~')
    if result == '~':
        result = None
    return result


def file_in(fname, directory) -> bool:
    """Return True if file fname resides inside directory."""
    assert os.path.isfile(fname)
    # Empty directory (not to be confused with None) is the current directory.
    if directory == '':
        directory = os.getcwd()
    else:
        assert os.path.isdir(directory)
        directory = os.path.realpath(directory)
    fname = os.path.realpath(fname)
    return os.path.commonprefix((directory, fname)) == directory


def assign(dst, src):
    """Assign all attributes from 'src' object to 'dst' object."""
    for a, v in list(src.__dict__.items()):
        setattr(dst, a, v)


def strip_quotes(s: str) -> str:
    """Trim white space and, if necessary, quote characters from s."""
    s = s.strip()
    # Strip quotation mark characters from quoted strings.
    if len(s) >= 3 and s[0] == '"' and s[-1] == '"':
        s = s[1:-1]
    return s


def is_re(s) -> bool:
    """Return True if s is a valid regular expression else return False."""
    try:
        re.compile(s)
        return True
    except BaseException:
        return False


def re_join(relist: List) -> List:
    """Join list of regular expressions re1,re2,... to single regular
    expression (re1)|(re2)|..."""
    if len(relist) == 0:
        return None
    result = []
    # Delete named groups to avoid ambiguity.
    for s in relist:
        result.append(re.sub(r'\?P<\S+?>', '', s))
    result = ')|('.join(result)
    result = '(' + result + ')'
    return result


def lstrip_list(s: Union[List, Tuple]) -> Union[List, Tuple]:
    """
    Return list with empty items from start of list removed.
    """
    for i in range(len(s)):
        if s[i]:
            break
    else:
        return []
    return s[i:]


def rstrip_list(s: Union[List, Tuple]) -> Union[List, Tuple]:
    """
    Return list with empty items from end of list removed.
    """
    for i in range(len(s) - 1, -1, -1):
        if s[i]:
            break
    else:
        return []
    return s[:i + 1]


def strip_list(s: Union[List, Tuple]) -> Union[List, Tuple]:
    """
    Return list with empty items from start and end of list removed.
    """
    s = lstrip_list(s)
    s = rstrip_list(s)
    return s


def is_array(obj) -> bool:
    """
    Return True if object is list or tuple type.
    """
    return isinstance(obj, list) or isinstance(obj, tuple)


def dovetail(lines1, lines2):
    """
    Append list or tuple of strings 'lines2' to list 'lines1'.  Join the last
    non-blank item in 'lines1' with the first non-blank item in 'lines2' into a
    single string.
    """
    assert is_array(lines1)
    assert is_array(lines2)
    lines1 = strip_list(lines1)
    lines2 = strip_list(lines2)
    if not lines1 or not lines2:
        return list(lines1) + list(lines2)
    result = list(lines1[:-1])
    result.append(lines1[-1] + lines2[0])
    result += list(lines2[1:])
    return result


def dovetail_tags(stag, content, etag):
    """Merge the end tag with the first content line and the last
    content line with the end tag. This ensures verbatim elements don't
    include extraneous opening and closing line breaks."""
    return dovetail(dovetail(stag, content), etag)


def py2round(n: Union[float, int], d: int = 0) -> int:
    """Utility function to get python2 rounding in python3. Python3 changed it such that
    given two equally close multiples, it'll round towards the even choice. For example,
    round(42.5) == 42 instead of the expected round(42.5) == 43). This function gives us
    back that functionality."""
    p = 10 ** d
    return float(math.floor((n * p) + math.copysign(0.5, n))) / p


east_asian_widths = {
    'W': 2,   # Wide
    'F': 2,   # Full-width (wide)
    'Na': 1,  # Narrow
    'H': 1,   # Half-width (narrow)
    'N': 1,   # Neutral (not East Asian, treated as narrow)
    'A': 1,   # Ambiguous (s/b wide in East Asian context, narrow otherwise, but that
              #   doesn't work)
}
"""Mapping of result codes from `unicodedata.east_asian_width()` to character
column widths."""


def column_width(s: str) -> int:
    width = 0
    for c in s:
        width += east_asian_widths[unicodedata.east_asian_width(c)]
    return width


def date_time_str(t: float) -> Tuple[str, str]:
    """Convert seconds since the Epoch to formatted local date and time strings."""
    source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
    if source_date_epoch is not None:
        t = time.gmtime(min(t, int(source_date_epoch)))
    else:
        t = time.localtime(t)
    date_str = time.strftime('%Y-%m-%d', t)
    time_str = time.strftime('%H:%M:%S', t)
    if source_date_epoch is not None:
        time_str += ' UTC'
    elif time.daylight and t.tm_isdst == 1:
        time_str += ' ' + time.tzname[1]
    else:
        time_str += ' ' + time.tzname[0]
    return date_str, time_str


def get_args(val):
    d = {}
    args = ast.parse("d(" + val + ")", mode='eval').body.args
    i = 1
    for arg in args:
        if isinstance(arg, ast.Name):
            d[str(i)] = ast.literal_eval(arg.id)
        else:
            d[str(i)] = ast.literal_eval(arg)
        i += 1
    return d


def get_kwargs(val):
    d = {}
    args = ast.parse("d(" + val + ")", mode='eval').body.keywords
    for arg in args:
        d[arg.arg] = ast.literal_eval(arg.value)
    return d


def parse_to_list(val):
    values = ast.parse("[" + val + "]", mode='eval').body.elts
    return [ast.literal_eval(v) for v in values]