1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 14:31:46 +00:00
This commit is contained in:
deadc0de6
2022-08-25 23:21:11 +02:00
parent 348823eec7
commit 5b9d248219
14 changed files with 312 additions and 323 deletions

View File

@@ -36,13 +36,13 @@ class Cmd(DictParser):
try:
action = templater.generate_string(self.action)
except UndefinedException as exc:
err = 'undefined variable for {}: \"{}\"'.format(self.descr, exc)
err = f'undefined variable for {self.descr}: \"{exc}\"'
self.log.warn(err)
return False
if debug:
self.log.dbg('{}:'.format(self.descr))
self.log.dbg(' - raw \"{}\"'.format(self.action))
self.log.dbg(' - templated \"{}\"'.format(action))
self.log.dbg(f'{self.descr}:')
self.log.dbg(f' - raw \"{self.action}\"')
self.log.dbg(f' - templated \"{action}\"')
return action
def _get_args(self, templater):
@@ -69,36 +69,34 @@ class Cmd(DictParser):
if debug and args:
self.log.dbg('action args:')
for cnt, arg in enumerate(args):
self.log.dbg('\targs[{}]: {}'.format(cnt, arg))
self.log.dbg(f'\targs[{cnt}]: {arg}')
try:
cmd = action.format(*args)
except IndexError as exc:
err = 'index error for {}: \"{}\"'.format(self.descr, action)
err += ' with \"{}\"'.format(args)
err += ': {}'.format(exc)
err = 'findex error for {self.descr}: \"{action}\"'
err += f' with \"{args}\"'
err += f': {exc}'
self.log.warn(err)
return False
except KeyError as exc:
err = 'key error for {}: \"{}\": {}'.format(self.descr,
action,
exc)
err += ' with \"{}\"'.format(args)
err = f'key error for {self.descr}: \"{action}\": {exc}'
err += ' with \"{args}\"'
self.log.warn(err)
return False
if self.silent:
self.log.sub('executing silent action \"{}\"'.format(self.key))
self.log.sub(f'executing silent action \"{self.key}\"')
if debug:
self.log.dbg('action cmd silenced')
else:
if debug:
self.log.dbg('action cmd: \"{}\"'.format(cmd))
self.log.sub('executing \"{}\"'.format(cmd))
self.log.dbg(f'action cmd: \"{cmd}\"')
self.log.sub(f'executing \"{cmd}\"')
try:
ret = subprocess.call(cmd, shell=True)
except KeyboardInterrupt:
self.log.warn('{} interrupted'.format(self.descr))
self.log.warn('f{self.descr} interrupted')
if ret != 0:
self.log.warn('{} returned code {}'.format(self.descr, ret))
self.log.warn(f'{self.descr} returned code {ret}')
return ret == 0
@classmethod
@@ -106,7 +104,7 @@ class Cmd(DictParser):
return {'action': value}
def __str__(self):
return 'key:{} -> \"{}\"'.format(self.key, self.action)
return 'key:{self.key} -> \"{self.action}\"'
class Action(Cmd):
@@ -144,7 +142,7 @@ class Action(Cmd):
return out.format(self.key, self.kind, self.action)
def __repr__(self):
return 'action({})'.format(self.__str__())
return f'action({self.__str__()})'
class Transform(Cmd):

View File

