1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 20:19:46 +00:00
This commit is contained in:
deadc0de6
2019-02-03 14:38:01 +01:00
7 changed files with 232 additions and 70 deletions

123
README.md
View File

@@ -83,6 +83,7 @@ why [dotdrop](https://github.com/deadc0de6/dotdrop) rocks.
* [Available variables](#available-variables)
* [Available methods](#available-methods)
* [Dynamic dotfile paths](#dynamic-dotfile-paths)
* [Dynamic actions](#dynamic-actions)
* [Dotdrop header](#dotdrop-header)
* [Example](#example)
* [User tricks](#user-tricks)
@@ -198,7 +199,7 @@ $ dotdrop install
## Compare dotfiles
Compare local dotfiles with dotdrop's defined ones:
Compare local dotfiles with the ones stored in dotdrop:
```bash
$ dotdrop compare
```
@@ -209,8 +210,7 @@ options to diff using the `-o` switch.
It is possible to add patterns to ignore when using `compare` for example
when a directory is managed by dotdrop and might contain temporary files
that don't need to appear in the output of compare.
Either use the command line switch `-i --ignore` or add an entry in the dotfile
Either use the command line switch `-i --ignore` or add a line in the dotfile
directly in the `cmpignore` entry (see [Config](#config)).
The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
Make sure to quote those when using wildcards in the config file.
@@ -235,8 +235,8 @@ with the option `longkey` (per default to *false*).
Two formats are available:
* short format (default): take the shortest unique path
* long format: take the full path
* *short format* (default): take the shortest unique path
* *long format*: take the full path
For example `~/.config/awesome/rc.lua` gives
@@ -273,20 +273,24 @@ $ dotdrop listfiles --profile=<some-profile>
For example:
```
Dotfile(s) for profile "some-profile":
f_vimrc (file: "vimrc", link: False)
f_vimrc (file: "vimrc", link: nolink)
-> ~/.vimrc
f_dunstrc (file: "config/dunst/dunstrc", link: False)
f_dunstrc (file: "config/dunst/dunstrc", link: nolink)
-> ~/.config/dunst/dunstrc
```
By using the `-T --template` switch, only the dotfiles that
are using jinja2 directives are listed.
are using [jinja2](http://jinja.pocoo.org/) directives are listed.
It is also possible to list all files related to each dotfile entries
by invoking the `detail` command, for example:
```bash
$ dotdrop detail
dotfiles details for profile "some-profile":
f_tmux.conf (dst: "~/.tmux.conf", link: nolink)
-> /home/user/dotfiles/tmux.conf (template:no)
f_vimrc (dst: "~/.vimrc", link: nolink)
-> /home/user/dotfiles/vimrc (template:no)
```
This is especially useful when the dotfile entry is a directory
@@ -474,7 +478,7 @@ $ dotdrop update ~/.vimrc
$ dotdrop update --key f_vimrc
```
It is possible to ignore files to update using unix pattern by providing those
It is possible to ignore files to update using unix patterns by providing those
either through the switch `-i --ignore` or as part of the dotfile under the
key `upignore` (see [Config](#config)).
The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
@@ -539,64 +543,64 @@ Dotdrop offers two ways to symlink dotfiles. The first simply links `dst` to
`src`. To enable it, simply set `link: true` under the dotfile entry in the
config file.
The second symlink method is a little more complicated. It creates a symlink in
`dst` for every file/directory in `src`.
The second symlink method allows to have every files/directories under `src` to
be symlinked in `dst`. It is enabled by setting `link_children: true`.
### Why would I use `link_children`?
### Link children
This feature can be very useful for dotfiles such as vim where you may not want
plugins cluttering your dotfiles repository. First, the simpler `link: true` is
shown for comparison. With the `config.yaml` entry shown below, `~/.vim` gets
symlinked to `~/.dotfiles/vim/`. This means that using vim will now pollute the
dotfiles repository. `plugged` (if using
[vim-plug](https://github.com/junegunn/vim-plug)), `spell`, and `swap`
directories will appear `~/.dotfiles/vim/`.
This feature can be very useful for dotfiles when you don't want the entire
directory to be symlink but still want to keep a clean config files (with a
limited number of entries).
```yml
A good example of its use is when managing `~/.vim` with dotdrop.
Here's what it looks like when using the basic `link: true`. The top
directory `~/.vim` is symlinked to the *dotpath* location (here `~/.dotfiles/vim`):
```yaml
vim:
dst: ~/.vim/
src: ./vim/
actions:
- vim-plug-install
- vim-plug
src: vim
link: true
```
```
```bash
$ readlink ~/.vim
~/.dotfiles/vim/
$ ls ~/.dotfiles/vim/
after autoload plugged plugin snippets spell swap vimrc
```
Let's say we just want to store `after`, `plugin`, `snippets`, and `vimrc` in
our `~/.dotfiles` repository. This is where `link_children` comes in. Using the
configuration below, `~/.vim/` is a normal directory and only the children of
`~/.dotfiles/vim` are symlinked into it.
```yml
As a result, all files under `~/.vim` will be managed by
dotdrop (including unwanted directories like `spell`, `swap`, etc).
A cleaner solution is to use `link_children` which allows to only symlink specific
files under the dotfile directory. Let's say only `after`, `plugin`, `snippets`, and `vimrc`
need to be managed in dotdrop. `~/.vim` is imported in dotdrop, cleaned off all unwanted
files and directories and then the `link_children` entry is set to `true` in the config file.
Now all children of the `vim` dotfile's directory in the *dotpath* will be symlinked under `~/.vim/`
without affecting the rest of the local files, keeping the config file clean
and all unwanted files only on the local system.
```yaml
vim:
dst: ~/.vim/
src: ./vim/
actions:
- vim-plug-install
- vim-plug
src: vim
link_children: true
```
As can be seen below, `~/.vim/` is a normal directory, not a symlink. Also, the
files/directories `after`, `plugin`, `snippets`, and `vimrc` are symlinked to
`~/.dotfiles/vim/`.
```
```bash
$ readlink -f ~/.vim
~/.vim
$ tree -L 1 ~/.vim
~/.vim
├── after -> /.dotfiles/./vim/after
├── after -> ~/.dotfiles/vim/after
├── autoload
├── plugged
├── plugin -> /.dotfiles/./vim/plugin
├── snippets -> /.dotfiles/./vim/snippets
├── plugin -> ~/.dotfiles/vim/plugin
├── snippets -> ~/.dotfiles/vim/snippets
├── spell
├── swap
└── vimrc -> /.dotfiles/./vim/vimrc
└── vimrc -> ~/.dotfiles/vim/vimrc
```
### Templating symlinked dotfiles
@@ -614,7 +618,7 @@ For example
/home/user/.xyz -> /home/user/.config/dotdrop/.xyz
# without template
/home/user/.xyz -> /home/user/dotdrop/dotfiles/xyz
/home/user/.xyz -> /home/user/dotfiles/xyz
```
# Config
@@ -642,6 +646,7 @@ the following entries:
* `dst`: where this dotfile needs to be deployed (can use `variables` and `dynvariables`, make sure to quote).
* `src`: dotfile path within the `dotpath` (can use `variables` and `dynvariables`, make sure to quote).
* `link`: if true dotdrop will create a symlink instead of copying (default *false*).
* `link_children`: if true dotdrop will create a symlink for each file under `src` (default *false*).
* `cmpignore`: list of pattern to ignore when comparing (enclose in quotes when using wildcards).
* `upignore`: list of pattern to ignore when updating (enclose in quotes when using wildcards).
* `actions`: list of action keys that need to be defined in the **actions** entry below.
@@ -654,7 +659,7 @@ the following entries:
dst: <where-this-file-is-deployed>
src: <filename-within-the-dotpath>
# Optional
link: <true|false>
(link|link_children): <true|false>
ignoreempty: <true|false>
cmpignore:
- "<ignore-pattern>"
@@ -795,7 +800,7 @@ Following template variables are available:
* interpreted variables (see [Interpreted variables](#interpreted-variables))
All variables are recursively evaluated what means that
a config similar to:
a config like the below
```yaml
variables:
var1: "var1"
@@ -809,7 +814,7 @@ dynvariables:
dvar4: "echo {{@@ var3 @@}}"
```
will result in
will result in the following available variables
* var3: `var1 var2 var3`
* dvar3: `dvar1 dvar2 dvar3`
* var4: `echo var1 var2 var3`
@@ -943,6 +948,24 @@ profiles:
Make sure to quote the path in the config file.
## Dynamic actions
Variables (`variables` and `dynvariables`) can be used
in actions for more advanced use-cases:
For example
```yaml
dynvariables:
trizen_itself_available: (command -v trizen>/dev/null || cd /tmp; git clone https://aur.archlinux.org/trizen.git; cd trizen; makepkg -si)
trizen_package_install: {{@@ trizen_itself_available @@}} && trizen -S --needed
actions:
trizen_install: {{@@ trizen_package_install @@}} {0}
pre:
pip_install: {{@@ trizen_package_install @@}} python-pip && pip install {0}
yarn_install: {{@@ trizen_package_install @@}} yarn && yarn global add {0}
```
## Dotdrop header
Dotdrop is able to insert a header in the generated dotfiles. This allows
@@ -953,13 +976,13 @@ Here's what it looks like:
This dotfile is managed using dotdrop
```
The header can be automatically added using jinja2 directive:
The header can be automatically added using [jinja2](http://jinja.pocoo.org/) directive:
```
{{@@ header() @@}}
```
Properly commenting the header in templates is the responsability of the user
as jinja2 has no way of knowing what is the proper char(s) used for comments.
as [jinja2](http://jinja.pocoo.org/) has no way of knowing what is the proper char(s) used for comments.
Either prepend the directive with the commenting char(s) used in the dotfile (for example `# {{@@ header() @@}}`)
or provide it as an argument `{{@@ header('# ') @@}}`. The result is equivalent.
@@ -986,7 +1009,7 @@ Dotdrop allows to store only one single *.xinitrc* but
to deploy different versions depending on where it is run from.
The following file is the dotfile stored in dotdrop containing
jinja2 directives for the deployment based on the profile used.
[jinja2](http://jinja.pocoo.org/) directives for the deployment based on the profile used.
Dotfile `<dotpath>/xinitrc`:
```bash

View File

@@ -119,11 +119,20 @@ class Cfg:
raise ValueError('config is not valid')
def eval_dotfiles(self, profile, debug=False):
"""resolve dotfiles src/dst templating for this profile"""
"""resolve dotfiles src/dst/actions templating for this profile"""
t = Templategen(variables=self.get_variables(profile, debug=debug))
for d in self.get_dotfiles(profile):
# src and dst path
d.src = t.generate_string(d.src)
d.dst = t.generate_string(d.dst)
# pre actions
if self.key_actions_pre in d.actions:
for action in d.actions[self.key_actions_pre]:
action.action = t.generate_string(action.action)
# post actions
if self.key_actions_post in d.actions:
for action in d.actions[self.key_actions_post]:
action.action = t.generate_string(action.action)
def _load_file(self):
"""load the yaml file"""

View File

@@ -216,15 +216,15 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
def cmd_update(opts, conf, paths, iskey=False, ignore=[]):
"""update the dotfile(s) from path(s) or key(s)"""
ret = True
updater = Updater(conf, opts['dotpath'], opts['dry'],
opts['safe'], iskey=iskey,
updater = Updater(conf, opts['dotpath'], opts['profile'],
opts['dry'], opts['safe'], iskey=iskey,
debug=opts['debug'], ignore=[])
if not iskey:
# update paths
if opts['debug']:
LOG.dbg('update by paths: {}'.format(paths))
for path in paths:
if not updater.update_path(path, opts['profile']):
if not updater.update_path(path):
ret = False
else:
# update keys
@@ -235,7 +235,7 @@ def cmd_update(opts, conf, paths, iskey=False, ignore=[]):
if opts['debug']:
LOG.dbg('update by keys: {}'.format(keys))
for key in keys:
if not updater.update_key(key, opts['profile']):
if not updater.update_key(key):
ret = False
return ret
@@ -332,7 +332,7 @@ def cmd_list_files(opts, conf, templateonly=False):
if not Templategen.is_template(src):
continue
LOG.log('{} (src: \"{}\", link: {})'.format(dotfile.key, dotfile.src,
dotfile.link))
dotfile.link.name.lower()))
LOG.sub('{}'.format(dotfile.dst))
LOG.log('')
@@ -360,7 +360,7 @@ def cmd_detail(opts, conf, keys=None):
def _detail(dotpath, dotfile):
"""print details on all files under a dotfile entry"""
LOG.log('{} (dst: \"{}\", link: {})'.format(dotfile.key, dotfile.dst,
dotfile.link))
dotfile.link.name.lower()))
path = os.path.join(dotpath, os.path.expanduser(dotfile.src))
if not os.path.isdir(path):
template = 'no'

View File

@@ -37,7 +37,7 @@ class Dotfile:
def __str__(self):
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'
return msg.format(self.key, self.src, self.dst, self.link.name)
return msg.format(self.key, self.src, self.dst, self.link.name.lower())
def __eq__(self, other):
return self.__dict__ == other.__dict__

View File

@@ -64,7 +64,8 @@ class Installer:
if self.debug:
self.log.dbg('link {} to {}'.format(src, dst))
self.action_executed = False
src = os.path.join(self.base, os.path.expanduser(src))
src = os.path.normpath(os.path.join(self.base,
os.path.expanduser(src)))
if not os.path.exists(src):
self.log.err('source dotfile does not exist: {}'.format(src))
return []
@@ -124,8 +125,10 @@ class Installer:
os.mkdir(dst)
children = os.listdir(parent)
srcs = [os.path.join(parent, child) for child in children]
dsts = [os.path.join(dst, child) for child in children]
srcs = [os.path.normpath(os.path.join(parent, child))
for child in children]
dsts = [os.path.normpath(os.path.join(dst, child))
for child in children]
for i in range(len(children)):
src = srcs[i]

View File

@@ -20,10 +20,11 @@ TILD = '~'
class Updater:
def __init__(self, conf, dotpath, dry, safe,
def __init__(self, conf, dotpath, profile, dry, safe,
iskey=False, debug=False, ignore=[]):
self.conf = conf
self.dotpath = dotpath
self.profile = profile
self.dry = dry
self.safe = safe
self.iskey = iskey
@@ -31,13 +32,13 @@ class Updater:
self.ignore = ignore
self.log = Logger()
def update_path(self, path, profile):
def update_path(self, path):
"""update the dotfile installed on path"""
if not os.path.lexists(path):
self.log.err('\"{}\" does not exist!'.format(path))
return False
path = self._normalize(path)
dotfile = self._get_dotfile_by_path(path, profile)
dotfile = self._get_dotfile_by_path(path)
if not dotfile:
return False
path = os.path.expanduser(path)
@@ -45,9 +46,9 @@ class Updater:
self.log.dbg('updating {} from path \"{}\"'.format(dotfile, path))
return self._update(path, dotfile)
def update_key(self, key, profile):
def update_key(self, key):
"""update the dotfile referenced by key"""
dotfile = self._get_dotfile_by_key(key, profile)
dotfile = self._get_dotfile_by_key(key)
if not dotfile:
return False
if self.debug:
@@ -111,9 +112,9 @@ class Updater:
path = os.path.join(TILD, path)
return path
def _get_dotfile_by_key(self, key, profile):
def _get_dotfile_by_key(self, key):
"""get the dotfile matching this key"""
dotfiles = self.conf.get_dotfiles(profile)
dotfiles = self.conf.get_dotfiles(self.profile)
subs = [d for d in dotfiles if d.key == key]
if not subs:
self.log.err('key \"{}\" not found!'.format(key))
@@ -124,9 +125,9 @@ class Updater:
return None
return subs[0]
def _get_dotfile_by_path(self, path, profile):
def _get_dotfile_by_path(self, path):
"""get the dotfile matching this path"""
dotfiles = self.conf.get_dotfiles(profile)
dotfiles = self.conf.get_dotfiles(self.profile)
subs = [d for d in dotfiles if d.dst == path]
if not subs:
self.log.err('\"{}\" is not managed!'.format(path))

126
tests-ng/dynactions.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test dynamic actions
# returns 1 in case of error
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "\e[96m\e[1m==> RUNNING $(basename $BASH_SOURCE) <==\e[0m"
################################################################
# this is the test
################################################################
# the action temp
tmpa=`mktemp -d`
# the dotfile source
tmps=`mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d`
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
variables:
var1: "var1"
var2: "{{@@ var1 @@}} var2"
var3: "{{@@ var2 @@}} var3"
var4: "{{@@ dvar4 @@}}"
dynvariables:
dvar1: "echo dvar1"
dvar2: "{{@@ dvar1 @@}} dvar2"
dvar3: "{{@@ dvar2 @@}} dvar3"
dvar4: "echo {{@@ var3 @@}}"
actions:
pre:
preaction1: "echo {{@@ var3 @@}} > ${tmpa}/preaction1"
preaction2: "echo {{@@ dvar3 @@}} > ${tmpa}/preaction2"
post:
postaction1: "echo {{@@ var3 @@}} > ${tmpa}/postaction1"
postaction2: "echo {{@@ dvar3 @@}} > ${tmpa}/postaction2"
naked1: "echo {{@@ var3 @@}} > ${tmpa}/naked1"
naked2: "echo {{@@ dvar3 @@}} > ${tmpa}/naked2"
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
f_abc:
dst: ${tmpd}/abc
src: abc
actions:
- preaction1
- preaction2
- postaction1
- postaction2
- naked1
- naked2
profiles:
p1:
dotfiles:
- f_abc
_EOF
cat ${cfg}
# create the dotfile
echo "test" > ${tmps}/dotfiles/abc
# install
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 --verbose
# checks
[ ! -e ${tmpa}/preaction1 ] && exit 1
[ ! -e ${tmpa}/preaction2 ] && exit 1
[ ! -e ${tmpa}/postaction1 ] && exit 1
[ ! -e ${tmpa}/postaction2 ] && exit 1
[ ! -e ${tmpa}/naked1 ] && exit 1
[ ! -e ${tmpa}/naked2 ] && exit 1
grep 'var1 var2 var3' ${tmpa}/preaction1 >/dev/null
grep 'dvar1 dvar2 dvar3' ${tmpa}/preaction2 >/dev/null
grep 'var1 var2 var3' ${tmpa}/postaction1 >/dev/null
grep 'dvar1 dvar2 dvar3' ${tmpa}/postaction2 >/dev/null
grep 'var1 var2 var3' ${tmpa}/naked1 >/dev/null
grep 'dvar1 dvar2 dvar3' ${tmpa}/naked2 >/dev/null
## CLEANING
rm -rf ${tmps} ${tmpd} ${tmpa}
echo "OK"
exit 0