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 207 208 209 210 211 212 213 214 215 216
|
"""A set of util functions and variables for the PIUS suite."""
# vim:shiftwidth=4:tabstop=4:expandtab:textwidth=80:softtabstop=4:ai:
import os
import re
from copy import copy
from optparse import Option, OptionValueError
from libpius.constants import HOME
class PiusUtil:
DEBUG_ON = False
EXPAND_USER_OPTIONS = [
"--gpg-path",
"--keyring",
"--mail-text",
"--out-dir",
"--tmp-dir",
]
FALLBACK_PIUSRC_PATH = os.path.join(HOME, ".pius")
def debug(line):
"""Print a line, if debug is on, preceded with "DEBUG: "."""
if PiusUtil.DEBUG_ON:
print("DEBUG:", line)
def logcmd(cmd):
PiusUtil.debug("Running: %s" % " ".join(cmd))
def clean_files(flist):
"""Delete a list of files."""
for cfile in flist:
if os.path.exists(cfile):
os.unlink(cfile)
def statedir():
# if the base XDG_DATA_HOME exists, (not our directory within, but the
# top level directory, usually ~/.local/share), then we'll use that,
# otherwise fall back to our own pathing.
xdg_data = os.environ.get(
"XDG_DATA_HOME", os.path.join(HOME, ".local", "share")
)
if os.path.exists(xdg_data):
state_dir = os.path.join(xdg_data, "pius")
else:
state_dir = os.path.join(HOME, ".pius")
PiusUtil.debug("Data dir: %s" % state_dir)
return state_dir
def previous_statedirs():
return [os.path.join(HOME, ".pius")]
def configdir():
# if the base XDG_CONFIG_HOME exists, (not our directory within, but the
# top level directory, usually ~/.config), then we'll use that,
# otherwise fall back to our own pathing.
xdg_home = os.environ.get(
"XDG_CONFIG_HOME", os.path.join(HOME, ".config")
)
if os.path.exists(xdg_home):
return os.path.join(xdg_home, "pius")
return os.path.join(HOME, ".pius")
def dotfile_path():
# if the base XDG_CONFIG_HOME exists, (not our directory within, but the
# top level directory, usually ~/.config), then we'll use that,
# otherwise fall back to our own pathing.
return os.path.join(PiusUtil.configdir(), "piusrc")
def previous_dotfile_paths():
return [
os.path.join(PiusUtil.FALLBACK_PIUSRC_PATH, "piusrc"),
os.path.join(HOME, ".pius"),
]
def migrate_file(old, new):
print("WARNING: Migrating %s to %s" % (old, new))
d = os.path.dirname(new)
if not os.path.isdir(d):
os.mkdir(d, 0o750)
os.rename(old, new)
os.symlink(new, old)
def handle_path_migration(new_path, old_paths):
PiusUtil.debug("Migration to from %s -> %s" % (old_paths, new_path))
for path in old_paths:
# if we don't have XDG than one our desired RC *is* one of the old
# paths, so don't try to convert between the same file and itself
if path == new_path:
continue
# If this old path doesn't exist at all, cool
if not os.path.exists(path):
continue
# If the new file doesn't exist and the old file does, convert it.
if not os.path.exists(new_path) and os.path.exists(path):
PiusUtil.migrate_file(path, new_path)
# If the new file exists and the old file is a symlink, we're good
elif os.path.exists(new_path) and os.path.islink(path):
continue
# If they're both a file, warn
elif (
os.path.exists(new_path)
and os.path.exists(path)
and os.path.islink(path)
):
print(
"WARNING: Both %s and %s exist... ignoring %s"
% (new_path, path, path)
)
continue
def parse_dotfile(parser):
# People need a way to debug the parsing of the dotfile, which is before
# command-line parsing is done. So, of PIUS_DEBUG=1 in the env, we go
# ahead and turn debug on early.
if int(os.environ.get("PIUS_DEBUG", 0)) > 0:
PiusUtil.DEBUG_ON = True
sep = re.compile(r"(?:\s*=\s*|\s*:\s*\s+)")
piusrc = PiusUtil.dotfile_path()
PiusUtil.handle_path_migration(
piusrc, PiusUtil.previous_dotfile_paths()
)
# if we have a config file, parse it
opts = []
if os.path.isfile(piusrc):
fp = open(piusrc, "r")
for line in fp:
if line.startswith("#"):
continue
parts = sep.split(line.strip())
if not parts[0].startswith("--"):
parts[0] = "--%s" % parts[0]
if parts[0] in PiusUtil.EXPAND_USER_OPTIONS and len(parts) > 1:
parts[1] = os.path.expanduser(parts[1])
if parser.has_option(parts[0]):
opts.extend(parts)
else:
PiusUtil.debug(
"Line '%s' in %s is unknown, but that may be because "
"that option doesn't exist for this mode, so ignoring."
% (line.strip(), piusrc)
)
fp.close()
return opts
#
# Stupid fucking optparse will assume "-m -e" means "-e is the email address
# being passed to -m"... instead of "oh, -e is an option, -m is missing its
# required argument. This is an ugly hack around that.
#
def check_not_another_opt(_, opt, value):
"""Ensure argument to an option isn't another option."""
match = re.search(r"^\-", value)
if match:
raise OptionValueError(
"Option %s: Value %s looks like another option"
" instead of the required argument" % (opt, value)
)
return value
def check_email(_, opt, value):
"""Ensure argument seems like an email address."""
match = re.match(r".+@.+\..+", value)
if not match:
raise OptionValueError(
"Option %s: Value %s does not appear like a well"
" formed email address" % (opt, value)
)
return value
def check_display_name(_, opt, value):
"""Ensure argument is a valid email display name."""
match = re.search(r'[()<>[\]:;@\\,."]', value)
if match:
raise OptionValueError(
"Option %s: Value %s contains one or more illegal"
" characters" % (opt, value)
)
return value
def check_keyid(_, opt, value):
"""Ensure argument seems like a keyid."""
match = re.match(r"[0-9a-fA-Fx]", value)
if not match:
raise OptionValueError(
"Option %s: Value %s does not appear to be a KeyID" % (opt, value)
)
return value
class MyOption(Option):
"""Our own option class."""
TYPES = Option.TYPES + ("not_another_opt", "email", "display_name", "keyid")
TYPE_CHECKER = copy(Option.TYPE_CHECKER)
TYPE_CHECKER.update(
{
"not_another_opt": check_not_another_opt,
"email": check_email,
"display_name": check_display_name,
"keyid": check_keyid,
}
)
# END Stupid python optparse hack.
|