@@ -221,7 +221,7 @@ class CfgAggregator:
"""create a new dotfile"""
# get a new dotfile with a unique key
key = self._get_new_dotfile_key(dst)
self.log.dbg('new dotfile key: {}'.format(key))
self.log.dbg(f'new dotfile key: {key}')
# add the dotfile
trans_r_key = trans_w_key = None
if trans_read:
@@ -298,7 +298,7 @@ class CfgAggregator:
self._patch_keys_to_objs([self.settings],
"default_actions", self._get_action_w_args)
msg = 'default actions: {}'.format(self.settings.default_actions)
msg = f'default actions: {self.settings.default_actions}'
self.log.dbg(msg)
# patch trans_w/trans_r in dotfiles
@@ -318,7 +318,7 @@ class CfgAggregator:
"""
if not containers:
return
self.log.dbg('patching {} ...'.format(keys))
self.log.dbg(f'patching {keys} ...')
for container in containers:
objects = []
okeys = getattr(container, keys)
@@ -329,8 +329,8 @@ class CfgAggregator:
for key in okeys:
obj = get_by_key(key)
if not obj:
err = '{} does not contain'.format(container)
err += ' a {} entry named {}'.format(keys, key)
err = f'{container} does not contain'
err += f' a {keys} entry named {key}'
self.log.err(err)
raise Exception(err)
objects.append(obj)

View File

@@ -144,15 +144,16 @@ class CfgYaml:
self.variables = {}
if not os.path.exists(self._path):
err = 'invalid config path: \"{}\"'.format(path)
err = f'invalid config path: \"{path}\"'
if self._debug:
self._dbg(err)
raise YamlException(err)
self._dbg('START of config parsing')
self._dbg('reloading: {}'.format(reloading))
self._dbg('profile: {}'.format(profile))
self._dbg('included profiles: {}'.format(','.join(self._inc_profiles)))
self._dbg(f'reloading: {reloading}')
self._dbg(f'profile: {profile}')
pfs = ','.join(self._inc_profiles)
self._dbg(f'included profiles: {pfs}')
self._yaml_dict = self._load_yaml(self._path)
# live patch deprecated entries
@@ -287,7 +288,7 @@ class CfgYaml:
# end of parsing
if self._debug:
self._dbg('########### {} ###########'.format('final config'))
self._dbg('########### final config ###########')
self._debug_entries()
self._dbg('END of config parsing')
@@ -300,10 +301,10 @@ class CfgYaml:
newlink = self._template_item(link)
# check link value
if newlink not in self.allowed_link_val:
err = 'bad link value: {}'.format(newlink)
err = f'bad link value: {newlink}'
self._log.err(err)
self._log.err('allowed: {}'.format(self.allowed_link_val))
raise YamlException('config content error: {}'.format(err))
self._log.err(f'allowed: {self.allowed_link_val}')
raise YamlException(f'config content error: {err}')
return newlink
def resolve_dotfile_src(self, src, templater=None):
@@ -314,7 +315,7 @@ class CfgYaml:
if templater:
new = templater.generate_string(src)
if new != src and self._debug:
msg = 'dotfile src: \"{}\" -> \"{}\"'.format(src, new)
msg = f'dotfile src: \"{src}\" -> \"{new}\"'
self._dbg(msg)
src = new
src = os.path.join(self.settings[self.key_settings_dotpath],
@@ -330,7 +331,7 @@ class CfgYaml:
if templater:
new = templater.generate_string(dst)
if new != dst and self._debug:
msg = 'dotfile dst: \"{}\" -> \"{}\"'.format(dst, new)
msg = f'dotfile dst: \"{dst}\" -> \"{new}\"'
self._dbg(msg)
dst = new
newdst = self._norm_path(dst)
@@ -361,8 +362,7 @@ class CfgYaml:
pro = self._yaml_dict[self.key_profiles][profile_key]
pro[self.key_profile_dotfiles].append(dotfile_key)
if self._debug:
msg = 'add \"{}\" to profile \"{}\"'.format(dotfile_key,
profile_key)
msg = f'add \"{dotfile_key}\" to profile \"{profile_key}\"'
msg.format(dotfile_key, profile_key)
self._dbg(msg)
self._dirty = True
@@ -383,9 +383,9 @@ class CfgYaml:
if old == chmod:
return False
if self._debug:
self._dbg('update dotfile: {}'.format(key))
self._dbg('old chmod value: {}'.format(old))
self._dbg('new chmod value: {}'.format(chmod))
self._dbg(f'update dotfile: {key}')
self._dbg(f'old chmod value: {old}')
self._dbg(f'new chmod value: {chmod}')
dotfile = self._yaml_dict[self.key_dotfiles][key]
if not chmod:
del dotfile[self.key_dotfile_chmod]
@@ -400,13 +400,13 @@ class CfgYaml:
if key in self.dotfiles.keys():
return False
if self._debug:
self._dbg('adding new dotfile: {}'.format(key))
self._dbg('new dotfile src: {}'.format(src))
self._dbg('new dotfile dst: {}'.format(dst))
self._dbg('new dotfile link: {}'.format(link))
self._dbg('new dotfile chmod: {}'.format(chmod))
self._dbg('new dotfile trans_r: {}'.format(trans_r_key))
self._dbg('new dotfile trans_w: {}'.format(trans_w_key))
self._dbg(f'adding new dotfile: {key}')
self._dbg(f'new dotfile src: {src}')
self._dbg(f'new dotfile dst: {dst}')
self._dbg(f'new dotfile link: {link}')
self._dbg(f'new dotfile chmod: {chmod}')
self._dbg(f'new dotfile trans_r: {trans_r_key}')
self._dbg(f'new dotfile trans_w: {trans_w_key}')
# create the dotfile dict
df_dict = {
@@ -430,7 +430,7 @@ class CfgYaml:
df_dict[self.key_trans_w] = str(trans_w_key)
if self._debug:
self._dbg('dotfile dict: {}'.format(df_dict))
self._dbg(f'dotfile dict: {df_dict}')
# add to global dict
self._yaml_dict[self.key_dotfiles][key] = df_dict
@@ -440,26 +440,26 @@ class CfgYaml:
def del_dotfile(self, key):
"""remove this dotfile from config"""
if key not in self._yaml_dict[self.key_dotfiles]:
self._log.err('key not in dotfiles: {}'.format(key))
self._log.err(f'key not in dotfiles: {key}')
return False
if self._debug:
self._dbg('remove dotfile: {}'.format(key))
self._dbg(f'remove dotfile: {key}')
del self._yaml_dict[self.key_dotfiles][key]
if self._debug:
dfs = self._yaml_dict[self.key_dotfiles]
self._dbg('new dotfiles: {}'.format(dfs))
self._dbg(f'new dotfiles: {dfs}')
self._dirty = True
return True
def del_dotfile_from_profile(self, df_key, pro_key):
"""remove this dotfile from that profile"""
if self._debug:
self._dbg('removing \"{}\" from \"{}\"'.format(df_key, pro_key))
self._dbg(f'removing \"{df_key}\" from \"{pro_key}\"')
if df_key not in self.dotfiles.keys():
self._log.err('key not in dotfiles: {}'.format(df_key))
self._log.err(f'key not in dotfiles: {df_key}')
return False
if pro_key not in self.profiles.keys():
self._log.err('key not in profile: {}'.format(pro_key))
self._log.err(f'key not in profile: {pro_key}')
return False
# get the profile dictionary
profile = self._yaml_dict[self.key_profiles][pro_key]
@@ -470,12 +470,12 @@ class CfgYaml:
return True
if self._debug:
dfs = profile[self.key_profile_dotfiles]
self._dbg('{} profile dotfiles: {}'.format(pro_key, dfs))
self._dbg('remove {} from profile {}'.format(df_key, pro_key))
self._dbg(f'{pro_key} profile dotfiles: {dfs}')
self._dbg(f'remove {df_key} from profile {pro_key}')
profile[self.key_profile_dotfiles].remove(df_key)
if self._debug:
dfs = profile[self.key_profile_dotfiles]
self._dbg('{} profile dotfiles: {}'.format(pro_key, dfs))
self._dbg(f'{pro_key} profile dotfiles: {dfs}')
self._dirty = True
return True
@@ -493,13 +493,13 @@ class CfgYaml:
# save to file
if self._debug:
self._dbg('saving to {}'.format(self._path))
self._dbg(f'saving to {self._path}')
try:
with open(self._path, 'w', encoding='utf8') as file:
self._yaml_dump(content, file, fmt=self._config_format)
except Exception as exc:
self._log.err(exc)
err = 'error saving config: {}'.format(self._path)
err = f'error saving config: {self._path}'
raise YamlException(err) from exc
if self._dirty_deprecated:
@@ -558,7 +558,7 @@ class CfgYaml:
keys = dotfiles.keys()
if len(keys) != len(list(set(keys))):
dups = [x for x in keys if x not in list(set(keys))]
err = 'duplicate dotfile keys found: {}'.format(dups)
err = f'duplicate dotfile keys found: {dups}'
raise YamlException(err)
dotfiles = self._norm_dotfiles(dotfiles)
@@ -645,7 +645,7 @@ class CfgYaml:
if name in current:
# ignore if already defined
if self._debug:
self._dbg('ignore uservariables {}'.format(name))
self._dbg(f'ignore uservariables {name}')
continue
content = userinput(prompt, debug=self._debug)
uvars[name] = content
@@ -764,24 +764,24 @@ class CfgYaml:
if self.key_dotfile_chmod in val:
value = str(val[self.key_dotfile_chmod])
if len(value) < 3:
err = 'bad format for chmod: {}'.format(value)
err = f'bad format for chmod: {value}'
self._log.err(err)
raise YamlException('config content error: {}'.format(err))
raise YamlException(f'config content error: {err}')
try:
int(value)
except Exception as exc:
err = 'bad format for chmod: {}'.format(value)
err = f'bad format for chmod: {value}'
self._log.err(err)
err = 'config content error: {}'.format(err)
err = f'config content error: {err}'
raise YamlException(err) from exc
# normalize chmod value
for chmodv in list(value):
chmodint = int(chmodv)
if chmodint < 0 or chmodint > 7:
err = 'bad format for chmod: {}'.format(value)
err = f'bad format for chmod: {value}'
self._log.err(err)
raise YamlException(
'config content error: {}'.format(err)
f'config content error: {err}'
)
val[self.key_dotfile_chmod] = int(value, 8)
@@ -877,7 +877,7 @@ class CfgYaml:
continue
if self.key_all in dfs:
if self._debug:
self._dbg('add ALL to profile \"{}\"'.format(k))
self._dbg(f'add ALL to profile \"{k}\"')
val[self.key_profile_dotfiles] = self.dotfiles.keys()
def _resolve_profile_includes(self):
@@ -903,17 +903,15 @@ class CfgYaml:
return dotfiles, actions
if self._debug:
self._dbg('{} includes {}'.format(profile, ','.join(includes)))
self._dbg('{} dotfiles before include: {}'.format(profile,
dotfiles))
self._dbg('{} actions before include: {}'.format(profile,
actions))
incs = ','.join(includes)
self._dbg(f'{profile} includes {incs}')
self._dbg(f'{profile} dotfiles before include: {dotfiles}')
self._dbg(f'{profile} actions before include: {actions}')
seen = []
for i in uniq_list(includes):
if self._debug:
self._dbg('resolving includes "{}" <- "{}"'
.format(profile, i))
self._dbg(f'resolving includes "{profile}" <- "{i}"')
# ensure no include loop occurs
if i in seen:
@@ -921,37 +919,36 @@ class CfgYaml:
seen.append(i)
# included profile even exists
if i not in self.profiles.keys():
self._log.warn('include unknown profile: {}'.format(i))
self._log.warn(f'include unknown profile: {i}')
continue
# recursive resolve
if self._debug:
self._dbg('recursively resolving includes for profile "{}"'
.format(i))
self._dbg(f'recursively resolving includes for profile "{i}"')
o_dfs, o_actions = self._rec_resolve_profile_include(i)
# merge dotfile keys
if self._debug:
self._dbg('Merging dotfiles {} <- {}: {} <- {}'
.format(profile, i, dotfiles, o_dfs))
msg = f'Merging dotfiles {profile}'
msg += f' <- {i}: {dotfiles} <- {o_dfs}'
self._dbg(msg)
dotfiles.extend(o_dfs)
this_profile[self.key_profile_dotfiles] = uniq_list(dotfiles)
# merge actions keys
if self._debug:
self._dbg('Merging actions {} <- {}: {} <- {}'
.format(profile, i, actions, o_actions))
actions.extend(o_actions)
msg = f'Merging actions {profile} '
msg += '<- {i}: {actions} <- {o_actions}'
self._dbg(msg)
actions.extend(o_actions)
this_profile[self.key_profile_actions] = uniq_list(actions)
dotfiles = this_profile.get(self.key_profile_dotfiles, [])
actions = this_profile.get(self.key_profile_actions, [])
if self._debug:
self._dbg('{} dotfiles after include: {}'.format(profile,
dotfiles))
self._dbg('{} actions after include: {}'.format(profile,
actions))
self._dbg(f'{profile} dotfiles after include: {dotfiles}')
self._dbg(f'{profile} actions after include: {actions}')
# since included items are resolved here
# we can clear these include
@@ -971,11 +968,11 @@ class CfgYaml:
newvars = {}
for path in paths:
if self._debug:
self._dbg('import variables from {}'.format(path))
self._dbg(f'import variables from {path}')
var = self._import_sub(path, self.key_variables,
mandatory=False)
if self._debug:
self._dbg('import dynvariables from {}'.format(path))
self._dbg(f'import dynvariables from {path}')
dvar = self._import_sub(path, self.key_dvariables,
mandatory=False)
@@ -997,7 +994,7 @@ class CfgYaml:
paths = self._resolve_paths(paths)
for path in paths:
if self._debug:
self._dbg('import actions from {}'.format(path))
self._dbg(f'import actions from {path}')
new = self._import_sub(path, self.key_actions,
mandatory=False,
patch_func=self._norm_actions)
@@ -1010,7 +1007,7 @@ class CfgYaml:
if not imp:
continue
if self._debug:
self._dbg('import dotfiles for profile {}'.format(k))
self._dbg(f'import dotfiles for profile {k}')
paths = self._resolve_paths(imp)
for path in paths:
current = val.get(self.key_dotfiles, [])
@@ -1021,9 +1018,9 @@ class CfgYaml:
def _import_config(self, path):
"""import config from path"""
if self._debug:
self._dbg('import config from {}'.format(path))
self._dbg('profile: {}'.format(self._profile))
self._dbg('included profiles: {}'.format(self._inc_profiles))
self._dbg(f'import config from {path}')
self._dbg(f'profile: {self._profile}')
self._dbg(f'included profiles: {self._inc_profiles}')
sub = CfgYaml(path, profile=self._profile,
addprofiles=self._inc_profiles,
debug=self._debug,
@@ -1065,8 +1062,7 @@ class CfgYaml:
paths = self._resolve_paths(imp)
for path in paths:
if path in self.imported_configs:
err = '{} imported more than once in {}'.format(path,
self._path)
err = f'{path} imported more than once in {self._path}'
raise YamlException(err)
self._import_config(path)
@@ -1076,19 +1072,19 @@ class CfgYaml:
patch_func is applied to each element if defined
"""
if self._debug:
self._dbg('import \"{}\" from \"{}\"'.format(key, path))
self._dbg(f'import \"{key}\" from \"{path}\"')
extdict = self._load_yaml(path)
new = self._get_entry(extdict, key, mandatory=mandatory)
if patch_func:
if self._debug:
self._dbg('calling patch: {}'.format(patch_func))
self._dbg(f'calling patch: {patch_func}')
new = patch_func(new)
if not new and mandatory:
err = 'no \"{}\" imported from \"{}\"'.format(key, path)
err = f'no \"{key}\" imported from \"{path}\"'
self._log.warn(err)
raise YamlException(err)
if self._debug:
self._dbg('imported \"{}\": {}'.format(key, new))
self._dbg(f'imported \"{key}\": {new}')
return new
########################################################
@@ -1106,7 +1102,7 @@ class CfgYaml:
self.key_profile_dotfiles: []
}
if self._debug:
self._dbg('adding new profile: {}'.format(key))
self._dbg(f'adding new profile: {key}')
self._dirty = True
########################################################
@@ -1159,7 +1155,7 @@ class CfgYaml:
self._dirty = True
self._dirty_deprecated = True
warn = 'deprecated \"link: <boolean>\"'
warn += ', updated to \"link: {}\"'.format(new)
warn += f', updated to \"link: {new}\"'
self._log.warn(warn)
if self.key_dotfile_link in dotfile and \
@@ -1171,7 +1167,7 @@ class CfgYaml:
self._dirty = True
self._dirty_deprecated = True
warn = 'deprecated \"link: link\"'
warn += ', updated to \"link: {}\"'.format(new)
warn += f', updated to \"link: {new}\"'
self._log.warn(warn)
if old_key in dotfile and \
@@ -1186,7 +1182,7 @@ class CfgYaml:
self._dirty = True
self._dirty_deprecated = True
warn = 'deprecated \"link_children\" value'
warn += ', updated to \"{}\"'.format(new)
warn += f', updated to \"{new}\"'
self._log.warn(warn)
########################################################
@@ -1209,22 +1205,22 @@ class CfgYaml:
"""load a yaml file to a dict"""
content = {}
if self._debug:
self._dbg('----------dump:{}----------'.format(path))
self._dbg(f'----------dump:{path}----------')
cfg = '\n'
with open(path, 'r', encoding='utf8') as file:
for line in file:
cfg += line
self._dbg(cfg.rstrip())
self._dbg('----------end:{}----------'.format(path))
self._dbg(f'----------end:{path}----------')
try:
content, fmt = self._yaml_load(path)
self._config_format = fmt
except Exception as exc:
self._log.err(exc)
err = 'config yaml error: {}'.format(path)
err = f'config yaml error: {path}'
raise YamlException(err) from exc
if self._debug:
self._dbg('format: {}'.format(self._config_format))
self._dbg(f'format: {self._config_format}')
return content
def _validate(self, yamldict):
@@ -1235,9 +1231,9 @@ class CfgYaml:
# check top entries
for entry in self.top_entries:
if entry not in yamldict:
err = 'no {} entry found'.format(entry)
err = f'no {entry} entry found'.format(entry)
self._log.err(err)
raise YamlException('config format error: {}'.format(err))
raise YamlException(f'config format error: {err}')
# check link_dotfile_default
if self.key_settings not in yamldict:
@@ -1253,10 +1249,10 @@ class CfgYaml:
return
val = settings[self.key_settings_link_dotfile_default]
if val not in self.allowed_link_val:
err = 'bad link value: {}'.format(val)
err = f'bad link value: {val}'
self._log.err(err)
self._log.err('allowed: {}'.format(self.allowed_link_val))
raise YamlException('config content error: {}'.format(err))
self._log.err(f'allowed: {self.allowed_link_val}')
raise YamlException(f'config content error: {err}')
@classmethod
def _yaml_load(cls, path):
@@ -1352,7 +1348,7 @@ class CfgYaml:
for entry in entries:
newe = self._template_item(entry)
if self._debug and entry != newe:
self._dbg('resolved: {} -> {}'.format(entry, newe))
self._dbg(f'resolved: {entry} -> {newe}')
new.append(newe)
return new
@@ -1364,7 +1360,7 @@ class CfgYaml:
for k, val in entries.items():
newv = self._template_item(val)
if self._debug and val != newv:
self._dbg('resolved: {} -> {}'.format(val, newv))
self._dbg(f'resolved: {val} -> {newv}')
new[k] = newv
return new
@@ -1486,7 +1482,7 @@ class CfgYaml:
def _parse_extended_import_path(self, path_entry):
"""Parse an import path in a tuple (path, fatal_not_found)."""
if self._debug:
self._dbg('parsing path entry {}'.format(path_entry))
self._dbg(f'parsing path entry {path_entry}')
path, _, attribute = path_entry.rpartition(self.key_import_sep)
fatal_not_found = attribute != self.key_import_ignore_key
@@ -1500,18 +1496,20 @@ class CfgYaml:
# str.rpartition
# In both cases, path_entry is the path we're looking for.
if self._debug:
self._dbg('using attribute default values for path {}'
.format(path_entry))
msg = 'using attribute default values'
msg += f' for path {path_entry}'
self._dbg(msg)
path = path_entry
fatal_not_found = self.key_import_fatal_not_found
elif self._debug:
self._dbg('path entry {} has fatal_not_found flag set to {}'
.format(path_entry, fatal_not_found))
msg = f'path entry {path_entry} has fatal_not_found'
msg += f' flag set to {fatal_not_found}'
self._dbg(msg)
return path, fatal_not_found
def _handle_non_existing_path(self, path, fatal_not_found=True):
"""Raise an exception or log a warning to handle non-existing paths."""
error = 'bad path {}'.format(path)
error = f'bad path {path}'
if fatal_not_found:
raise YamlException(error)
self._log.warn(error)
@@ -1520,7 +1518,7 @@ class CfgYaml:
"""Check if a path exists, raising if necessary."""
if os.path.exists(path):
if self._debug:
self._dbg('path {} exists'.format(path))
self._dbg(f'path {path} exists')
return path
self._handle_non_existing_path(path, fatal_not_found)
@@ -1542,7 +1540,7 @@ class CfgYaml:
paths = self._glob_path(path) if self._is_glob(path) else [path]
if not paths:
if self._debug:
self._dbg("glob path {} didn't expand".format(path))
self._dbg(f"glob path {path} didn't expand")
self._handle_non_existing_path(path, fatal_not_found)
return []
@@ -1598,7 +1596,7 @@ class CfgYaml:
"""return copy of entry from yaml dictionary"""
if key not in dic:
if mandatory:
err = 'invalid config: no entry \"{}\" found'.format(key)
err = f'invalid config: no entry \"{key}\" found'
raise YamlException(err)
dic[key] = {}
return deepcopy(dic[key])
@@ -1643,7 +1641,7 @@ class CfgYaml:
def _glob_path(self, path):
"""Expand a glob."""
if self._debug:
self._dbg('expanding glob {}'.format(path))
self._dbg(f'expanding glob {path}')
expanded_path = os.path.expanduser(path)
return glob.glob(expanded_path, recursive=True)
@@ -1661,7 +1659,7 @@ class CfgYaml:
path = ret
ret = os.path.normpath(path)
if self._debug and path != ret:
self._dbg('normalizing: {} -> {}'.format(path, ret))
self._dbg(f'normalizing: {path} -> {ret}')
return ret
def _shell_exec_dvars(self, dic, keys=None):
@@ -1672,11 +1670,11 @@ class CfgYaml:
val = dic[k]
ret, out = shellrun(val, debug=self._debug)
if not ret:
err = 'var \"{}: {}\" failed: {}'.format(k, val, out)
err = f'var \"{k}: {val}\" failed: {out}'
self._log.err(err)
raise YamlException(err)
if self._debug:
self._dbg('{}: `{}` -> {}'.format(k, val, out))
self._dbg(f'{k}: `{val}` -> {out}')
dic[k] = out
@classmethod
@@ -1687,7 +1685,7 @@ class CfgYaml:
cur = ([int(x) for x in VERSION.split('.')])
cfg = ([int(x) for x in minversion.split('.')])
except Exception as exc:
err = 'bad version: \"{}\" VS \"{}\"'.format(VERSION, minversion)
err = f'bad version: \"{VERSION}\" VS \"{minversion}\"'
raise YamlException(err) from exc
if cur < cfg:
err = 'current dotdrop version is too old for that config file.'
@@ -1711,21 +1709,21 @@ class CfgYaml:
"""pretty print dict"""
if not self._debug:
return
self._dbg('{}:'.format(title))
self._dbg(f'{title}:')
if not elems:
return
for k, val in elems.items():
if isinstance(val, dict):
self._dbg(' - \"{}\"'.format(k))
self._dbg(f' - \"{k}\"')
for subkey, sub in val.items():
self._dbg(' * {}: \"{}\"'.format(subkey, sub))
self._dbg(f' * {subkey}: \"{sub}\"')
else:
self._dbg(' - \"{}\": {}'.format(k, val))
self._dbg(f' - \"{k}\": {val}')
def _dbg(self, content):
directory = os.path.basename(os.path.dirname(self._path))
pre = os.path.join(directory, os.path.basename(self._path))
self._log.dbg('[{}] {}'.format(pre, content))
self._log.dbg(f'[{pre}] {content}')
def _save_uservariables(self, uvars):
"""save uservariables to file"""
@@ -1737,7 +1735,7 @@ class CfgYaml:
if cnt == 0:
name = self.save_uservariables_name.format('')
else:
name = self.save_uservariables_name.format('-{}'.format(cnt))
name = self.save_uservariables_name.format(f'-{cnt}')
cnt += 1
path = os.path.join(parent, name)
@@ -1748,12 +1746,12 @@ class CfgYaml:
content = {'variables': uvars}
try:
if self._debug:
self._dbg('saving uservariables values to {}'.format(path))
self._dbg(f'saving uservariables values to {path}')
with open(path, 'w', encoding='utf8') as file:
self._yaml_dump(content, file, fmt=self._config_format)
except Exception as exc:
# self._log.err(exc)
err = 'error saving uservariables to {}'.format(path)
err = f'error saving uservariables to {path}'
self._log.err(err)
raise YamlException(err) from exc
self._log.log('uservariables values saved to {}'.format(path))
self._log.log(f'uservariables values saved to {path}')

