snowsaw is currently being rewritten from scratch in Go!
Please see the roadmap and epic ticket #33 for more details and information about the backwards compatibility with this original Python implementation as well as migration strategies, planned features and improvements and the overall project goals.
Please report any bug you encounter when testing the new Go implementation to help making the project more stable and read for production. Every feedback is always welcome!
A lightweight, plugin-driven and simple configurable dotfile bootstrapper.
It does less than you think, because version control systems do more than you think.
Designed to be self-contained and extensible with no external dependencies and no installation required.
Please make sure to read the design concept and configuration documentation sections before integrating snowsaw to understand the way it works.
Create the base directory for snowblocks and add snowsaw with the latest stable version as a submodule to your dotfile repository:
mkdir snowblocks
git submodule add https://github.com/arcticicestudio/snowsaw .snowsaw
This command will add the snowsaw project at the main development branch develop
, but it is recommened to use a stable release version by running
cd .snowsaw
git checkout v0.2.0
cd ..
and commit the changes in your dotfile repository to lock it on the specified version tag.
The "Update the version" section contains more information on how to update to another version at any time.
The develop
branch will always include the latest commits, but use it on your own risk as it may raise unexpected error or compatibility issues when fetching the latest changes without ensuring that it runs with your current dotfile configurations.
The latest changes from the develop
branch can be simply fetched by running
git submodule update --remote .snowsaw
It is recommened to create a bootstrap
script that calls the snowsaw
binary with the required parameters.
Information about available options and environment variables can be found in the CLI documentation.
#!/usr/bin/env bash
set -e
SNOWSAW_DIR=".snowsaw"
SNOWSAW_BIN="bin/snowsaw"
SNOWBLOCKS_BASE_DIR_NAME="snowblocks"
SNOWBLOCKSDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$SNOWBLOCKS_BASE_DIR_NAME"
"${SNOWSAW_DIR}/${SNOWSAW_BIN}" -s "${SNOWBLOCKSDIR}" "${@}"
The ${@}
allows to additionally specify supported options on the terminal for a dynamic script execution.
The snowsaw version can be simply updated by checking out to the desired version tag inside the submodule repository:
cd .snowsaw
git checkout <TAG>
cd ..
A snowblock
is a named directory that represents a topic area.
Every valid snowblock
contains a snowblock.json
configuration file.
All snowblock
directories are placed in one base directory, defaults to <DOTFILE_REPOSITORY_ROOT>/snowblocks
, which will be processed recursively.
This design allows a modular structured dotfile repository where each topic can be represented as a snowblock
instead of placing all files and folders without any logical division in the dotfile repository root.
The structure plays well with the snowsaw feature that allows to specify one configuration file to only process a single snowblock
.
snowsaw does not need any specific repository structure except the base snowblocks directory. A dotfile repository may also contain more than one base snowblock directory. This way the repository can be structured even more fine-grained.
The dotfile repository Igloo may be used as a repository structure reference.
<DOTFILE_REPOSITORY_ROOT>
|-- .git
|-- .github
|-- .snowsaw
|-- assets
|-- snowblocks
|-- atom
|-- config.cson
|-- projects.cson
|-- snowblock.json
|-- git
|-- gitconfig
|-- git-commit-message
|-- gitingore
|-- snowblock.json
|-- vim
|-- snowblock.json
|-- vimrc
|-- .gitignore
|-- .gitmodules
|-- CHANGELOG.md
|-- LICENSE.md
|-- README.md
|-- bootstrap
Example directory tree of a dotfile repository
snowsaw is designed as a plugin system to be easily extensible.
The plugin API also allows users to implement their own plugins for custom tasks.
Tasks are detected as a directive
where each plugin can handle one or more directive.
The snowsaw core plugins provide tasks to
All core plugins are loaded by default, but can be disabled by using the --disable-core-plugins
terminal option.
A list of available plugins can be found in the project wiki.
All core plugins have been implemented following the KISS principle and Unix philosophy.
Plugins are implemented as subclasses of snowsaw.Plugin
.
They must implement the methods
can_handle()
handle()
The can_handle()
method should return True
if the plugin can handle an action with the given name.
The handle()
method should process the task and return whether or not it completed successfully.
Plugins are loaded using the --plugin
and --plugin-dir
terminal option, using either absolute paths or paths relative to the base directory.
The core plugins can be used as a reference to implement custom plugins.
snowsaw supports to specify CLI terminal parameters to dynamically control the execution.
A terminal help page can be shown by using the -h
/--help
option.
Option | Parameter(s) | Required | Description |
---|---|---|---|
-Q , --super-quiet |
- | No | Suppress almost all output. |
-q , --quiet |
- | No | Suppress most output. |
-v , --verbose |
- | No | Enable verbose output. |
-s , --snowblocks-directory |
SNOWBLOCKSDIR |
Yes | Base snowblock directory to run all tasks of. |
-c , --config-file |
CONFIGFILE |
No | Run tasks for the specified snowblock. |
-p , --plugin |
PLUGIN |
No | Load PLUGIN as a plugin. |
--disable-core-plugins |
- | No | Disable all core plugins. |
--plugin-dir |
PLUGIN_DIR |
No | Load all plugins in PLUGIN_DIR . |
snowsaw uses JSON configuration files to specify tasks on how to set up your dotfiles.
A configuration file is a array of tasks, where each task is a dictionary that contains a command name mapping to data for that command. Tasks are run in the order in which they are specified. Commands within a task do not have a defined ordering.
Links specify how files and directories should be symbolically linked. If desired, items can be specified to be forcibly linked, overwriting existing files if necessary. Environment variables in paths are automatically expanded.
Links are specified as a dictionary mapping targets to source locations. Source locations are specified relative to the base snowblock
directory that is specified as terminal parameter. Directory names should not contain a trailing /
character.
Links support an optional extended configuration. In this type of configuration, instead of specifying source locations directly, targets are mapped to extended configuration dictionaries.
These dictionaries support the following options:
Option | Values | Default Value | Required | Description |
---|---|---|---|---|
create |
true , false |
false |
No | Specifies if the parent directory should be created if necessary. |
force |
true , false |
false |
No | Specifies if the file or directory should be forcibly linked. This can cause irreversible data loss! Use with caution! |
hosts |
dict |
{} |
No | Contains key-value entries with hostnames and their associated target path this link should be processed for. Links with an empty dictionary will be processed irrespective of the host. The hostname - can be specified as fallback target if non of the given hostnames matched. |
path |
string , null |
null |
No | The path to map the source path. If the path is omitted or null , snowsaw will use the basename of the destination, with a leading . stripped if present. |
relink |
true , false |
false |
No | Specifies if incorrect symbolic links should be automatically overwritten. |
relative |
true , false |
false |
No | Specifies if the symbolic link should have a relative path. |
[
{
"link": {
"~/.gitconfig": {
"create": true,
"hosts": {
"archlinux-home": "gitconfig.home",
"archlinux-work": "gitconfig.work",
"-": "gitconfig.base"
}
},
"~/.gitconfig_auth": {
"path": "gitconfig_auth.local"
},
"~/.gitignore": {
"force": true,
"relink": true,
},
"~/.git-commit-message": {
"relative": true
}
}
}
]
If the source location is omitted or set to null
, snowsaw will use the basename of the destination, with a leading .
stripped if present.
The shell task specifies shell commands to be run. Shell tasks are run in the base snowblock
directory that is specified as terminal parameter.
Shell tasks can be specified in several different ways. The simplest way is just to specify a command as a string containing the command to be run.
Another way is to specify a two element array where the first element is the shell command and the second is an optional human-readable description.
Shell tasks support an extended syntax as well, which provides more fine-grained control. A command can be specified as a dictionary that contains the following options:
Option | Values | Default Value | Required | Description |
---|---|---|---|---|
command |
string |
- | Yes | The command to be run. |
description |
string |
- | No | A human-readable description. |
stdin |
true , false |
false |
No | Specifies if the standard input stream is enabled. |
stdout |
true , false |
false |
No | Specifies if the standard output stream is enabled. |
stderr |
true , false |
false |
No | Specifies if the standard error stream is enabled. |
[
{
"shell": [
"mkdir -p ~/yogurt",
["mkdir -p ~/yogurt", "Creating yogurt folder"],
{
"command": "mkdir -p ~/coconut",
"description": "Creating coconut folder",
"stderr": true,
"stdin": true,
"stdout": true
}
]
}
]
Clean tasks specify directories that should be checked for broken symbolic links. These broken links are removed automatically. Only broken links that point to the dotfiles directory are removed.
Clean commands are specified as an array of directories to be cleaned.
[
{
"clean": ["~"]
}
]
Default options for plugins can be specified so that options don't have to be repeated many times.
Defaults apply to all tasks that follow setting the defaults. Defaults can be set multiple times where each change replaces the defaults with a new set of options.
Defaults are specified as a dictionary mapping action names to settings, which are dictionaries from option names to values.
[
{
"defaults": {
"create": true,
"relink": true
}
}
]
snowsaw is developed using one of the awesome tools from JetBrains called PyCharm.
The project files are located in the .idea
directory.
The included run/debug configurations for the script must be manually adjusted to match the paths to the main snowsaw
script and the path parameter for the -s
/ --snowblocks-directory
CLI option.
snowsaw can run in the debug mode by using the bug icon or from the menu via Run -> Debug 'snowsaw'.
The debug window will automatically toggle to show Variables for defined debug breakpoints marked in the editor gutter.
Please report issues/bugs, feature requests and suggestions for improvements to the issue tracker.
snowsaw is based on the awesome Dotbot project by @anishathalye as a customized fork for my personal dotfiles repository Igloo.