forked from fabric/patchwork
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfiles.py
More file actions
142 lines (118 loc) · 4.69 KB
/
Copy pathfiles.py
File metadata and controls
142 lines (118 loc) · 4.69 KB
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
"""
Tools for file and directory management.
"""
import re
from invoke.vendor import six
from .util import set_runner
@set_runner
def directory(c, runner, path, user=None, group=None, mode=None):
"""
Ensure a directory exists and has given user and/or mode
:param c:
`~invoke.context.Context` within to execute commands.
:param str path:
File path to directory.
:param str user:
Username which should own the directory.
:param str group:
Group which should own the directory; defaults to ``user``.
:param str mode:
``chmod`` compatible mode string to apply to the directory.
"""
runner("mkdir -p {}".format(path))
if user is not None:
group = group or user
runner("chown {}:{} {}".format(user, group, path))
if mode is not None:
runner("chmod {} {}".format(mode, path))
@set_runner
def exists(c, runner, path):
"""
Return True if given path exists on the current remote host.
:param c:
`~invoke.context.Context` within to execute commands.
:param str path:
Path to check for existence.
"""
cmd = 'test -e "$(echo {})"'.format(path)
return runner(cmd, hide=True, warn=True).ok
@set_runner
def contains(c, runner, filename, text, exact=False, escape=True):
"""
Return True if ``filename`` contains ``text`` (which may be a regex.)
By default, this function will consider a partial line match (i.e. where
``text`` only makes up part of the line it's on). Specify ``exact=True`` to
change this behavior so that only a line containing exactly ``text``
results in a True return value.
This function leverages ``egrep`` on the remote end, so it may not follow
Python regular expression syntax perfectly.
If ``escape`` is False, no extra regular expression related escaping is
performed (this includes overriding ``exact`` so that no ``^``/``$`` is
added.)
:param c:
`~invoke.context.Context` within to execute commands.
:param str filename:
File path within which to check for ``text``.
:param str text:
Text to search for.
:param bool exact:
Whether to expect an exact match.
:param bool escape:
Whether to perform regex-oriented escaping on ``text``.
"""
if escape:
text = _escape_for_regex(text)
if exact:
text = "^{}$".format(text)
egrep_cmd = 'egrep "{}" "{}"'.format(text, filename)
return runner(egrep_cmd, hide=True, warn=True).ok
@set_runner
def append(c, runner, filename, text, partial=False, escape=True):
"""
Append string (or list of strings) ``text`` to ``filename``.
When a list is given, each string inside is handled independently (but in
the order given.)
If ``text`` is already found in ``filename``, the append is not run, and
None is returned immediately. Otherwise, the given text is appended to the
end of the given ``filename`` via e.g. ``echo '$text' >> $filename``.
The test for whether ``text`` already exists defaults to a full line match,
e.g. ``^<text>$``, as this seems to be the most sensible approach for the
"append lines to a file" use case. You may override this and force partial
searching (e.g. ``^<text>``) by specifying ``partial=True``.
Because ``text`` is single-quoted, single quotes will be transparently
backslash-escaped. This can be disabled with ``escape=False``.
:param c:
`~invoke.context.Context` within to execute commands.
:param str filename:
File path to append onto.
:param str text:
Text to append.
:param bool partial:
Whether to test partial line matches when determining if appending is
necessary.
:param bool escape:
Whether to perform regex-oriented escaping on ``text``.
"""
# Normalize non-list input to be a list
if isinstance(text, six.string_types):
text = [text]
for line in text:
regex = "^" + _escape_for_regex(line) + ("" if partial else "$")
if (
line
and exists(c, filename, runner=runner)
and contains(c, filename, regex, escape=False, runner=runner)
):
continue
line = line.replace("'", r"'\\''") if escape else line
runner("echo '{}' >> {}".format(line, filename))
def _escape_for_regex(text):
"""Escape ``text`` to allow literal matching using egrep"""
regex = re.escape(text)
# Seems like double escaping is needed for \
regex = regex.replace("\\\\", "\\\\\\")
# Triple-escaping seems to be required for $ signs
regex = regex.replace(r"\$", r"\\\$")
# Whereas single quotes should not be escaped
regex = regex.replace(r"\'", "'")
return regex