View File

@@ -38,33 +38,28 @@ class Comparator:
ignore = []
local_path = os.path.expanduser(local_path)
deployed_path = os.path.expanduser(deployed_path)
self.log.dbg('comparing {} and {}'.format(
local_path,
deployed_path,
))
self.log.dbg('ignore pattern(s): {}'.format(ignore))
self.log.dbg(f'comparing {local_path} and {deployed_path}')
self.log.dbg(f'ignore pattern(s): {ignore}')
# test type of file
if os.path.isdir(local_path) and not os.path.isdir(deployed_path):
return '\"{}\" is a dir while \"{}\" is a file\n'.format(
local_path,
deployed_path,
)
ret = f'\"{local_path}\" is a dir'
ret += f' while \"{deployed_path}\" is a file\n'
return ret
if not os.path.isdir(local_path) and os.path.isdir(deployed_path):
return '\"{}\" is a file while \"{}\" is a dir\n'.format(
local_path,
deployed_path,
)
ret = f'\"{local_path}\" is a file'
ret += f' while \"{deployed_path}\" is a dir\n'
return ret
# test content
if not os.path.isdir(local_path):
self.log.dbg('{} is a file'.format(local_path))
self.log.dbg(f'{local_path} is a file')
ret = self._comp_file(local_path, deployed_path, ignore)
if not ret:
ret = self._comp_mode(local_path, deployed_path, mode=mode)
return ret
self.log.dbg('{} is a directory'.format(local_path))
self.log.dbg(f'{local_path} is a directory')
ret = self._comp_dir(local_path, deployed_path, ignore)
if not ret:
@@ -82,79 +77,68 @@ class Comparator:
deployed_mode = get_file_perm(deployed_path)
if local_mode == deployed_mode:
return ''
msg = 'mode differ {} ({:o}) and {} ({:o})'
self.log.dbg(msg.format(local_path, local_mode, deployed_path,
deployed_mode))
ret = 'modes differ for {} ({:o}) vs {:o}\n'
return ret.format(deployed_path, deployed_mode, local_mode)
msg = f'mode differ {local_path} ({local_mode:o}) '
msg += f'and {deployed_path} ({deployed_mode:o})'
self.log.dbg(msg)
ret = f'modes differ for {deployed_path} '
ret += f'({deployed_mode:o}) vs {local_mode:o}\n'
return ret
def _comp_file(self, local_path, deployed_path, ignore):
"""compare a file"""
self.log.dbg('compare file {} with {}'.format(
local_path,
deployed_path,
))
self.log.dbg(f'compare file {local_path} with {deployed_path}')
if (self.ignore_missing_in_dotdrop and not
os.path.exists(local_path)) \
or must_ignore([local_path, deployed_path], ignore,
debug=self.debug):
self.log.dbg('ignoring diff {} and {}'.format(
local_path,
deployed_path,
))
self.log.dbg(f'ignoring diff {local_path} and {deployed_path}')
return ''
return self._diff(local_path, deployed_path)
def _comp_dir(self, local_path, deployed_path, ignore):
"""compare a directory"""
self.log.dbg('compare directory {} with {}'.format(
local_path,
deployed_path,
))
self.log.dbg(f'compare directory {local_path} with {deployed_path}')
if not os.path.exists(deployed_path):
return ''
if (self.ignore_missing_in_dotdrop and not
os.path.exists(local_path)) \
or must_ignore([local_path, deployed_path], ignore,
debug=self.debug):
self.log.dbg('ignoring diff {} and {}'.format(
local_path,
deployed_path,
))
self.log.dbg(f'ignoring diff {local_path} and {deployed_path}')
return ''
if not os.path.isdir(deployed_path):
return '\"{}\" is a file\n'.format(deployed_path)
return f'\"{deployed_path}\" is a file\n'
return self._compare_dirs(local_path, deployed_path, ignore)
def _compare_dirs(self, local_path, deployed_path, ignore):
"""compare directories"""
self.log.dbg('compare {} and {}'.format(local_path, deployed_path))
self.log.dbg(f'compare {local_path} and {deployed_path}')
ret = []
comp = filecmp.dircmp(local_path, deployed_path)
# handle files only in deployed dir
self.log.dbg('files only in deployed dir: {}'.format(comp.left_only))
self.log.dbg(f'files only in deployed dir: {comp.left_only}')
for i in comp.left_only:
if self.ignore_missing_in_dotdrop or \
must_ignore([os.path.join(local_path, i)],
ignore, debug=self.debug):
continue
ret.append('=> \"{}\" does not exist on destination\n'.format(i))
ret.append(f'=> \"{i}\" does not exist on destination\n')
# handle files only in dotpath dir
self.log.dbg('files only in dotpath dir: {}'.format(comp.right_only))
self.log.dbg(f'files only in dotpath dir: {comp.right_only}')
for i in comp.right_only:
if must_ignore([os.path.join(deployed_path, i)],
ignore, debug=self.debug):
continue
if not self.ignore_missing_in_dotdrop:
ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i))
ret.append(f'=> \"{i}\" does not exist in dotdrop\n')
# same local_path and deployed_path but different type
funny = comp.common_funny
self.log.dbg('files with different types: {}'.format(funny))
self.log.dbg(f'files with different types: {funny}')
for i in funny:
source_file = os.path.join(local_path, i)
deployed_file = os.path.join(deployed_path, i)
@@ -166,13 +150,13 @@ class Comparator:
continue
short = os.path.basename(source_file)
# file vs dir
ret.append('=> different type: \"{}\"\n'.format(short))
ret.append(f'=> different type: \"{short}\"\n')
# content is different
funny = comp.diff_files
funny.extend(comp.funny_files)
funny = uniq_list(funny)
self.log.dbg('files with different content: {}'.format(funny))
self.log.dbg(f'files with different content: {funny}')
for i in funny:
source_file = os.path.join(local_path, i)
deployed_file = os.path.join(deployed_path, i)
@@ -198,5 +182,5 @@ class Comparator:
diff_cmd=self.diff_cmd, debug=self.debug)
if header:
lshort = os.path.basename(local_path)
out = '=> diff \"{}\":\n{}'.format(lshort, out)
out = f'=> diff \"{lshort}\":\n{out}'
return out

