Passed
Pull Request — develop (#458)
by
unknown
02:49
created

doorstop.common.copy_dir_contents()   A

Complexity

Conditions 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 19
rs 9.2833
c 0
b 0
f 0
cc 5
nop 2
1
"""Common exceptions, classes, and functions for Doorstop."""
2
3
import os
4
import shutil
5
import argparse
6
import logging
7
8
import yaml
9
10
verbosity = 0  # global verbosity setting for controlling string formatting
11
PRINT_VERBOSITY = 0  # minimum verbosity to using `print`
12
STR_VERBOSITY = 3  # minimum verbosity to use verbose `__str__`
13
MAX_VERBOSITY = 4  # maximum verbosity level implemented
14
15
16
def _trace(self, message, *args, **kws):  # pragma: no cover (manual test)
17
    """New logging level, TRACE."""
18
    if self.isEnabledFor(logging.DEBUG - 1):
19
        self._log(logging.DEBUG - 1, message, args, **kws)  # pylint: disable=W0212
20
21
    # add: pipe logging.info messages into a file called example.log
22
#logging.basicConfig(filename='example.log',level=logging.INFO)
23
    # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
24
25
26
logging.addLevelName(logging.DEBUG - 1, "TRACE")
27
logging.Logger.trace = _trace
28
29
logger = logging.getLogger
30
log = logger(__name__)
31
32
33
# exception classes ##########################################################
34
35
36
class DoorstopError(Exception):
37
38
    """Generic Doorstop error."""
39
40
41
class DoorstopWarning(DoorstopError, Warning):
42
43
    """Generic Doorstop warning."""
44
45
46
class DoorstopInfo(DoorstopWarning, Warning):
47
48
    """Generic Doorstop info."""
49
50
51
class HelpFormatter(argparse.HelpFormatter):
52
53
    """Command-line help text formatter with wider help text."""
54
55
    def __init__(self, *args, **kwargs):
56
        super().__init__(*args, max_help_position=40, **kwargs)
57
58
59
class WarningFormatter(logging.Formatter, object):
0 ignored issues
show
introduced by
Class 'WarningFormatter' inherits from object, can be safely removed from bases in python3
Loading history...
60
61
    """Logging formatter that displays verbose formatting for WARNING+."""
62
63
    def __init__(self, default_format, verbose_format, *args, **kwargs):
64
        super().__init__(*args, **kwargs)
65
        self.default_format = default_format
66
        self.verbose_format = verbose_format
67
68
    def format(self, record):  # pragma: no cover (manual test)
69
        """Python 3 hack to change the formatting style dynamically."""
70
        if record.levelno > logging.INFO:
71
            self._style._fmt = self.verbose_format  # pylint: disable=W0212
72
        else:
73
            self._style._fmt = self.default_format  # pylint: disable=W0212
74
        return super().format(record)
75
76
77
# disk helper functions ######################################################
78
79
80
def create_dirname(path):
81
    """Ensure a parent directory exists for a path."""
82
    dirpath = os.path.dirname(path)
83
    if dirpath and not os.path.isdir(dirpath):
84
        log.info("creating directory {}...".format(dirpath))
85
        os.makedirs(dirpath)
86
87
88
def read_lines(path, encoding='utf-8'):
89
    """Read lines of text from a file.
90
91
    :param path: file to write lines
92
    :param encoding: output file encoding
93
94
    :return: path of new file
95
96
    """
97
    log.trace("reading lines from '{}'...".format(path))
98
    with open(path, 'r', encoding=encoding) as stream:
99
        for line in stream:
100
            yield line
101
102
103
def read_text(path, encoding='utf-8'):
104
    """Read text from a file.
105
106
    :param path: file path to read from
107
    :param encoding: input file encoding
108
109
    :return: string
110
111
    """
112
    log.trace("reading text from '{}'...".format(path))
113
    with open(path, 'r', encoding=encoding) as stream:
114
        text = stream.read()
115
    return text
116
117
118
def load_yaml(text, path):
119
    """Parse a dictionary from YAML text.
120
121
    :param text: string containing dumped YAML data
122
    :param path: file path for error messages
123
124
    :return: dictionary
125
126
    """
127
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
128
    # Load the YAML data
129
    try:
130
        data = yaml.load(text) or {}
131
    except yaml.error.YAMLError as exc:  # pylint: disable=E1101
132
        msg = "invalid contents: {}:\n{}".format(path, exc)
133
        raise DoorstopError(msg) from None
134
    # Ensure data is a dictionary
135
    if not isinstance(data, dict):
136
        msg = "invalid contents: {}".format(path)
137
        raise DoorstopError(msg)
138
    # Return the parsed data
139
    return data
140
141
142
def write_lines(lines, path, end='\n', encoding='utf-8'):
143
    """Write lines of text to a file.
144
145
    :param lines: iterator of strings
146
    :param path: file to write lines
147
    :param end: string to end lines
148
    :param encoding: output file encoding
149
150
    :return: path of new file
151
152
    """
153
    log.trace("writing lines to '{}'...".format(path))
154
    with open(path, 'wb') as stream:
155
        for line in lines:
156
            # daten werden string für string in die vorgesehene datei geschrieben und sind mit Utf8 verschlüsselt
157
            data = (line + end).encode(encoding)
158
            stream.write(data)
159
    return path
160
161
162
def write_text(text, path, encoding='utf-8'):
163
    """Write text to a file.
164
165
    :param text: string
166
    :param path: file to write text
167
    :param encoding: output file encoding
168
169
    :return: path of new file
170
171
    """
172
    if text:
173
        log.trace("writing text to '{}'...".format(path))
174
    with open(path, 'wb') as stream:
175
        data = text.encode(encoding)
176
        stream.write(data)
177
    return path
178
179
180
def touch(path):  # pragma: no cover (integration test)
181
    """Ensure a file exists."""
182
    if not os.path.exists(path):
183
        log.trace("creating empty '{}'...".format(path))
184
        write_text('', path)
185
186
187
def delete(path):  # pragma: no cover (integration test)
188
    """Delete a file or directory with error handling."""
189
    if os.path.isdir(path):
190
        try:
191
            log.trace("deleting '{}'...".format(path))
192
            shutil.rmtree(path)
193
        except IOError:
194
            # bug: http://code.activestate.com/lists/python-list/159050
195
            msg = "unable to delete: {}".format(path)
196
            log.warning(msg)
197
    elif os.path.isfile(path):
198
        log.trace("deleting '{}'...".format(path))
199
        os.remove(path)
200