Automatic Cleanup¶
One of the key features provided by homely is automatic cleanup of things
you are no longer using. It does this by keeping a record of files it modified
and programs it installed each time it runs. Later on, when you run
homely update again and your HOMELY.py
script is no longer asking to
change a particular file or install a particular program, homely
assumes that you no longer want that thing and it will try to undo the changes
to the file or uninstall the program.
Even though the automatic cleanup feature should just work how you expect it to, it is still worthwhile reading through the examples below which will give you a better idea what to expect in various scenarios.
Example 1: Cleaning Directories¶
Let’s assume you have a HOMELY.py
script that creates several directories
like this:
# ~/dotfiles/HOMELY.py
from homely.files import mkdir
mkdir('~/bin')
mkdir('~/.vim')
mkdir('~/.config')
When you run homely update homely will create all of those directories
(as long as they don’t already exist) and it will remember which ones it
created. At some point in the future you might no longer want the .vim
directory so you can just remove it or comment it out:
# ~/dotfiles/HOMELY.py
from homely.files import mkdir
mkdir('~/bin')
# I don't use this any more
#mkdir('~/.vim')
mkdir('~/.config')
Next time you run homely update, homely will get to the end of the
update process and notice that there was no call to mkdir("~/.vim")
. It
assumes this means you no longer want this directory so it will try to remove
the directory for you.
homely tries to be smart about the order it does cleanup. It can’t remove a directory that still contains files, so it will first perform cleanup of any files inside that directory just in case this makes it possible to remove the directory itself afterward.
Example 2: Cleaning Conditional Changes¶
Automatic cleanup also works well with conditional sections of code in your
HOMELY.py
script. For example, you might have a section of code that
creates your .isort.cfg
file only when an isort
executable is installed
on your system:
from homely.files import symlink
from homely.system import haveexecutable
if haveexecutable('isort'):
symlink('.isort.cfg')
If you run homely update with an isort
executable in your $PATH
,
the .isort.cfg
symlink will be created. If you uninstall isort
and
re-run homely update, the call to symlink('.isort.cfg')
will no
longer be run and homely will automatically remove the symlink for you.
Example 3: Multiple Repos¶
If you have multiple dotfiles repos, you may sometimes move code from one repo
to another. For example, let’s say you had a personal-dotfiles
repo and a
work-dotfiles
repo with these HOMELY.py
scripts:
# ~/personal-dotfiles/HOMELY.py
from homely.files import symlink
symlink('.vimrc')
symlink('.gitconfig')
symlink('.isort.cfg')
# ~/work-dotfiles/HOMELY.py
from homely.files import symlink
symlink('.hgrc')
… and you decide that you want to make the isort.cfg
part of your
work-dotfiles
repo. You might just move the symlink()
line like this:
# ~/personal-dotfiles/HOMELY.py
from homely.files import symlink
symlink('.vimrc')
symlink('.gitconfig')
# ~/work-dotfiles/HOMELY.py
from homely.files import symlink
symlink('.hgrc')
symlink('.isort.cfg')
Next time you run homely update, homely will not attempt to
cleanup the .isort.cfg
symlink because the the work-dotfiles/HOMELY.py
script is still asking for the symlink to be created.
Example 4: Cleaning Modified Files¶
Sometimes homely can’t take total ownership of files for cleanup purposes.
For example, if you have a HOMELY.py
script that modifies ~/.bashrc
using
homely.files.lineinfile() like this:
from homely.files import lineinfile
lineinfile('~/.bashrc', 'PATH=$HOME/dotfiles/bin:$PATH')
If you comment out the call to lineinfile()
and run homely update,
homely knows it can’t just remove the whole ~/.bashrc
file. Instead, it
will try and “undo” the file changes – it will look for the line of text added
by the call to lineinfile()
and remove it if it is still present.
But what happens if you actually replaced the call to lineinfile()
with a
call to blockinfile()
that creates the same line?
from homely.files import blockinfile
#lineinfile('~/.bashrc', 'PATH=$HOME/dotfiles/bin:$PATH')
lines = ['PATH=$HOME/dotfiles/bin:$PATH']
blockinfile('~/.bashrc', lines, '# dotfiles begin', '# dotfiles end')
This scenario is also handled just fine because when homely cleans up a file by undoing changes to it, it will then re-run all of the other functions that modified that file. This is safe to do because all of the file modification functions are idempotent.
In other words, when you run homely update after making the above change, homely will:
- Add 3 new lines to
~/.bashrc
whenblockinfile()
is called. This will result in thePATH=...
temporarily appearing in~/.bashrc
twice. - Note the fact that there was a call to
blockinfile()
where the target file was~/.bashrc
. - Run automatic cleanup of the
lineinfile()
call that no longer exists. This will cause all occurences of thePATH=...
line to be removed from~/.bashrc
– even the line between# dotfiles begin
and# dotfiles end
will be removed. - Re-run the call to
blockinfile()
which will recreate the# dotfiles begin ... # dotfiles end
block.
Limitations¶
- homely can only cleanup changes that were made using functions from its
own modules. E.g., directories created using
homely.files.mkdir()
can be cleaned up, but not directories created usingos.mkdir()
. - homely can only perform cleanup when you perform an update of all repos using homely update.
- Sometimes things can’t be cleaned up if they are still in use. E.g., if a
directory created by
homely.files.mkdir()
isn’t empty, then homely cannot remove it automatically. Check the documentation for each feature to find out if it has any additional limitations. - If homely is prevented from performing cleanup (e.g. a directory can’t be removed because isn’t empty) it gives up and won’t try and cleanup that thing again. This is to prevent homely update warning you every time about something that can’t be cleaned up.