View File

@@ -57,11 +57,10 @@ class Dotfile(DictParser):
if self.link != LinkTypes.NOLINK and \
(
(trans_r and len(trans_r) > 0)
or
(trans_r and len(trans_r) > 0) or
(trans_w and len(trans_w) > 0)
):
msg = '[{}] transformations disabled'.format(key)
msg = f'[{key}] transformations disabled'
msg += ' because dotfile is linked'
self.log.warn(msg)
self.trans_r = []
@@ -112,48 +111,48 @@ class Dotfile(DictParser):
return hash(self.dst) ^ hash(self.src) ^ hash(self.key)
def __str__(self):
msg = 'key:\"{}\"'.format(self.key)
msg += ', src:\"{}\"'.format(self.src)
msg += ', dst:\"{}\"'.format(self.dst)
msg += ', link:\"{}\"'.format(str(self.link))
msg += ', template:{}'.format(self.template)
msg = f'key:\"{self.key}\"'
msg += f', src:\"{self.src}\"'
msg += f', dst:\"{self.dst}\"'
msg += f', link:\"{self.link}\"'
msg += f', template:{self.template}'
if self.chmod:
msg += ', chmod:{:o}'.format(self.chmod)
msg += f', chmod:{self.chmod:o}'
return msg
def prt(self):
"""extended dotfile to str"""
indent = ' '
out = 'dotfile: \"{}\"'.format(self.key)
out += '\n{}src: \"{}\"'.format(indent, self.src)
out += '\n{}dst: \"{}\"'.format(indent, self.dst)
out += '\n{}link: \"{}\"'.format(indent, str(self.link))
out += '\n{}template: \"{}\"'.format(indent, str(self.template))
out = f'dotfile: \"{self.key}\"'
out += f'\n{indent}src: \"{self.src}\"'
out += f'\n{indent}dst: \"{self.dst}\"'
out += f'\n{indent}link: \"{self.link}\"'
out += f'\n{indent}template: \"{self.template}\"'
if self.chmod:
out += '\n{}chmod: \"{:o}\"'.format(indent, self.chmod)
out += f'\n{indent}chmod: \"{self.chmod:o}\"'
out += '\n{}pre-action:'.format(indent)
out += f'\n{indent}pre-action:'
some = self.get_pre_actions()
if some:
for act in some:
out += '\n{}- {}'.format(2 * indent, act)
out += f'\n{2*indent}- {act}'
out += '\n{}post-action:'.format(indent)
out += f'\n{indent}post-action:'
some = self.get_post_actions()
if some:
for act in some:
out += '\n{}- {}'.format(2 * indent, act)
out += f'\n{2*indent}- {act}'
out += '\n{}trans_r:'.format(indent)
out += f'\n{indent}trans_r:'
some = self.get_trans_r()
if some:
out += '\n{}- {}'.format(2 * indent, some)
out += f'\n{2*indent}- {some}'
out += '\n{}trans_w:'.format(indent)
out += f'\n{indent}trans_w:'
some = self.get_trans_w()
if some:
out += '\n{}- {}'.format(2 * indent, some)
out += f'\n{2*indent}- {some}'
return out
def __repr__(self):
return 'dotfile({!s})'.format(self)
return f'dotfile({self})'

