{"id":20165990,"url":"https://github.com/arcticicestudio/snowsaw","last_synced_at":"2025-04-10T01:15:43.410Z","repository":{"id":15502945,"uuid":"78273845","full_name":"arcticicestudio/snowsaw","owner":"arcticicestudio","description":"A lightweight, plugin-driven and dynamic dotfiles bootstrapper.","archived":false,"fork":false,"pushed_at":"2022-04-17T08:06:21.000Z","size":1466,"stargazers_count":98,"open_issues_count":10,"forks_count":7,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2025-04-10T01:15:32.738Z","etag":null,"topics":["bootstrap","bootstrapper","dotfile","dotfiles","hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arcticicestudio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-07T10:57:27.000Z","updated_at":"2025-03-29T03:52:10.000Z","dependencies_parsed_at":"2022-08-07T08:01:09.393Z","dependency_job_id":null,"html_url":"https://github.com/arcticicestudio/snowsaw","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcticicestudio%2Fsnowsaw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcticicestudio%2Fsnowsaw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcticicestudio%2Fsnowsaw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcticicestudio%2Fsnowsaw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arcticicestudio","download_url":"https://codeload.github.com/arcticicestudio/snowsaw/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137891,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bootstrap","bootstrapper","dotfile","dotfiles","hacktoberfest"],"created_at":"2024-11-14T00:42:14.345Z","updated_at":"2025-04-10T01:15:43.390Z","avatar_url":"https://github.com/arcticicestudio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/7836623/59827422-f5a20b00-9338-11e9-9a53-34cacc567e9e.png\" width=\"320px\" /\u003e\u003c/p\u003e\n\n\u003ch2 align=\"center\"\u003esnowsaw is \u003ca href=\"https://github.com/arcticicestudio/snowsaw/tree/epic/gh-33-the-way-to-go\"\u003ecurrently being rewritten\u003c/a\u003e from scratch in \u003ca href=\"https://golang.org\"\u003eGo\u003c/a\u003e!\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003ePlease \u003ca href=\"https://github.com/arcticicestudio/snowsaw/issues/33\"\u003esee the roadmap and epic ticket #33\u003c/a\u003e for more details and information about the \u003cstrong\u003ebackwards compatibility\u003c/strong\u003e with this original Python implementation as well as \u003cstrong\u003emigration strategies\u003c/strong\u003e, planned \u003cstrong\u003efeatures and improvements\u003c/strong\u003e and the \u003cstrong\u003eoverall project goals\u003c/strong\u003e.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cdiv align=\"center\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/7836623/62348122-4ee68800-b4fc-11e9-9c08-2ad29bd77693.png\" width=\"16\" /\u003e \u003ca href=\"https://github.com/arcticicestudio/snowsaw/issues/33\"\u003eRead all about the rewrite and project future plans\u003c/a\u003e\u003c/div\u003e\n  \u003cdiv align=\"center\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/7836623/62347724-ea76f900-b4fa-11e9-8be6-492280cc3da5.png\" width=\"24\" /\u003e \u003ca href=\"https://github.com/arcticicestudio/snowsaw/tree/epic/gh-33-the-way-to-go\"\u003eBrowse the current Go implementation branch\u003c/a\u003e\u003c/div\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003ePlease \u003cstrong\u003ereport any bug you encounter when testing the new Go implementation\u003c/strong\u003e to help making the project more stable and read for production. Every feedback is always welcome!\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://cdn.rawgit.com/arcticicestudio/snowsaw/develop/assets/snowsaw-banner.svg\"/\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003ca href=\"https://github.com/arcticicestudio/snowsaw/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/release/arcticicestudio/snowsaw.svg?style=flat-square\u0026color=88C0D0\u0026label=Release\u0026logo=github\u0026logoColor=eceff4\"/\u003e\u003c/a\u003e \u003ca href=\"https://github.com/arcticicestudio/snowsaw/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/badge/pre--release---_-88C0D0.svg?style=flat-square\"/\u003e\u003c/a\u003e \u003cimg src=\"https://img.shields.io/badge/Python-3.5+-88C0D0.svg?style=flat-square\u0026logo=python\u0026logoColor=eceff4\"/\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eA lightweight, plugin-driven and simple configurable dotfile bootstrapper.\u003c/p\u003e\n\n---\n\nIt does less than you think, because version control systems do more than you think.  \nDesigned to be self-contained and extensible with no external dependencies and no installation required.\n\n  - [Getting started](#getting-started)\n    - [Integration](#integration)\n      - [Add the submodule](#add-the-submodule)\n      - [Create a bootstrap script](#create-a-bootstrap-script)\n    - [Version Update](#version-update)\n  - [Design Concept](#design-concept)\n    - [snowblocks](#snowblocks)\n    - [Repository Structure](#repository-structure)\n  - [Plugins](#plugins)\n    - [Plugins API](#plugin-api)\n  - [CLI](#cli)\n  - [Configuration](#configuration)\n    - [Core Tasks](#core-tasks)\n      - [`link`](#link)\n      - [`shell`](#shell)\n      - [`clean`](#clean)\n    - [Defaults](#defaults)\n  - [Development](#development)\n    - [Debugging](#debugging)\n      - [JetBrains PyCharm](#jetbrains-pycharm)\n    - [Contribution](#contribution)\n  - [Credits](#credits)\n\n## Getting started\nPlease make sure to read the [design concept](#design-concept) and [configuration](#configuration) documentation sections before integrating snowsaw to understand the way it works.\n\n### Integration\n#### Add the submodule\nCreate the base directory for snowblocks and add snowsaw with the latest stable version as a submodule to your dotfile repository:\n```\nmkdir snowblocks\ngit submodule add https://github.com/arcticicestudio/snowsaw .snowsaw\n```\nThis command will add the snowsaw project at the main development branch `develop`, but it is recommened to use a stable release version by running\n```sh\ncd .snowsaw\ngit checkout v0.2.0\ncd ..\n```\nand commit the changes in your dotfile repository to lock it on the specified version tag.  \nThe \"[Update the version](#update-the-version)\" section contains more information on how to update to another version at any time.\n\nThe `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.\n\nThe latest changes from the `develop` branch can be simply fetched by running\n```sh\ngit submodule update --remote .snowsaw\n```\n\n#### Create a bootstrap script\nIt is recommened to create a `bootstrap` script that calls the [`snowsaw`](https://github.com/arcticicestudio/snowsaw/blob/master/bin/snowsaw) binary with the required parameters.  \nInformation about available options and environment variables can be found in the [CLI](#cli) documentation.\n```sh\n#!/usr/bin/env bash\nset -e\n\nSNOWSAW_DIR=\".snowsaw\"\nSNOWSAW_BIN=\"bin/snowsaw\"\n\nSNOWBLOCKS_BASE_DIR_NAME=\"snowblocks\"\nSNOWBLOCKSDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" \u0026\u0026 pwd)/$SNOWBLOCKS_BASE_DIR_NAME\"\n\n\"${SNOWSAW_DIR}/${SNOWSAW_BIN}\" -s \"${SNOWBLOCKSDIR}\" \"${@}\"\n```\nThe `${@}` allows to additionally specify supported options on the terminal for a dynamic script execution.\n\n### Version Update\nThe snowsaw version can be simply updated by checking out to the desired version tag inside the submodule repository:\n```sh\ncd .snowsaw\ngit checkout \u003cTAG\u003e\ncd ..\n```\n\n## Design Concept\n### \u003cimg src=\"https://cdn.rawgit.com/arcticicestudio/snowsaw/develop/assets/icon-snowblocks.svg\"/\u003e snowblocks\nA `snowblock` is a named directory that represents a topic area.  \nEvery valid `snowblock` contains a `snowblock.json` configuration file.  \nAll `snowblock` directories are placed in one base directory, defaults to `\u003cDOTFILE_REPOSITORY_ROOT\u003e/snowblocks`, which will be processed recursively.\n\nThis 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.  \nThe structure plays well with the snowsaw feature that allows to specify one configuration file to only process a single `snowblock`.\n\n### Repository Structure\nsnowsaw 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.\n\nThe dotfile repository [Igloo](https://github.com/arcticicestudio/igloo) may be used as a repository structure reference.\n\n```\n\u003cDOTFILE_REPOSITORY_ROOT\u003e\n|-- .git\n|-- .github\n|-- .snowsaw\n|-- assets\n|-- snowblocks\n    |-- atom\n        |-- config.cson\n        |-- projects.cson\n        |-- snowblock.json\n    |-- git\n        |-- gitconfig\n        |-- git-commit-message\n        |-- gitingore\n        |-- snowblock.json\n    |-- vim\n        |-- snowblock.json\n        |-- vimrc\n|-- .gitignore\n|-- .gitmodules\n|-- CHANGELOG.md\n|-- LICENSE.md\n|-- README.md\n|-- bootstrap\n```\n\u003e Example directory tree of a dotfile repository\n\n## Plugins\nsnowsaw is designed as a plugin system to be easily extensible.\nThe [plugin API](#plugin-api) also allows users to implement their own plugins for custom tasks.\nTasks are detected as a `directive` where each plugin can handle one or more directive.\n\nThe snowsaw core plugins provide tasks to\n  - [`link`](#link) files and folders\n  - execute [`shell`](#shell) commands\n  - [`clean`](#clean) directories of broken symbolic links\n\nAll core plugins are loaded by default, but can be disabled by using the `--disable-core-plugins` terminal option.\n\nA list of available plugins can be found in the [project wiki](https://github.com/arcticicestudio/snowsaw/wiki/Plugins).\n\nAll core plugins have been implemented following the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) and [Unix philosophy](https://en.wikipedia.org/wiki/Unix_philosophy).  \n\n### Plugin API\nPlugins are implemented as subclasses of [`snowsaw.Plugin`](https://github.com/arcticicestudio/snowsaw/blob/master/snowsaw/plugin.py).  \nThey must implement the methods\n```python\ncan_handle()\nhandle()\n```\n\nThe `can_handle()` method should return `True` if the plugin can handle an action with the given name.  \nThe `handle()` method should process the task and return whether or not it completed successfully.\n\nPlugins are loaded using the `--plugin` and `--plugin-dir` terminal option, using either absolute paths or paths relative to the base directory.\n\nThe [core plugins](https://github.com/arcticicestudio/snowsaw/tree/master/snowsaw/plugins) can be used as a reference to implement custom plugins.\n\n## CLI\nsnowsaw supports to specify CLI terminal parameters to dynamically control the execution.  \nA terminal help page can be shown by using the `-h`/`--help` option.\n\n| Option | Parameter(s) | Required | Description |\n| --- | --- | --- | --- |\n| `-Q`, `--super-quiet` | - | No | Suppress almost all output. |\n| `-q`, `--quiet` | - | No | Suppress most output. |\n| `-v`, `--verbose` | - | No | Enable verbose output. |\n| `-s`, `--snowblocks-directory` | `SNOWBLOCKSDIR` | Yes | Base snowblock directory to run all tasks of. |\n| `-c`, `--config-file` | `CONFIGFILE` | No | Run tasks for the specified snowblock. |\n| `-p`, `--plugin` | `PLUGIN` | No | Load `PLUGIN` as a plugin. |\n| `--disable-core-plugins` | - | No | Disable all core plugins. |\n| `--plugin-dir` | `PLUGIN_DIR` | No | Load all plugins in `PLUGIN_DIR`. |\n\n## Configuration\nsnowsaw uses [JSON](http://json.org) configuration files to specify tasks on how to set up your dotfiles.\n\nA 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.\n\n### Core Tasks\n#### `link`\nLinks 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.\n\n##### Format\nLinks 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.\n\nLinks support an optional extended configuration. In this type of configuration, instead of specifying source locations directly, targets are mapped to extended configuration dictionaries.  \nThese dictionaries support the following options:\n\n| Option | Values | Default Value | Required | Description |\n| --- | --- | --- | --- | --- |\n| `create` | `true`, `false` | `false` | No | Specifies if the parent directory should be created if necessary. |\n| `force` | `true`, `false` | `false` | No | Specifies if the file or directory should be forcibly linked. **This can cause irreversible data loss! Use with caution!** |\n| `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.\u003cbr\u003e\u003cbr\u003eThe hostname `-` can be specified as fallback target if non of the given hostnames matched. |\n| `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. |\n| `relink` | `true`, `false` | `false` |  No | Specifies if incorrect symbolic links should be automatically overwritten. |\n| `relative` | `true`, `false` | `false` |  No | Specifies if the symbolic link should have a relative path. |\n\n##### Example\n```json\n[\n  {\n    \"link\": {\n      \"~/.gitconfig\": {\n        \"create\": true,\n        \"hosts\": {\n          \"archlinux-home\": \"gitconfig.home\",\n          \"archlinux-work\": \"gitconfig.work\",\n          \"-\": \"gitconfig.base\"\n        }\n      },\n      \"~/.gitconfig_auth\": {\n        \"path\": \"gitconfig_auth.local\"\n      },\n      \"~/.gitignore\": {\n        \"force\": true,\n        \"relink\": true,\n      },\n      \"~/.git-commit-message\": {\n        \"relative\": true\n      }\n    }\n  }\n]\n```\nIf the source location is omitted or set to `null`, snowsaw will use the basename of the destination, with a leading `.` stripped if present.\n\n#### `shell`\nThe shell task specifies shell commands to be run. Shell tasks are run in the base `snowblock` directory that is specified as terminal parameter.\n\n##### Format\nShell 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.\n\nAnother 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.\n\nShell 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:\n\n| Option | Values | Default Value | Required | Description |\n| --- | --- | --- | --- | --- |\n| `command` | `string` | - |  Yes | The command to be run. |\n| `description` | `string` | - | No |  A human-readable description. |\n| `stdin` | `true`, `false` | `false` | No |  Specifies if the standard input stream is enabled. |\n| `stdout` | `true`, `false` | `false` | No |  Specifies if the standard output stream is enabled. |\n| `stderr` | `true`, `false` | `false` | No |  Specifies if the standard error stream is enabled. |\n\n##### Example\n```json\n[\n  {\n    \"shell\": [\n      \"mkdir -p ~/yogurt\",\n      [\"mkdir -p ~/yogurt\", \"Creating yogurt folder\"],\n      {\n        \"command\": \"mkdir -p ~/coconut\",\n        \"description\": \"Creating coconut folder\",\n        \"stderr\": true,\n        \"stdin\": true,\n        \"stdout\": true\n      }\n    ]\n  }\n]\n```\n\n#### `clean`\nClean 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.\n\n##### Format\nClean commands are specified as an array of directories to be cleaned.\n\n##### Example\n```json\n[\n  {\n    \"clean\": [\"~\"]\n  }\n]\n```\n\n### Defaults\nDefault options for plugins can be specified so that options don't have to be repeated many times.\n\nDefaults 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.\n\n##### Format\nDefaults are specified as a dictionary mapping action names to settings, which are dictionaries from option names to values.\n\n##### Example\n```json\n[\n  {\n    \"defaults\": {\n      \"create\": true,\n      \"relink\": true\n    }\n  }\n]\n```\n\n## Development\n[![](https://img.shields.io/badge/Changelog-0.2.0-81A1C1.svg?style=flat-square)](https://github.com/arcticicestudio/snowsaw/blob/v0.2.0/CHANGELOG.md) [![](https://img.shields.io/badge/Workflow-gitflow--branching--model-81A1C1.svg?style=flat-square)](http://nvie.com/posts/a-successful-git-branching-model) [![](https://img.shields.io/badge/Versioning-ArcVer_0.8.0-81A1C1.svg?style=flat-square)](https://github.com/arcticicestudio/arcver)\n\n### Debugging\n#### JetBrains PyCharm\nsnowsaw is developed using one of the awesome tools from JetBrains called [PyCharm](https://www.jetbrains.com/pycharm).\nThe project files are located in the [`.idea`](https://github.com/arcticicestudio/snowsaw/tree/develop/.idea) directory.\n\nThe included [run/debug configurations](https://www.jetbrains.com/help/pycharm/run-debug-configurations.html) for the script must be manually adjusted to match the paths to the main [`snowsaw`](https://github.com/arcticicestudio/snowsaw/blob/develop/bin/snowsaw) script and the path parameter for the `-s` / `--snowblocks-directory` CLI option.\n![][scrot-readme-debugging-run-configuration]\n\nsnowsaw can [run](https://www.jetbrains.com/help/pycharm/running-applications.html) in the debug mode by using the bug icon or from the menu via *Run* -\u003e *Debug 'snowsaw'*.\n![][scrot-readme-debugging-run]\n![][scrot-readme-debugging-run-menu]\n\nThe [debug window](https://www.jetbrains.com/help/pycharm/debug-tool-window.html) will automatically toggle to show [*Variables*](https://www.jetbrains.com/help/pycharm/debug-tool-window-variables.html) for defined debug breakpoints marked in the editor gutter.\n![][scrot-readme-debugging-code]\n![][scrot-readme-debugging-variables]\n\n### Contribution\nPlease report issues/bugs, feature requests and suggestions for improvements to the [issue tracker](https://github.com/arcticicestudio/snowsaw/issues).\n\n## Credits\nsnowsaw is based on the awesome [Dotbot](https://github.com/anishathalye/dotbot) project by [@anishathalye](https://github.com/anishathalye) as a customized fork for my personal dotfiles repository [Igloo](https://github.com/arcticicestudio/igloo).\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/arcticicestudio/nord-docs/develop/assets/images/nord/repository-footer-separator.svg?sanitize=true\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-5E81AC.svg?style=flat-square\"/\u003e\u003c/p\u003e\n\n[scrot-readme-debugging-run-configuration]: https://raw.githubusercontent.com/arcticicestudio/snowsaw/develop/assets/scrot-readme-debugging-run-configuration.png\n[scrot-readme-debugging-run]: https://raw.githubusercontent.com/arcticicestudio/snowsaw/develop/assets/scrot-readme-debugging-run.png\n[scrot-readme-debugging-run-menu]: https://raw.githubusercontent.com/arcticicestudio/snowsaw/develop/assets/scrot-readme-debugging-run-menu.png\n[scrot-readme-debugging-variables]: https://raw.githubusercontent.com/arcticicestudio/snowsaw/develop/assets/scrot-readme-debugging-variables.png\n[scrot-readme-debugging-code]: https://raw.githubusercontent.com/arcticicestudio/snowsaw/develop/assets/scrot-readme-debugging-code.png\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farcticicestudio%2Fsnowsaw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farcticicestudio%2Fsnowsaw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farcticicestudio%2Fsnowsaw/lists"}