Skip to content

A lightweight, plugin-driven and dynamic dotfiles bootstrapper.

License

Notifications You must be signed in to change notification settings

arcticicestudio/snowsaw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

69 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

Getting started

Please make sure to read the design concept and configuration documentation sections before integrating snowsaw to understand the way it works.

Integration

Add the submodule

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

Create a bootstrap script

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.

Version Update

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 ..

Design Concept

snowblocks

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.

Repository Structure

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

Plugins

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

  • link files and folders
  • execute shell commands
  • clean directories of broken symbolic links

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.

Plugin API

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.

CLI

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.

Configuration

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.

Core Tasks

link

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.

Format

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.
Example
[
  {
    "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.

shell

The shell task specifies shell commands to be run. Shell tasks are run in the base snowblock directory that is specified as terminal parameter.

Format

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.
Example
[
  {
    "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

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.

Format

Clean commands are specified as an array of directories to be cleaned.

Example
[
  {
    "clean": ["~"]
  }
]

Defaults

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.

Format

Defaults are specified as a dictionary mapping action names to settings, which are dictionaries from option names to values.

Example
[
  {
    "defaults": {
      "create": true,
      "relink": true
    }
  }
]

Development

Debugging

JetBrains PyCharm

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.

Contribution

Please report issues/bugs, feature requests and suggestions for improvements to the issue tracker.

Credits

snowsaw is based on the awesome Dotbot project by @anishathalye as a customized fork for my personal dotfiles repository Igloo.