View File

@@ -67,9 +67,9 @@ class Importer:
-1: error
"""
path = os.path.abspath(path)
self.log.dbg('import {}'.format(path))
self.log.dbg(f'import {path}'.format(path))
if not os.path.exists(path):
self.log.err('\"{}\" does not exist, ignored!'.format(path))
self.log.err(f'\"{path}\" does not exist, ignored!')
return -1
# check transw if any
@@ -110,8 +110,8 @@ class Importer:
if self.safe:
realdst = os.path.realpath(dst)
if dst != realdst:
msg = '\"{}\" is a symlink, dereference it and continue?'
if not self.log.ask(msg.format(dst)):
msg = f'\"{dst}\" is a symlink, dereference it and continue?'
if not self.log.ask(msg):
return 0
# create src path
@@ -122,7 +122,7 @@ class Importer:
src = src.rstrip(os.sep)
src = os.path.abspath(src)
src = strip_home(src)
self.log.dbg('import src for {} as {}'.format(dst, src))
self.log.dbg(f'import src for {dst} as {src}')
# with or without dot prefix
strip = '.' + os.sep
if self.keepdot:
@@ -136,13 +136,13 @@ class Importer:
linktype = import_link
if linktype == LinkTypes.LINK_CHILDREN and \
not os.path.isdir(path):
self.log.err('importing \"{}\" failed!'.format(path))
self.log.err(f'importing \"{path}\" failed!')
return -1
if self._already_exists(src, dst):
return -1
self.log.dbg('import dotfile: src:{} dst:{}'.format(src, dst))
self.log.dbg(f'import dotfile: src:{src} dst:{dst}')
if not self._import_file(src, dst, trans_write=trans_write):
return -1
@@ -165,7 +165,7 @@ class Importer:
# handle file mode
chmod = None
dflperm = get_default_file_perms(dst, self.umask)
self.log.dbg('import chmod: {}'.format(import_mode))
self.log.dbg(f'import chmod: {import_mode}')
if import_mode or perm != dflperm:
msg = 'adopt mode {:o} (umask {:o})'
self.log.dbg(msg.format(perm, dflperm))
@@ -176,10 +176,10 @@ class Importer:
trans_read=trans_r,
trans_write=trans_w)
if not retconf:
self.log.warn('\"{}\" ignored during import'.format(path))
self.log.warn(f'\"{path}\" ignored during import')
return 0
self.log.sub('\"{}\" imported'.format(path))
self.log.sub(f'\"{path}\" imported')
return 1
def _check_existing_dotfile(self, src, dst):
@@ -196,7 +196,7 @@ class Importer:
diff = cmp.compare(src, dst)
if diff != '':
# files are different, dunno what to do
self.log.log('diff \"{}\" VS \"{}\"'.format(dst, src))
self.log.log(f'diff \"{dst}\" VS \"{src}\"')
self.log.emph(diff)
# ask user
msg = 'Dotfile \"{}\" already exists, overwrite?'
@@ -225,18 +225,18 @@ class Importer:
# create directory hierarchy
if self.dry:
cmd = 'mkdir -p {}'.format(srcfd)
self.log.dry('would run: {}'.format(cmd))
cmd = f'mkdir -p {srcfd}'
self.log.dry(f'would run: {cmd}')
else:
try:
os.makedirs(srcfd, exist_ok=True)
except OSError:
self.log.err('importing \"{}\" failed!'.format(dst))
self.log.err(f'importing \"{dst}\" failed!')
return False
# import the file
if self.dry:
self.log.dry('would copy {} to {}'.format(dst, srcf))
self.log.dry(f'would copy {dst} to {srcf}')
else:
# apply trans_w
dst = self._apply_trans_w(dst, trans_write)
@@ -257,7 +257,7 @@ class Importer:
except shutil.Error as exc:
src = exc.args[0][0][0]
why = exc.args[0][0][2]
self.log.err('importing \"{}\" failed: {}'.format(src, why))
self.log.err(f'importing \"{src}\" failed: {why}')
return True
@@ -290,8 +290,8 @@ class Importer:
def _ignore(self, path):
if must_ignore([path], self.ignore, debug=self.debug):
self.log.dbg('ignoring import of {}'.format(path))
self.log.warn('{} ignored'.format(path))
self.log.dbg(f'ignoring import of {path}')
self.log.warn(f'{path} ignored')
return True
return False
@@ -305,12 +305,12 @@ class Importer:
"""
if not trans:
return path
self.log.dbg('executing write transformation {}'.format(trans))
self.log.dbg(f'executing write transformation {trans}')
tmp = get_unique_tmp_name()
if not trans.transform(path, tmp, debug=self.debug,
templater=self.templater):
msg = 'transformation \"{}\" failed for {}'
self.log.err(msg.format(trans.key, path))
msg = f'transformation \"{trans.key}\" failed for {path}'
self.log.err(msg)
if os.path.exists(tmp):
removepath(tmp, logger=self.log)
return None

