1#!/usr/bin/env python3
  2
  3"""Misc utility functions."""
  4
  5import os
  6import logging
  7from datetime import timedelta
  8import math
  9import sys
 10
 11# do not import any other chart modules here because this module is parsed very early
 12# in startup from settings.py
 13
 14
 15def ensure_dir_exists(dirname, umask=None):
 16    """Create `dirname` if it doesn't already exist."""
 17    if dirname is None:
 18        raise ValueError('ensure_dir_exists() called with no path')
 19
 20    if dirname.is_file():
 21        raise ValueError('{path} exists and is a file'.format(path=dirname))
 22
 23    if not dirname.is_dir():
 24        logging.info('Creating directory {name}'.format(name=dirname))
 25        if umask is not None:
 26            old_umask = os.umask(umask)
 27
 28        dirname.mkdir(parents=True)
 29
 30        if umask is not None:
 31            os.umask(old_umask)
 32
 33
 34def expand_filenames(sources):
 35    """For each Path in `sources` yield either the original value (if a file)
 36    or yield a list of children (if a directory) or throw an IOError (if not found)."""
 37    for source in sources:
 38        if source.is_file():
 39            yield source
 40
 41        elif source.is_dir():
 42            for child in source.iterdir():
 43                if child.is_file():
 44                    yield child
 45
 46        else:
 47            raise IOError('{source} cannot be read as a file or directory'.format(
 48                source=source))
 49
 50
 51def creatable(path):
 52    """Test if `path` either a) exists and if writeable or b) can be created."""
 53    # this implementation is not complete
 54    if path.exists():
 55        return True
 56
 57    if path.parent.is_dir():
 58        return True
 59
 60    return False
 61
 62
 63def timedelta_to_us(a):
 64    """Calculate total number of microseconds in a timedelta
 65
 66    >>> timedelta_to_us(timedelta(days=1))  # doctest: +ELLIPSIS
 67    86400000000...
 68
 69    This function is superflous now that timedelta has a built-in total_seconds()
 70    """
 71    res = (a.days * 86400 + a.seconds) * 1000000 + a.microseconds
 72    return res
 73
 74
 75def timedelta_div(a, b):
 76    """Divide one timedelta by either another timedelta or by a number.
 77
 78    >>> timedelta_div(timedelta(hours=3), timedelta(hours=2))
 79    1.5
 80    """
 81    if isinstance(b, timedelta):
 82        return float(timedelta_to_us(a)) / float(timedelta_to_us(b))
 83
 84    else:
 85        return timedelta(microseconds=timedelta_to_us(a) / b)
 86
 87
 88def timedelta_mul(a, b):
 89    """Multiply a timedelta by a floating point value
 90
 91    >>> timedelta_mul(timedelta(hours=1), 3.5)
 92    datetime.timedelta(seconds=12600)
 93    """
 94    res = float(timedelta_to_us(a)) * b
 95    return timedelta(microseconds=res)
 96
 97
 98def nvl(obj, value_if_none=''):
 99    """Return `obj` unmodified if it is not None, otherwise return `value_if_none`.
100    """
101    if obj is not None:
102        return obj
103
104    else:
105        return value_if_none
106
107
108def nvl_min(a, b):
109    """If `a` and `b` are both non-None, return min(a,b).
110
111    If either is None, return the other.
112    If both are None, return None.
113    """
114    if ((a is None or (isinstance(a, float) and math.isnan(a))) and
115        (b is None or (isinstance(b, float) and math.isnan(b)))):
116        return None
117
118    elif a is None or (isinstance(a, float) and math.isnan(a)):
119        return b
120
121    elif b is None or (isinstance(b, float) and math.isnan(b)):
122        return a
123
124    else:
125        return min(a, b)
126
127
128def nvl_max(a, b):
129    """If `a` and `b` are both non-None, return max(a,b).
130
131    If either is None, return the other.
132    If both are None, return None.
133    """
134    if ((a is None or (isinstance(a, float) and math.isnan(a))) and
135        (b is None or (isinstance(b, float) and math.isnan(b)))):
136        return None
137
138    elif a is None or (isinstance(a, float) and math.isnan(a)):
139        return b
140
141    elif b is None or (isinstance(b, float) and math.isnan(b)):
142        return a
143
144    else:
145        return max(a, b)
146
147
148def round_sf(val, sf=3):
149    """Round `val` to `sf` significant figures, returning result as a float.
150
151    >>> round_sf(12345, 3)
152    12300
153
154    >>> round_sf(0.0001546, 3)
155    0.000155
156    """
157    if val == 0.0 or math.isinf(val):
158        return val
159
160    # elif math.isinf(val):
161        # return
162
163    else:
164        return round(val, sf - int(math.floor(math.log10(abs(val)))) - 1)
165
166
167def fp_eq(a, b=0.0):
168    """Test for floating point near-equality."""
169    THRESHOLD = 0.000000000001
170    return abs(a - b) < THRESHOLD
171
172
173def dump_local_variables():
174    """Prints a list of all local variables in the calling scope."""
175    import types
176    print('Local variable dump:')
177    for k, v in sys._getframe(1).f_locals.items():
178        if k.startswith('__') or isinstance(v, (types.ModuleType, types.FunctionType)):
179            continue
180
181        print('{typ} {name} = {value}'.format(typ=type(k),
182                                              name=k,
183                                              value=v))
184
185
186def filter_dict(in_dict, keys):
187    """Return a filtered `in_dict` containing only `keys`."""
188    res = {}
189    for key in keys:
190        res[key] = in_dict[key]
191
192    return res
193
194
195def initial_matching(a, b):
196    """Return the number of initial matching items from `a` and `b`.
197
198    >>> initial_matching('farm one init', 'barm one init')
199    0
200    >>> initial_matching('farm one init', 'form one init')
201    1
202    >>> initial_matching('farm one init', 'farm bne init')
203    5
204    """
205    for i, (aa, bb) in enumerate(zip(a, b)):
206        if aa != bb:
207            return i
208
209    return i + 1
210
211
212def coalesce(*values):
213    """Return the first non-None parameter."""
214    for v in values:
215        if v is not None:
216            return v