View File

@@ -27,7 +27,7 @@ class LinkTypes(IntEnum):
except KeyError as exc:
if default:
return default
err = 'bad {} value: "{}"'.format(cls.__name__, key)
err = f'bad {cls.__name__} value: "{key}"'
raise ValueError(err) from exc
def __str__(self):

View File

@@ -31,24 +31,23 @@ class Logger:
cend = self._color(self.RESET)
if bold:
bold = self._color(self.BOLD)
fmt = '{}{}{}{}{}'.format(pre, cstart, bold,
string, cend)
fmt += '{}{}'.format(end, cend)
fmt = f'{pre}{cstart}{bold}{string}{cend}'
fmt += f'{end}{cend}'
else:
fmt = '{}{}{}{}{}'.format(pre, cstart, string, end, cend)
fmt = f'{pre}{cstart}{string}{end}{cend}'
sys.stdout.write(fmt)
def sub(self, string, end='\n'):
"""sub log"""
cstart = self._color(self.BLUE)
cend = self._color(self.RESET)
sys.stdout.write('\t{}->{} {}{}'.format(cstart, cend, string, end))
sys.stdout.write(f'\t{cstart}->{cend} {string}{end}')
def emph(self, string, stdout=True):
"""emphasis log"""
cstart = self._color(self.EMPH)
cend = self._color(self.RESET)
content = '{}{}{}'.format(cstart, string, cend)
content = f'{cstart}{string}{cend}'
if not stdout:
sys.stderr.write(content)
else:
@@ -58,14 +57,14 @@ class Logger:
"""error log"""
cstart = self._color(self.RED)
cend = self._color(self.RESET)
msg = '{} {}'.format(string, end)
sys.stderr.write('{}[ERR] {}{}'.format(cstart, msg, cend))
msg = f'{string} {end}'.format(string, end)
sys.stderr.write(f'{cstart}[ERR] {msg}{cend}')
def warn(self, string, end='\n'):
"""warning log"""
cstart = self._color(self.YELLOW)
cend = self._color(self.RESET)
sys.stderr.write('{}[WARN] {} {}{}'.format(cstart, string, end, cend))
sys.stderr.write(f'{cstart}[WARN] {string} {end}{cend}')
def dbg(self, string, force=False):
"""debug log"""
@@ -78,29 +77,28 @@ class Logger:
cend = self._color(self.RESET)
clight = self._color(self.LMAGENTA)
bold = self._color(self.BOLD)
line = '{}{}[DEBUG][{}.{}]{}{} {}{}\n'
sys.stderr.write(line.format(bold, clight,
mod, func,
cend, cstart,
string, cend))
line = f'{bold}{clight}[DEBUG][{mod}.{func}]'
line += f'{cend}{cstart} {string}{cend}\n'
sys.stderr.write(line)
def dry(self, string, end='\n'):
"""dry run log"""
cstart = self._color(self.GREEN)
cend = self._color(self.RESET)
sys.stdout.write('{}[DRY] {} {}{}'.format(cstart, string, end, cend))
sys.stdout.write(f'{cstart}[DRY] {string} {end}{cend}')
@classmethod
def raw(cls, string, end='\n'):
"""raw log"""
sys.stdout.write('{}{}'.format(string, end))
sys.stdout.write(f'{string}{end}')
def ask(self, query):
"""ask user for confirmation"""
cstart = self._color(self.BLUE)
cend = self._color(self.RESET)
query = '{}{}{}'.format(cstart, query + ' [y/N] ? ', cend)
resp = input(query)
question = query + ' [y/N] ? '
qmsg = f'{cstart}{question}{cend}'
resp = input(qmsg)
return resp == 'y'
@classmethod

View File

@@ -38,9 +38,9 @@ if ENV_PROFILE in os.environ:
NAME = 'dotdrop'
CONFIGFILEYAML = 'config.yaml'
CONFIGFILETOML = 'config.toml'
HOMECFG = '~/.config/{}'.format(NAME)
ETCXDGCFG = '/etc/xdg/{}'.format(NAME)
ETCCFG = '/etc/{}'.format(NAME)
HOMECFG = f'~/.config/{NAME}'
ETCXDGCFG = f'/etc/xdg/{NAME}'
ETCCFG = f'/etc/{NAME}'
OPT_LINK = {
LinkTypes.NOLINK.name.lower(): LinkTypes.NOLINK,
@@ -51,11 +51,11 @@ OPT_LINK = {
BANNER = r""" _ _ _
__| | ___ | |_ __| |_ __ ___ _ __
/ _` |/ _ \| __/ _` | '__/ _ \| '_ |
\__,_|\___/ \__\__,_|_| \___/| .__/ v{}
|_|""".format(VERSION)
\__,_|\___/ \__\__,_|_| \___/| .__/ v{VERSION}
|_|"""
USAGE = """
{}
USAGE = f"""
{BANNER}
Usage:
dotdrop install [-VbtfndDaW] [-c <path>] [-p <profile>]
@@ -89,7 +89,7 @@ Options:
-L --file-only Do not show diff but only the files that differ.
-m --preserve-mode Insert a chmod entry in the dotfile with its mode.
-n --nodiff Do not diff when installing.
-p --profile=<profile> Specify the profile to use [default: {}].
-p --profile=<profile> Specify the profile to use [default: {PROFILE}].
-P --show-patch Provide a one-liner to manually patch template.
-s --as=<path> Import as a different path from actual path.
--transr=<key> Associate trans_read key on import.
@@ -102,7 +102,7 @@ Options:
-z --ignore-missing Ignore files in installed folders that are missing.
-v --version Show version.
-h --help Show this screen.
""".format(BANNER, PROFILE)
"""
class AttrMonitor:
@@ -159,14 +159,15 @@ class Options(AttrMonitor):
if not self.confpath:
raise YamlException('no config file found')
if not os.path.exists(self.confpath):
err = 'bad config file path: {}'.format(self.confpath)
err = f'bad config file path: {self.confpath}'
raise YamlException(err)
self.log.dbg('#################################################')
self.log.dbg('#################### DOTDROP ####################')
self.log.dbg('#################################################')
self.log.dbg('version: {}'.format(VERSION))
self.log.dbg('command: {}'.format(' '.join(sys.argv)))
self.log.dbg('config file: {}'.format(self.confpath))
self.log.dbg(f'version: {VERSION}')
args = ' '.join(sys.argv)
self.log.dbg(f'command: {args}')
self.log.dbg(f'config file: {self.confpath}')
self._read_config()
self._apply_args()
@@ -290,7 +291,7 @@ class Options(AttrMonitor):
self.compare_focus = self.args['--file']
self.compare_ignore = self.args['--ignore']
self.compare_ignore.extend(self.cmpignore)
self.compare_ignore.append('*{}'.format(self.install_backup_suffix))
self.compare_ignore.append(f'*{self.install_backup_suffix}')
self.compare_ignore = uniq_list(self.compare_ignore)
self.compare_fileonly = self.args['--file-only']
self.ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \
@@ -303,7 +304,7 @@ class Options(AttrMonitor):
self.import_mode = self.args['--preserve-mode'] or self.chmod_on_import
self.import_ignore = self.args['--ignore']
self.import_ignore.extend(self.impignore)
self.import_ignore.append('*{}'.format(self.install_backup_suffix))
self.import_ignore.append(f'*{self.install_backup_suffix}')
self.import_ignore = uniq_list(self.import_ignore)
self.import_transw = self.args['--transw']
self.import_transr = self.args['--transr']
@@ -314,7 +315,7 @@ class Options(AttrMonitor):
self.update_iskey = self.args['--key']
self.update_ignore = self.args['--ignore']
self.update_ignore.extend(self.upignore)
self.update_ignore.append('*{}'.format(self.install_backup_suffix))
self.update_ignore.append(f'*{self.install_backup_suffix}')
self.update_ignore = uniq_list(self.update_ignore)
self.update_showpatch = self.args['--show-patch']
@@ -362,7 +363,7 @@ class Options(AttrMonitor):
# overwrite default import link with cli switch
link = self.args['--link']
if link not in OPT_LINK:
self.log.err('bad option for --link: {}'.format(link))
self.log.err(f'bad option for --link: {link}')
sys.exit(USAGE)
self.import_link = OPT_LINK[link]
@@ -411,12 +412,12 @@ class Options(AttrMonitor):
if callable(val):
continue
if isinstance(val, list):
debug_list('-> {}'.format(att), val, self.debug)
debug_list(f'-> {att}', val, self.debug)
elif isinstance(val, dict):
debug_dict('-> {}'.format(att), val, self.debug)
debug_dict(f'-> {att}', val, self.debug)
else:
self.log.dbg('-> {}: {}'.format(att, val))
self.log.dbg(f'-> {att}: {val}')
def _attr_set(self, attr):
"""error when some inexistent attr is set"""
raise Exception('bad option: {}'.format(attr))
raise Exception(f'bad option: {attr}')

View File

@@ -55,8 +55,7 @@ class Profile(DictParser):
hash(tuple(self.dotfiles)))
def __str__(self):
msg = 'key:"{}"'
return msg.format(self.key)
return f'key:"{self.key}"'
def __repr__(self):
return 'profile({!s})'.format(self)
return f'profile({self})'

View File

@@ -115,7 +115,7 @@ class Settings(DictParser):
# check diff command
if not is_bin_in_path(self.diff_command):
err = 'bad diff_command: {}'.format(self.diff_command)
err = f'bad diff_command: {diff_command}'
raise YamlException(err)
def _serialize_seq(self, name, dic):

View File

@@ -73,11 +73,11 @@ class Templategen:
self._load_funcs_to_dic(jhelpers, self.env.globals)
if func_file:
for ffile in func_file:
self.log.dbg('load custom functions from {}'.format(ffile))
self.log.dbg(f'load custom functions from {ffile}')
self._load_path_to_dic(ffile, self.env.globals)
if filter_file:
for ffile in filter_file:
self.log.dbg('load custom filters from {}'.format(ffile))
self.log.dbg(f'load custom filters from {ffile}')
self._load_path_to_dic(ffile, self.env.filters)
if self.debug:
self._debug_dict('template additional variables', variables)
@@ -93,7 +93,7 @@ class Templategen:
try:
return self._handle_file(src)
except UndefinedError as exc:
err = 'undefined variable: {}'.format(exc.message)
err = f'undefined variable: {exc.message}'
raise UndefinedException(err) from exc
def generate_string(self, string):
@@ -107,7 +107,7 @@ class Templategen:
try:
return self.env.from_string(string).render(self.variables)
except UndefinedError as exc:
err = 'undefined variable: {}'.format(exc.message)
err = 'undefined variable: {exc.message}'
raise UndefinedException(err) from exc
def add_tmp_vars(self, newvars=None):
@@ -129,7 +129,7 @@ class Templategen:
def _load_path_to_dic(self, path, dic):
mod = utils.get_module_from_path(path)
if not mod:
self.log.warn('cannot load module \"{}\"'.format(path))
self.log.warn(f'cannot load module \"{path}\"')
return
self._load_funcs_to_dic(mod, dic)
@@ -139,13 +139,13 @@ class Templategen:
return
funcs = utils.get_module_functions(mod)
for name, func in funcs:
self.log.dbg('load function \"{}\"'.format(name))
self.log.dbg(f'load function \"{name}\"')
dic[name] = func
@classmethod
def _header(cls, prepend=''):
"""add a comment usually in the header of a dotfile"""
return '{}{}'.format(prepend, utils.header())
return f'{prepend}{utils.header()}'
def _handle_file(self, src):
"""generate the file content from template"""
@@ -161,8 +161,8 @@ class Templategen:
self.log.dbg('using \"file\" for filetype identification')
filetype = filetype.strip()
istext = self._is_text(filetype)
self.log.dbg('filetype \"{}\": {}'.format(src, filetype))
self.log.dbg('is text \"{}\": {}'.format(src, istext))
self.log.dbg(f'filetype \"{src}\": {filetype}')
self.log.dbg(f'is text \"{src}\": {istext}')
if not istext:
return self._handle_bin_file(src)
return self._handle_text_file(src)
@@ -220,7 +220,7 @@ class Templategen:
def is_template(path, ignore=None, debug=False):
"""recursively check if any file is a template within path"""
if debug:
LOG.dbg('is template: {}'.format(path), force=True)
LOG.dbg(f'is template: {path}', force=True)
path = os.path.expanduser(path)
if not os.path.exists(path):
@@ -280,8 +280,8 @@ class Templategen:
"""pretty print dict"""
if not self.debug:
return
self.log.dbg('{}:'.format(title))
self.log.dbg(f'{title}:')
if not elems:
return
for k, val in elems.items():
self.log.dbg(' - \"{}\": {}'.format(k, val))
self.log.dbg(f' - \"{k}\": {val}')

View File

@@ -42,11 +42,13 @@ NOREMOVE = [os.path.normpath(p) for p in DONOTDELETE]
def run(cmd, debug=False):
"""run a command (expects a list)"""
if debug:
LOG.dbg('exec: {}'.format(' '.join(cmd)), force=True)
proc = subprocess.Popen(cmd, shell=False,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, _ = proc.communicate()
ret = proc.returncode
fcmd = ' '.join(cmd)
LOG.dbg(f'exec: {fcmd}', force=True)
with subprocess.Popen(cmd, shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT) as proc:
out, _ = proc.communicate()
ret = proc.returncode
out = out.splitlines(keepends=True)
lines = ''.join([x.decode('utf-8', 'replace') for x in out])
return ret == 0, lines
@@ -66,10 +68,10 @@ def shellrun(cmd, debug=False):
returns True|False, output
"""
if debug:
LOG.dbg('shell exec: \"{}\"'.format(cmd), force=True)
LOG.dbg(f'shell exec: \"{cmd}\"', force=True)
ret, out = subprocess.getstatusoutput(cmd)
if debug:
LOG.dbg('shell result ({}): {}'.format(ret, out), force=True)
LOG.dbg(f'shell result ({ret}): {out}', force=True)
return ret == 0, out
@@ -79,11 +81,11 @@ def userinput(prompt, debug=False):
return user input
"""
if debug:
LOG.dbg('get user input for \"{}\"'.format(prompt), force=True)
pre = 'Please provide the value for \"{}\": '.format(prompt)
LOG.dbg(f'get user input for \"{prompt}\"', force=True)
pre = f'Please provide the value for \"{prompt}\": '
res = input(pre)
if debug:
LOG.dbg('user input result: {}'.format(res), force=True)
LOG.dbg(f'user input result: {res}', force=True)
return res
@@ -160,13 +162,13 @@ def removepath(path, logger=None):
if not path:
return
if not os.path.lexists(path):
err = 'File not found: {}'.format(path)
err = f'File not found: {path}'
if logger:
logger.warn(err)
return
raise OSError(err)
if os.path.normpath(os.path.expanduser(path)) in NOREMOVE:
err = 'Dotdrop refuses to remove {}'.format(path)
err = f'Dotdrop refuses to remove {path}'
if logger:
logger.warn(err)
return
@@ -178,7 +180,7 @@ def removepath(path, logger=None):
elif os.path.isdir(path):
shutil.rmtree(path)
else:
err = 'Unsupported file type for deletion: {}'.format(path)
err = f'Unsupported file type for deletion: {path}'
raise OSError(err)
except Exception as exc:
err = str(exc)
@@ -226,7 +228,7 @@ def must_ignore(paths, ignores, debug=False):
if not ignores:
return False
if debug:
LOG.dbg('must ignore? \"{}\" against {}'.format(paths, ignores),
LOG.dbg(f'must ignore? \"{paths}\" against {ignores}',
force=True)
ignored_negative, ignored = categorize(
lambda ign: ign.startswith('!'), ignores)
@@ -236,7 +238,7 @@ def must_ignore(paths, ignores, debug=False):
for i in ignored:
if fnmatch.fnmatch(path, i):
if debug:
LOG.dbg('ignore \"{}\" match: {}'.format(i, path),
LOG.dbg(f'ignore \"{i}\" match: {path}',
force=True)
ignore_matches.append(path)
@@ -249,24 +251,24 @@ def must_ignore(paths, ignores, debug=False):
LOG.dbg(msg.format(path, nign), force=True)
if fnmatch.fnmatch(path, nign):
if debug:
msg = 'negative ignore \"{}\" match: {}'.format(nign, path)
msg = f'negative ignore \"{nign}\" match: {path}'
LOG.dbg(msg, force=True)
try:
ignore_matches.remove(path)
except ValueError:
LOG.warn('no files that are currently being '
'ignored match \"{}\". In order '
'for a negative ignore pattern '
'to work, it must match a file '
'that is being ignored by a '
'previous ignore pattern.'.format(nign)
)
warn = 'no files that are currently being '
warn += f'ignored match \"{nign}\". In order '
warn += 'for a negative ignore pattern '
warn += 'to work, it must match a file '
warn += 'that is being ignored by a '
warn += 'previous ignore pattern.'
LOG.warn(warn)
if ignore_matches:
if debug:
LOG.dbg('ignoring {}'.format(paths), force=True)
LOG.dbg(f'ignoring {paths}', force=True)
return True
if debug:
LOG.dbg('NOT ignoring {}'.format(paths), force=True)
LOG.dbg(f'NOT ignoring {paths}', force=True)
return False
@@ -284,7 +286,7 @@ def uniq_list(a_list):
def patch_ignores(ignores, prefix, debug=False):
"""allow relative ignore pattern"""
new = []
LOG.dbg('ignores before patching: {}'.format(ignores), force=debug)
LOG.dbg(f'ignores before patching: {ignores}', force=debug)
for ignore in ignores:
negative = ignore.startswith('!')
if negative:
@@ -311,7 +313,7 @@ def patch_ignores(ignores, prefix, debug=False):
new.append('!' + path)
else:
new.append(path)
LOG.dbg('ignores after patching: {}'.format(new), force=debug)
LOG.dbg(f'ignores after patching: {new}', force=debug)
return new
@@ -424,7 +426,7 @@ def get_file_perm(path):
def chmod(path, mode, debug=False):
"""change mode of file"""
if debug:
LOG.dbg('chmod {} {}'.format(oct(mode), path), force=True)
LOG.dbg(f'chmod {mode:o} {path}', force=True)
os.chmod(path, mode)
return get_file_perm(path) == mode
@@ -452,23 +454,23 @@ def debug_list(title, elems, debug):
"""pretty print list"""
if not debug:
return
LOG.dbg('{}:'.format(title), force=debug)
LOG.dbg(f'{title}:', force=debug)
for elem in elems:
LOG.dbg('\t- {}'.format(elem), force=debug)
LOG.dbg(f'\t- {elem}', force=debug)
def debug_dict(title, elems, debug):
"""pretty print dict"""
if not debug:
return
LOG.dbg('{}:'.format(title), force=debug)
LOG.dbg(f'{title}:', force=debug)
for k, val in elems.items():
if isinstance(val, list):
LOG.dbg('\t- \"{}\":'.format(k), force=debug)
LOG.dbg(f'\t- \"{k}\":', force=debug)
for i in val:
LOG.dbg('\t\t- {}'.format(i), force=debug)
LOG.dbg(f'\t\t- {i}', force=debug)
else:
LOG.dbg('\t- \"{}\": {}'.format(k, val), force=debug)
LOG.dbg(f'\t- \"{k}\": {val}', force=debug)
def check_version():
@@ -493,14 +495,14 @@ def check_version():
def pivot_path(path, newdir, striphome=False, logger=None):
"""change path to be under newdir"""
if logger:
logger.dbg('pivot new dir: \"{}\"'.format(newdir))
logger.dbg('strip home: {}'.format(striphome))
logger.dbg(f'pivot new dir: \"{newdir}\"')
logger.dbg(f'strip home: {striphome}')
if striphome:
path = strip_home(path)
sub = path.lstrip(os.sep)
new = os.path.join(newdir, sub)
if logger:
logger.dbg('pivot \"{}\" to \"{}\"'.format(path, new))
logger.dbg(f'pivot \"{path}\" to \"{new}\"')
return new

14
tests.sh vendored
View File

@@ -18,7 +18,9 @@ pyflakes --version
which pycodestyle >/dev/null 2>&1
[ "$?" != "0" ] && echo "Install pycodestyle" && exit 1
echo "testing with pycodestyle"
pycodestyle --ignore=W503,W504,W605 dotdrop/
# W503: Line break occurred before a binary operator
# W504: Line break occurred after a binary operator
pycodestyle --ignore=W503,W504 dotdrop/
pycodestyle tests/
pycodestyle scripts/
@@ -29,6 +31,15 @@ pyflakes tests/
# pylint
echo "testing with pylint"
# https://pylint.pycqa.org/en/latest/user_guide/checkers/features.html
# R0902: too-many-instance-attributes
# R0913: too-many-arguments
# R0903: too-few-public-methods
# R0914: too-many-locals
# R0915: too-many-statements
# R0912: too-many-branches
# R0911: too-many-return-statements
# C0209: consider-using-f-string
pylint \
--disable=R0902 \
--disable=R0913 \
@@ -37,7 +48,6 @@ pylint \
--disable=R0915 \
--disable=R0912 \
--disable=R0911 \
--disable=R1732 \
--disable=C0209 \
dotdrop/