├── .codecov.yaml ├── .funky ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── cookie ├── img ├── demo.auto ├── demo.gif └── logo.png ├── scripts ├── install-deps └── zsh │ └── _cookie ├── setup.cfg └── tests ├── README.md ├── runtests ├── test_cookie.sh └── test_install.sh /.codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "70...100" 3 | 4 | ignore: 5 | - "scripts/*" 6 | - "tests/*" 7 | - "shunit2" 8 | -------------------------------------------------------------------------------- /.funky: -------------------------------------------------------------------------------- 1 | {"C": "echo \"Makefile .travis.yml .codecov.yaml\"", "D": "echo \"README.md CHANGELOG.md\" \"$@\"", "i": "sudo make install \"$@\"", "SS": "echo \"./scripts/install-deps\" \"$@\"", "u": "sudo make uninstall \"$@\"", "T": "echo \"tests/runtests tests/test_cookie.sh tests/test_install.sh\" \"$@\"", "V": "echo \"cookie\" \"$@\"", "t": "make check \"$@\""} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | marketing.txt 2 | bashlibs/* 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/bashlibs"] 2 | path = lib/bashlibs 3 | url = https://github.com/bbugyi200/bashlibs.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | sudo: required 4 | 5 | install: ./scripts/install-deps 6 | 7 | script: ./tests/runtests 8 | 9 | os: linux 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - libcurl4-openssl-dev 15 | - libelf-dev 16 | - libdw-dev 17 | - cmake 18 | 19 | after_success: | 20 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 21 | tar xzf master.tar.gz && 22 | cd kcov-master && 23 | mkdir build && 24 | cd build && 25 | cmake .. && 26 | make && 27 | sudo make install && 28 | cd ../.. && 29 | rm -rf kcov-master && 30 | mkdir -p coverage && 31 | kcov coverage ./tests/runtests && 32 | bash <(curl -s https://codecov.io/bash) 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. This project adheres to 4 | [Semantic Versioning](https://semver.org/), though minor breaking changes can happen in minor 5 | releases. 6 | 7 | ### Unreleased 8 | 9 | Added: 10 | 11 | * Will now automatically copy a full directory if one is given as a target. 12 | 13 | Changed: 14 | 15 | * TEMPLATE command-line option is now positional. 16 | 17 | 18 | ### v0.2.0 (2019-01-22) 19 | 20 | Added: 21 | 22 | * The `--remove` option. 23 | * Upgraded the startline template statements so now vim will identify the column number of the statement as well as the line number. In effect, cookie is now able to start vim with the cursor positioned at the exact spot where the startline statement was. 24 | * A ZSH completion script to the GitHub repository and into the standard `install` rule for the project's Makefile. 25 | * The `--mode` option for setting file mode bits. 26 | 27 | Changed: 28 | 29 | * The syntax for startline template statements (cookie now uses `{% INSERT %}` and `{% NORMAL %}`). 30 | * The `-T` option to `-t`. 31 | 32 | Removed: 33 | 34 | * The `--executable` option 35 | 36 | Fixed: 37 | 38 | * Only require the `-f` flag when the target is going to be executable. 39 | 40 | ### v0.1.1 (2018-11-18) 41 | 42 | Fixed: 43 | 44 | * When user is in root dir, default subdir should be used. 45 | * Spaces in template variables should be optional. 46 | * With variables with repeated occurrences in the template, cookie was 47 | forgetting the variables value and thus prompting the user repeatedly 48 | for the same variable. 49 | * `EXEC_HOOK_CMD` was not evaluating `${TARGET}`. 50 | * Can now use absolute path with `TARGET` argument. 51 | 52 | ### v0.1.0 (2018-11-13) 53 | 54 | * First Release 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, Bryan M Bugyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | PREFIX ?= /usr 4 | BINDIR ?= $(PREFIX)/bin 5 | 6 | bindir=$(DESTDIR)/$(BINDIR) 7 | runtests=tests/runtests 8 | bashlibs=lib/bashlibs 9 | project=cookie 10 | 11 | define update-bashlibs 12 | git submodule update --init 13 | git submodule update --remote $(bashlibs) 14 | endef 15 | 16 | 17 | .PHONY: help 18 | help: ## Print this message. 19 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort 20 | 21 | .PHONY: install 22 | install: install-bashlibs install-zsh $(bindir) $(project) ## Install cookie. 23 | cp $(project) $(bindir)/$(project) 24 | chmod +x $(bindir)/$(project) 25 | 26 | .PHONY: install-bashlibs 27 | install-bashlibs: ## Install the bashlibs library. 28 | ifeq (,$(wildcard /usr/bin/gutils.sh)) 29 | $(call update-bashlibs) 30 | $(MAKE) -C $(bashlibs) install 31 | endif 32 | 33 | .PHONY: install-zsh 34 | install-zsh: ## Install ZSH completion function. 35 | @mkdir -p $(DESTDIR)/$(PREFIX)/share/zsh/site-functions/ 36 | cp ./scripts/zsh/_cookie $(DESTDIR)/$(PREFIX)/share/zsh/site-functions/ 37 | 38 | $(bindir): 39 | @mkdir -p $(bindir) 40 | 41 | .PHONY: uninstall 42 | uninstall: ## Uninstall cookie. 43 | @rm -f $(bindir)/$(project) 44 | 45 | .PHONY: uninstall-all 46 | uninstall-all: uninstall ## Uninstall cookie and all of its dependencies. 47 | $(call update-bashlibs) 48 | $(MAKE) -C $(bashlibs) uninstall 49 | 50 | .PHONY: test check 51 | test: check 52 | check: $(runtests) ## Run all tests. 53 | $(call update-bashlibs) 54 | ./$(runtests) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cookie [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Stop%20repeating%20yourself!%20Cookie%20templates%20make%20writing%20scripts,%20LaTeX%20documents,%20Makefiles,%20and%20other%20one-off%20files%20easier%20than%20ever!&url=https://github.com/bbugyi200/funky&via=bryan_bugyi&hashtags=Linux,commandlineftw,developers) 2 | 3 | **Stop repeating yourself! Cookie templates make writing scripts, LaTeX documents, Makefiles, and other one-off files easier than ever!** 4 | 5 | [![Build Status](https://travis-ci.org/bbugyi200/cookie.svg?branch=master)](https://travis-ci.org/bbugyi200/cookie) [![codecov](https://codecov.io/gh/bbugyi200/cookie/branch/master/graph/badge.svg)](https://codecov.io/gh/bbugyi200/cookie) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | ![demo] 8 | 9 | ## Usage 10 | ``` 11 | Usage: cookie [-d] [-D TARGET_DIR] [-f] [-m MODE] [-q] [-v] [-x] TEMPLATE [TARGET] 12 | cookie -c 13 | cookie -e TEMPLATE 14 | cookie -h 15 | cookie -l [TEMPLATE] 16 | cookie -r TEMPLATE 17 | 18 | Initializes a new file (TARGET) using a predefined template (TEMPLATE). 19 | The target file can be a new script, configuration file, markup file, etc.... 20 | After the target file has been initialized, it is opened for editing using the 21 | system's default editor. 22 | 23 | Positional Arguments: 24 | TARGET The name of the file to initialize. 25 | 26 | Optional Arguments: 27 | -d | --debug 28 | Enable debug mode. 29 | 30 | -c | --config 31 | Edit the configuration file. 32 | 33 | -D DIR | --bin-subdir DIR 34 | Initialize TARGET into DIR, which should be a subdirectory of the 35 | default bin directory (see the configuration file). 36 | 37 | -e TEMPLATE | --edit TEMPLATE 38 | Add / edit cookie template. 39 | 40 | -f | --force 41 | Force TARGET initialization to be relative to the current 42 | directory. This option essentially overrides the ROOT_DIR 43 | configuration setting. Enabled by default for non-executable 44 | targets. 45 | 46 | -h | --help 47 | View this help message. 48 | 49 | -l [TEMPLATE] | --list [TEMPLATE] 50 | If TEMPLATE is provided, output template contents to STDOUT. 51 | Otherwise, list available templates. 52 | 53 | -m MODE | --mode MODE 54 | Sets file mode bits. Accepts any form for MODE that is recognized 55 | by the 'chmod' command. 56 | 57 | -r TEMPLATE | --remove TEMPLATE 58 | Delete cookie template. 59 | 60 | -q | --quiet 61 | Just initialize the new script without opening it up in an editor. 62 | 63 | -v | --verbose 64 | Enable verbose output. 65 | 66 | -x 67 | Make TARGET executable. Equivalent to '-m +x'. 68 | ``` 69 | 70 | ## Templates 71 | 72 | Templates are stored in the directory specified by `$COOKIE_DIR` which, if not specified in the configuration file (see the [Configuration](#config) section), defaults to `~/.cookiecutters` (the same directory used by `cookiecutter` to store templates). 73 | 74 | See my personal [templates] for examples on how you can use templates. 75 | 76 | ### Template Variables and Statements 77 | While not as full-featured as the [jinja] template engine that [cookiecutter] uses, there are a few special variables and statements available. The syntax for these will be familiar if you have used [jinja] in the past. 78 | 79 | #### Variable Substitution 80 | cookie also recognizes template variables of the form: 81 | ``` 82 | {{ foobar }} 83 | ``` 84 | This string will be replaced by the value of the environment variable `foobar` if it exists. Otherwise, the user will be prompted to provide a value for `foobar` on the command-line. 85 | 86 | To ensure compatibility with files in cookiecutter templates, you may also preface the variable with `cookiecutter` followed by a period. Hence, the following statement is equivalent to the one discussed above: 87 | ``` 88 | {{ cookiecutter.foobar }} 89 | ``` 90 | 91 | #### Mark Start Point for Editing (only works when vim is set as the default system editor) 92 | If the following statement is found in the template, vim will start with the cursor positioned on the line and column of the first curly brace (after removing the statement) and will start in INSERT mode: 93 | ``` 94 | {% INSERT %} 95 | ``` 96 | 97 | The following statement does the same thing but will start vim in NORMAL mode (vim's default behavior): 98 | ``` 99 | {% NORMAL %} 100 | ``` 101 | 102 | ## Configuration 103 | 104 | The configuration file can be found at `$XDG_CONFIG_HOME/cookie/config`. The following options are available: 105 | 106 | ``` ini 107 | # The target file will be initialized in a location relative to this directory 108 | # unless you specify the `-f` option. In which case the target file will be 109 | # initialized relative to the current directory. 110 | # 111 | # Defaults to "./" (the current directory). 112 | ROOT_DIR= 113 | 114 | # The target file is initialized in $ROOT_DIR/$DEFAULT_TARGET_DIR 115 | # unless the `-D {DIR}` option is used. In which case the target file will 116 | # be initialized to $ROOT_DIR/{DIR}. 117 | DEFAULT_TARGET_DIR= 118 | 119 | # If specified, this command is evaluated after (and if) the target file 120 | # has its executable bit set. This can be used to create symlinks to 121 | # the target file (using `stow`, for example). 122 | # 123 | # The $TARGET variable, which contains the full path of the target file, 124 | # will be injected into the environment of this command. 125 | EXEC_HOOK_CMD= 126 | 127 | # The directory used to store cookie templates. 128 | # 129 | # Defaults to "~/.cookiecutters". 130 | COOKIE_DIR= 131 | ``` 132 | 133 | ## Using Shell Aliases / Functions 134 | 135 | You can of course run `cookie` directly, but I have not found that to 136 | be very convenient. Instead, I have created a variety of shell aliases and 137 | functions which serve as custom initialization commands that are specific to a 138 | single goal and filetype. Here are a few examples: 139 | 140 | ``` bash 141 | alias ainit='cookie -t template.awk -D awk -x' 142 | alias binit='cookie -t minimal.sh -x' 143 | alias Binit='cookie -t full.sh -x' 144 | hw() { ASSIGNMENT_NUMBER="$1" cookie -t hw.tex "${@:2}" HW"$1"/hw"$1".tex; } 145 | alias minit='cookie -t c.make Makefile' 146 | alias mtinit='cookie -t gtest.make Makefile' 147 | alias pyinit='cookie -t template.py -x' 148 | pytinit() { cookie -t pytest.py test_"$1".py; } 149 | alias texinit='cookie -t template.tex' 150 | ``` 151 | 152 | ## Examples 153 | 154 | Let us now take a look at a few examples of how cookie might be useful. 155 | 156 | For reference, my personal cookie templates can be found [here][templates] and these are the configuration settings that I use: 157 | ``` bash 158 | PARENT_BIN_DIR="/home/bryan/Dropbox/bin" 159 | DEFAULT_BIN_SUBDIR="main" 160 | EXEC_HOOK_CMD=/usr/local/bin/clinks 161 | ``` 162 | where `/home/bryan/Dropbox/bin` is a home for [this][scripts] GitHub repository. (The [clinks] script wraps a bunch of [stow] commands which makes creating symlinks to a system `bin` folder a walk in the park while still keeping my scripts organized the way I like in my filesystem.) 163 | 164 | To initialize a minimal bash script named `foo` into the `/home/bryan/Dropbox/main` directory (where I keep most of my scripts), I could run the following command: 165 | ``` 166 | binit foo 167 | ``` 168 | Suppose instead that I wanted to initialize a new productivity script named `bar` into the `/home/bryan/Dropbox/GTD` directory. Furthermore, suppose that I know that `bar` might get complicated (and thus needs to scale well). I could then choose to run 169 | ``` 170 | Binit -D GTD bar 171 | ``` 172 | to initialize a full featured bash script (bells and whistles included) into the `/home/bryan/Dropbox/GTD` directory. 173 | 174 | ## Advanced Usage 175 | 176 | I wrote a short [blog post][blog] describing a few of cookie's more advanced features. 177 | 178 | ## Similar Projects 179 | 180 | * [cookiecutter] - A command-line utility that creates projects from cookiecutters (project templates). 181 | * [j2cli] - Jinja2 Command-Line Tool. 182 | * [plop] - Micro-generator framework that makes it easy for an entire team to create files with a level of uniformity. 183 | 184 | ## Installation 185 | 186 | #### Root Installation 187 | If you have root permissions on your machine, installation is as simple as cloning the repository with `git clone https://github.com/bbugyi200/cookie`, traveling into the project directory (`cd cookie`), and then running `sudo make install`. 188 | 189 | #### User Installation 190 | If you do not have root permissions on your machine, you can still install cookie by using an alternate bin directory. This can be accomplished, for example, by cloning the repository and using `cd` to travel to the repository directory (as described in the previous section) and then running the following command: 191 | ``` 192 | make DESTDIR=/home//.local PREFIX= install 193 | ``` 194 | where `` should be replaced with your username. Keep in mind that, for this to work, the `/home//.local/bin` directory must be added to your system's path. 195 | 196 | #### macOS 197 | 198 | MacOS uses a different version of getopt than Linux does. You can install a GNU version of getopt and set it as the default getopt for your system using brew: 199 | 200 | ``` 201 | brew install gnu-getopt 202 | brew link --force gnu-getopt 203 | ``` 204 | See this [Stack Overflow answer](https://stackoverflow.com/questions/12152077/how-can-i-make-bash-deal-with-long-param-using-getopt-command-in-mac) before modifying your `gnu-getopt`. 205 | 206 | #### ZSH Completion 207 | 208 | The appropriate completion function should be installed automatically. If necessary, however, you can enable ZSH command-line completion manually by copying the [\_cookie][zsh-completion] file to a directory listed by your system's `$fpath` variable (normally the `/usr/share/zsh/site-functions` directory works). 209 | 210 | [blog]: https://bryanbugyi.com/blog/tips-and-tricks-for-using-cookie/ 211 | [logo]: https://raw.githubusercontent.com/bbugyi200/cookie/master/img/logo.png 212 | [demo]: https://raw.githubusercontent.com/bbugyi200/cookie/master/img/demo.gif "Cookie Demonstration GIF" 213 | [jinja]: https://github.com/pallets/jinja 214 | [cookiecutter]: https://github.com/audreyr/cookiecutter 215 | [scripts]: https://github.com/bbugyi200/scripts 216 | [clinks]: https://github.com/bbugyi200/scripts/blob/master/main/clinks 217 | [templates]: https://github.com/bbugyi200/dotfiles/tree/master/.cookiecutters 218 | [stow]: https://www.gnu.org/software/stow/manual/stow.html 219 | [travis]: https://travis-ci.org/bbugyi200/cookie.svg?branch=master 220 | [codecov]: https://codecov.io/gh/bbugyi200/cookie/branch/master/graph/badge.svg 221 | [j2cli]: https://github.com/kolypto/j2cli 222 | [zsh-completion]: https://github.com/bbugyi200/cookie/blob/master/scripts/zsh/_cookie 223 | [plop]: https://github.com/amwmedia/plop 224 | -------------------------------------------------------------------------------- /cookie: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC1090 4 | 5 | read -r -d '' doc <<-EOM 6 | Initializes a new file (TARGET) using a predefined template (TEMPLATE). 7 | The target file can be a new script, configuration file, markup file, etc.... 8 | After the target file has been initialized, it is opened for editing using the 9 | system's default editor. 10 | EOM 11 | 12 | # ---------- Source Libraries / Configs ---------- 13 | # ===== Global Utilities ===== 14 | source bugyi.sh 15 | 16 | [ -d "${MY_XDG_CONFIG}" ] || mkdir -p "${MY_XDG_CONFIG}" 17 | 18 | # ===== Configuration File ===== 19 | read -r -d '' default_config << "EOM" 20 | # The target file will be initialized in a location relative to this directory 21 | # unless you specify the `-f` option. In which case the target file will be 22 | # initialized relative to the current directory. 23 | # 24 | # Defaults to "./" (the current directory). 25 | ROOT_DIR= 26 | 27 | # The target file is initialized in $ROOT_DIR/$DEFAULT_TARGET_DIR 28 | # unless the `-D {DIR}` option is used. In which case the target file will 29 | # be initialized to $ROOT_DIR/{DIR}. 30 | DEFAULT_TARGET_DIR= 31 | 32 | # If specified, this command is evaluated after (and if) the target file 33 | # has its executable bit set. This can be used to create symlinks to 34 | # the target file (using `stow`, for example). 35 | # 36 | # The $TARGET variable, which contains the full path of the target file, 37 | # will be injected into the environment of this command. 38 | EXEC_HOOK_CMD= 39 | 40 | # The directory used to store cookie templates. 41 | # 42 | # Defaults to "~/.cookiecutters". 43 | COOKIE_DIR= 44 | EOM 45 | 46 | # LCOV_EXCL_START 47 | config_file="${MY_XDG_CONFIG}"/config 48 | if [[ -f "${config_file}" ]]; then 49 | source "${config_file}" 50 | else 51 | imsg "Configruation file has been initialized." 52 | echo "${default_config}" > "${config_file}" 53 | fi 54 | # LCOV_EXCL_STOP 55 | 56 | # Set default bin directory if not specified in config file. 57 | if [[ -z "${ROOT_DIR}" ]]; then 58 | ROOT_DIR=./ 59 | fi 60 | 61 | if [[ -z "${COOKIE_DIR}" ]]; then 62 | COOKIE_DIR="${HOME}"/.cookiecutters 63 | fi 64 | 65 | # ---------- Traps ---------- 66 | function exit_handler() { 67 | EC="$1"; shift 68 | created_by_me="$1"; shift 69 | dest_dir="$1"; shift 70 | 71 | if [[ "${created_by_me}" = true && "${EC}" -ne 0 ]]; then 72 | dmsg "Removing directory: ${dest_dir}" 73 | rm -rf "${dest_dir}" 74 | fi 75 | } 76 | 77 | trap 'exit_handler $? ${parent_dir_created} ${dest_dir}' EXIT 78 | 79 | # ---------- Function Definitions ---------- 80 | function main() { 81 | parse_args "$@" 82 | 83 | get_dest_dir "${target}" "${force}" 84 | 85 | full_target="${dest_dir}"/"${target}" 86 | full_template="${COOKIE_DIR}"/"${template}" 87 | if ! [[ -f "${full_template}" ]]; then 88 | if [[ -d "${full_template}" ]]; then 89 | cp -r "${full_template}" "${full_target}" 90 | imsg "Initializing the '${full_target}' directory." 91 | exit 0 92 | fi 93 | die "Template does not exist: ${full_template}" 94 | fi 95 | 96 | # ===== Initialize New Script ===== 97 | # >>> Copy Template Contents to New Script 98 | if ! [[ -f "${full_target}" ]]; then 99 | imsg "Initializing the '${full_target}' script." 100 | cp "${full_template}" "${full_target}" 101 | else 102 | imsg "The '${full_target}' script already exists." 103 | maybe_edit "$(editor_cmd "" "" "NORMAL" "${full_target}")" 104 | exit 0 105 | fi 106 | 107 | # >>> Set File Mode 108 | if [[ -n "${mode}" ]]; then 109 | sudo chmod "${mode}" "${full_target}" 110 | 111 | if [[ "${executable}" = true ]]; then 112 | if [[ -n "${EXEC_HOOK_CMD}" ]]; then 113 | TARGET="${full_target}" eval "imsg \"Running execute hook: ${EXEC_HOOK_CMD}\"" 114 | TARGET="${full_target}" eval "${EXEC_HOOK_CMD}" 115 | fi 116 | fi 117 | fi 118 | 119 | contents="$(cat "${full_target}")" 120 | template_engine "${contents}" 121 | echo "${new_contents}" > "${full_target}" 122 | 123 | maybe_edit "$(editor_cmd "${start_line}" "${start_col}" "${mode}" "${full_target}")" 124 | } 125 | 126 | function maybe_edit() { 127 | if [[ "${quiet}" != true ]]; then 128 | eval "$@" 129 | fi 130 | } 131 | 132 | function parse_args() { 133 | eval set -- "$(getopt -o "d,c,D:,e:,f,F:,h,l,m:,r:,q,v,x" -l "config,debug,docs:,bin-subdir:,edit:,help,list,mode:,remove:,quiet,verbose,use-extension:" -- "$@")" 134 | 135 | # LCOV_EXCL_START 136 | export USAGE_GRAMMAR=( 137 | "[-d] [-D TARGET_DIR] [-f] [-m MODE] [-q] [-v] [-x] TEMPLATE [TARGET]" 138 | "-c" 139 | "-e TEMPLATE" 140 | "-h" 141 | "-l [TEMPLATE]" 142 | "-r TEMPLATE" 143 | ) 144 | # LCOV_EXCL_STOP 145 | 146 | # shellcheck disable=SC2153 147 | if [[ -n "${EDITOR}" ]]; then 148 | EDITOR="${EDITOR}" 149 | else 150 | EDITOR="vim" 151 | fi 152 | 153 | read -r -d '' help <<-EOM 154 | $(usage) 155 | 156 | ${doc} 157 | 158 | Positional Arguments: 159 | TARGET The name of the file to initialize. 160 | 161 | Optional Arguments: 162 | -d | --debug 163 | Enable debug mode. 164 | 165 | -c | --config 166 | Edit the configuration file. 167 | 168 | -D DIR | --bin-subdir DIR 169 | Initialize TARGET into DIR, which should be a subdirectory of the 170 | default bin directory (see the configuration file). 171 | 172 | -e TEMPLATE | --edit TEMPLATE 173 | Add / edit cookie template. 174 | 175 | -f | --force 176 | Force TARGET initialization to be relative to the current 177 | directory. This option essentially overrides the ROOT_DIR 178 | configuration setting. Enabled by default for non-executable 179 | targets. 180 | 181 | -h | --help 182 | View this help message. 183 | 184 | -l [TEMPLATE] | --list [TEMPLATE] 185 | If TEMPLATE is provided, output template contents to STDOUT. 186 | Otherwise, list available templates. 187 | 188 | -m MODE | --mode MODE 189 | Sets file mode bits. Accepts any form for MODE that is recognized 190 | by the 'chmod' command. 191 | 192 | -r TEMPLATE | --remove TEMPLATE 193 | Delete cookie template. 194 | 195 | -q | --quiet 196 | Just initialize the new script without opening it up in an editor. 197 | 198 | -v | --verbose 199 | Enable verbose output. 200 | 201 | -x 202 | Make TARGET executable. Equivalent to '-m +x'. 203 | EOM 204 | 205 | force=false 206 | template= 207 | target= 208 | debug=false 209 | verbose=false 210 | 211 | while [[ -n "$1" ]]; do 212 | case $1 in 213 | -c|--config ) 214 | "${EDITOR}" "${config_file}" 215 | exit 0 216 | ;; 217 | -d|--debug ) 218 | debug=true # LCOV_EXCL_LINE 219 | ;; 220 | -e|--edit ) 221 | shift 222 | "${EDITOR}" "${COOKIE_DIR}"/"$1" 223 | exit 0 224 | ;; 225 | -h|--help ) 226 | # LCOV_EXCL_START 227 | echo "${help}" 228 | exit 0 229 | # LCOV_EXCL_STOP 230 | ;; 231 | -l|--list ) 232 | list=true 233 | ;; 234 | -D|--bin-subdir ) 235 | shift 236 | target_dir="$1" 237 | ;; 238 | --docs ) 239 | # LCOV_EXCL_START 240 | shift 241 | eval "printf -- \"\${$1}\n\"" 242 | exit 0 243 | # LCOV_EXCL_STOP 244 | ;; 245 | -f ) 246 | force=true 247 | ;; 248 | -m|--mode ) 249 | shift 250 | mode="$1" 251 | ;; 252 | -r|--remove ) 253 | shift 254 | template="$1" 255 | remove=true 256 | ;; 257 | -q|--quiet ) 258 | quiet=true 259 | ;; 260 | -v|--verbose ) 261 | verbose=true # LCOV_EXCL_LINE 262 | ;; 263 | -x ) 264 | mode="+x" 265 | ;; 266 | -- ) 267 | shift 268 | break 269 | ;; 270 | esac 271 | shift 272 | done 273 | 274 | if [[ "${list}" = true ]]; then 275 | if [[ -n "$1" ]]; then 276 | template="$1"; shift 277 | full_template="${COOKIE_DIR}"/"${template}" 278 | [[ -f "${full_template}" ]] || die "Template does not exist: ${full_template}" 279 | cat "${full_template}" 280 | else 281 | for T in "${COOKIE_DIR}"/*; do 282 | [[ -f "${T}" ]] && basename "${T}" 283 | done 284 | fi 285 | 286 | exit 0 287 | fi 288 | 289 | if [[ "${remove}" = true ]]; then 290 | full_template="${COOKIE_DIR}"/"${template}" 291 | [[ -f "${full_template}" ]] || die "Template does not exist: ${full_template}" 292 | read -n1 -p "Are you sure you want to delete the ${template} template? [y/n]: " choice 293 | if [[ "${choice}" == "y" ]]; then 294 | rm "${full_template}" 295 | printf "\n" 296 | imsg "The ${template} template has been deleted." 297 | fi 298 | exit 0 299 | fi 300 | 301 | if [[ "${mode}" == *"7"* || "${mode}" == *"+"*"x"* ]]; then 302 | executable=true 303 | else 304 | executable=false 305 | fi 306 | 307 | if [[ "${debug}" = true && "${verbose}" = true ]]; then 308 | # LCOV_EXCL_START 309 | PS4='$LINENO: ' 310 | set -x 311 | # LCOV_EXCL_STOP 312 | fi 313 | 314 | if [[ -z "$1" ]]; then 315 | die "$(usage)" 2 316 | fi 317 | 318 | template="$1"; shift 319 | 320 | if [[ -n "$1" ]]; then 321 | target="$1"; shift 322 | else 323 | target="$(basename "${template}")" 324 | fi 325 | } 326 | 327 | function get_dest_dir() { 328 | target="$1"; shift 329 | force="$1"; shift 330 | 331 | if [[ "${target}" == "/"* ]]; then 332 | dest_dir="$(dirname "${target}")" 333 | target="$(basename "${target}")" 334 | return 0 335 | fi 336 | 337 | # ===== Calculate Filesystem Paths ===== 338 | if [[ "$PWD" == "${ROOT_DIR}"/* && -z "${target_dir}" ]] || [[ "${force}" = true ]]; then 339 | dest_dir="$PWD" 340 | elif [[ -n "${target_dir}" ]]; then 341 | dest_dir="${ROOT_DIR}"/"${target_dir}" 342 | elif [[ -n "${DEFAULT_TARGET_DIR}" ]]; then 343 | dest_dir="${ROOT_DIR}"/"${DEFAULT_TARGET_DIR}" 344 | else 345 | dest_dir="${ROOT_DIR}" 346 | fi 347 | 348 | target_dir="$(dirname "${target}")" 349 | 350 | if [[ "${target_dir}" != "." ]]; then 351 | dest_dir="${dest_dir}"/"${target_dir}" 352 | target="$(basename "${target}")" 353 | fi 354 | 355 | if [[ "${dest_dir}" != "./"* ]]; then 356 | if ! [[ -d "${dest_dir}" ]]; then 357 | dmsg "Creating directory: ${dest_dir}" 358 | mkdir -p "${dest_dir}" 359 | parent_dir_created=true 360 | fi 361 | fi 362 | } 363 | 364 | function template_engine() { 365 | old_contents="$1"; shift 366 | new_contents="${old_contents}" 367 | 368 | # ===== Template Statements and Substitutions ===== 369 | # >>> START HERE 370 | istart_mark="{% INSERT %}" 371 | read -r iline icol <<< "$(get_start_line "${istart_mark}" "${old_contents}")" 372 | 373 | nstart_mark="{% NORMAL %}" 374 | read -r nline ncol <<< "$(get_start_line "${nstart_mark}" "${old_contents}")" 375 | 376 | new_contents="${new_contents//\{% INSERT %\}}" 377 | new_contents="${new_contents//\{% NORMAL %\}}" 378 | 379 | if [[ -n "${iline}" ]] && [[ "${iline}" -gt 0 ]]; then 380 | mode="INSERT" 381 | start_line="${iline}" 382 | start_col="${icol}" 383 | elif [[ -n "${nline}" ]] && [[ "${nline}" -gt 0 ]]; then 384 | mode="NORMAL" 385 | start_line="${nline}" 386 | start_col="${ncol}" 387 | fi 388 | 389 | # >>> Environment Variable Replacements 390 | grep_epttrn="{{[ ]*(.*?)[ ]*}}" 391 | sed_epttrn="{{[ ]*\([^ ]*\)[ ]*}}" 392 | 393 | exec 5>&0 # save STDIN 394 | while read evar; do 395 | evalue="$(eval "echo \"\$${evar}\"")" 396 | if [[ -z "${evalue}" ]]; then 397 | read -p "${evar}: " evalue <&5 398 | eval "${evar}=${evalue}" 399 | fi 400 | 401 | new_contents="$(echo "${new_contents}" | sed "s/{{[ ]*${evar}[ ]*}}/${evalue}/g")" 402 | new_contents="$(echo "${new_contents}" | sed "s/{{[ ]*cookiecutter.${evar}[ ]*}}/${evalue}/g")" 403 | done < <(echo "${new_contents}" | grep -P -o "${grep_epttrn}" | sed "s/${sed_epttrn}/\1/" | sed 's/cookiecutter\.//') # LCOV_EXCL_LINE 404 | } 405 | 406 | function get_start_line() { 407 | mark="$1"; shift 408 | contents="$1"; shift 409 | 410 | line="$(echo "${contents}" | grep -n "${mark}" | awk -F':' '{print $1}')" 411 | 412 | line_contents="$(echo "${contents}" | grep "${mark}")" 413 | col="$(awk -v line_contents="${line_contents}" 'BEGIN{print index(line_contents, "{")}')" 414 | 415 | printf "${line} ${col}\n" 416 | } 417 | 418 | function editor_cmd() { 419 | start_line="$1"; shift 420 | start_col="$1"; shift 421 | mode="$1"; shift 422 | full_target="$1"; shift 423 | 424 | if [[ "${EDITOR}" == *"vim"* ]]; then 425 | Vim_Opts=() 426 | if [[ -n "${start_line}" ]] && [[ -n "${start_col}" ]]; then 427 | Vim_Opts+=( "-c" "\"call cursor(${start_line},${start_col})\"" ) 428 | fi 429 | 430 | if [[ "${mode}" == "INSERT" ]]; then 431 | Vim_Opts+=( +startinsert ) 432 | fi 433 | 434 | echo "${EDITOR} ${Vim_Opts[*]} ${full_target}" 435 | else 436 | echo "${EDITOR} ${full_target}" 437 | fi 438 | } 439 | 440 | if [[ "${SCRIPTNAME}" == "cookie" ]]; then 441 | main "$@" # LCOV_EXCL_LINE 442 | fi 443 | -------------------------------------------------------------------------------- /img/demo.auto: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Autodemo specification for 'cookie' project demonstration. # 3 | ################################################################################ 4 | ((50))# This is a demo for cookie.<<1>>[CLEAR]((100))# We use the '-l' option to list all templates.<<1>>[CLEAR]cookie -l 5 | ((100))# Let's take a closer look at the 'full.sh' template.<<1>>[CLEAR]cookie -e full.sh 6 | :4 7 | <<0.5>>v$h<<1.5>>[ESC]:q 8 | ((100))# We'll now create a new script using that template.<<1>>[CLEAR]# We use the '-x' option to make the script executable.<<1>>[CLEAR]cookie -t full.sh -x demo 9 | <<2>>((50))This is a test bash script created for the 'cookie' demo.[ESC]<<1>>:wq 10 | demo -h 11 | ((100))# Did you notice how we used 'demo' instead of './demo'?<<1>>[CLEAR]# We were able to do that because the 'demo' script was initialized in a directory on our system's PATH.<<1>>[CLEAR]# We could have instead used the '-f' option to force cookie to use the current directory.<<1>>[CLEAR]((25))clear && rm ~/Dropbox/bin/main/demo 12 | ((100))# Next, we'll explore the 'hw.tex' template.<<1>>[CLEAR]cookie -e hw.tex 13 | :3 14 | <<0.5>>((50))^12lv68l<<2>>[ESC]:q 15 | ((100))# Did you notice the template variables in the 'hw.tex' template?<<1>>[CLEAR]# Let's see how they work.<<1>>[CLEAR]cookie -t hw.tex -f hw5.tex 16 | 5 17 | CS 18 | 314 19 | 03 20 | [ESC]:3 21 | <<0.5>>^12lv12l<<2>>[ESC]:wq 22 | ((100))# We can also use environment variables to set those values.<<1>>[CLEAR]((25))clear && rm hw5.tex 23 | 24 | -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbugyi200/cookie/e4315c40cb10488041ac571a32ad145d1abbd6ab/img/demo.gif -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbugyi200/cookie/e4315c40cb10488041ac571a32ad145d1abbd6ab/img/logo.png -------------------------------------------------------------------------------- /scripts/install-deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir lib > /dev/null 2>&1 4 | 5 | make update-bashlibs 6 | cp lib/bashlibs/gutils.sh ./ 7 | 8 | git clone https://github.com/kward/shunit2 lib/shunit2 9 | cp lib/shunit2/shunit2 ./ 10 | -------------------------------------------------------------------------------- /scripts/zsh/_cookie: -------------------------------------------------------------------------------- 1 | #compdef cookie 2 | 3 | args=( 4 | "-c[edit config file]" 5 | "-d[debug output]" 6 | "-D[bin-subdir]" ":SUBDIR" 7 | "-e[edit template]:filename:_files -W ${HOME}/.cookiecutters" 8 | "-f[force relative directory]" 9 | "-h[help menu]" 10 | "-l[list templates]::filename:_files -W ${HOME}/.cookiecutters" 11 | "-m[set file mode]:mode:_file_modes" 12 | "-r[remove template]:filename:_files -W ${HOME}/.cookiecutters" 13 | "-x[make executable]" 14 | "-v[verbose output]" 15 | ) 16 | 17 | _arguments -C -s "${args[@]}" 18 | _alternative "files:filename:_files -W ${HOME}/.cookiecutters" 19 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.0 3 | commit = True 4 | tag = True 5 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Unit Tests 2 | These tests are written with the help of the [shunit2] test framework. 3 | 4 | The following command will run all of the tests: 5 | ``` bash 6 | make check 7 | ``` 8 | 9 | [shunit2]: https://github.com/kward/shunit2 10 | -------------------------------------------------------------------------------- /tests/runtests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EC=0 4 | 5 | printf "%s\n" "------- test_cookie.sh --------" 6 | if ! ./tests/test_cookie.sh; then 7 | EC=1 8 | fi 9 | 10 | printf "\n%s\n" "------- test_install.sh --------" 11 | if ! sudo ./tests/test_install.sh; then 12 | EC=1 13 | fi 14 | 15 | exit "${EC}" 16 | -------------------------------------------------------------------------------- /tests/test_cookie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2154 4 | 5 | source ./cookie 6 | 7 | my_target="myTarget" 8 | my_template="myTemplate" 9 | 10 | tmpdir=/tmp/foodir 11 | tmpdir2=/tmp/bardir 12 | foobar=/tmp/foobar 13 | foobaz=/tmp/foobaz 14 | fake_temp=cookie.tmp 15 | 16 | setUp() { 17 | echo "Fake Template" > /tmp/"${fake_temp}" 18 | mkdir -p "${tmpdir}" &> /dev/null 19 | mkdir -p "${tmpdir2}" &> /dev/null 20 | } 21 | 22 | tearDown() { 23 | export ROOT_DIR= 24 | export DEFAULT_TARGET_DIR= 25 | export target_dir= 26 | 27 | rm -rf "${tmpdir}" 28 | rm -rf "${tmpdir2}" 29 | rm -rf "${foobar}" 30 | rm -rf "${foobaz}" 31 | rm -rf /tmp/"${fake_temp}" 32 | } 33 | 34 | ############################################################################### 35 | # Test parse_args # 36 | ############################################################################### 37 | test_parse_args__NOX_NOF() { 38 | parse_args "${my_template}" "${my_target}" &> /dev/null 39 | assertEquals 0 "$?" 40 | 41 | assertEquals "${template}" "${my_template}" 42 | assertEquals "${my_target}" "${target}" 43 | assertEquals false "${force}" 44 | } 45 | 46 | test_parse_args__X_NOF() { 47 | parse_args "${my_template}" "-x" "${my_target}" &> /dev/null 48 | assertEquals 0 "$?" 49 | 50 | assertEquals "${template}" "${my_template}" 51 | assertEquals "${my_target}" "${target}" 52 | assertEquals false "${force}" 53 | } 54 | 55 | test_parse_args__X_F() { 56 | parse_args "${my_template}" "-x" "-f" "${my_target}" &> /dev/null 57 | assertEquals 0 "$?" 58 | 59 | assertEquals "+x" "${mode}" 60 | assertEquals true "${force}" 61 | } 62 | 63 | test_parse_args__CONFIG() { 64 | export EDITOR="echo" 65 | output="$(parse_args -c)" 66 | assertEquals "${output##*/}" "config" 67 | } 68 | 69 | test_parse_args__EDIT_NO_EXISTS() { 70 | export EDITOR="echo" 71 | export COOKIE_DIR=/tmp 72 | 73 | assertEquals "/tmp/my_template" "$(parse_args -e my_template)" 74 | } 75 | 76 | test_parse_args__EDIT_EXISTS() { 77 | export EDITOR="echo" 78 | export COOKIE_DIR=/tmp 79 | 80 | assertEquals "${foobar}" "$(parse_args -e foobar)" 81 | } 82 | 83 | test_parse_args__REMOVE() { 84 | export COOKIE_DIR=/tmp 85 | 86 | assertTrue "${fake_temp} NEVER existed to begin with" "[[ -f /tmp/${fake_temp} ]]" 87 | (parse_args "-r" "${fake_temp}" < <(printf "y") &> /dev/null) 88 | assertFalse "${fake_temp} STILL exists" "[[ -f /tmp/${fake_temp} ]]" 89 | } 90 | 91 | ############################################################################### 92 | # Test get_dest_dir # 93 | ############################################################################### 94 | test_get_dest_dir__NO_CONFIG() { 95 | export ROOT_DIR=./ 96 | get_dest_dir "${my_target}" 97 | assertEquals "./" "${dest_dir}" 98 | } 99 | 100 | test_get_dest_dir__ROOT() { 101 | export ROOT_DIR=/tmp 102 | get_dest_dir "${my_target}" 103 | assertEquals "/tmp" "${dest_dir}" 104 | } 105 | 106 | test_get_dest_dir__ROOT_AND_TARGET() { 107 | export ROOT_DIR=/tmp 108 | export DEFAULT_TARGET_DIR=foobar 109 | 110 | get_dest_dir "${my_target}" 111 | assertEquals "${foobar}" "${dest_dir}" 112 | rm -rf "${foobar}" 113 | } 114 | 115 | test_get_dest_dir__ROOT_AND_TARGET_IN_ROOT() { 116 | export ROOT_DIR=/tmp 117 | export DEFAULT_TARGET_DIR=foobar 118 | 119 | cd /tmp || return 1 120 | 121 | get_dest_dir "${my_target}" 122 | assertEquals "${foobar}" "${dest_dir}" 123 | rm -rf "${foobar}" 124 | } 125 | 126 | test_get_dest_dir__ROOT_AND_TARGET_IN_ROOT_SUBDIR() { 127 | export ROOT_DIR=/tmp 128 | export DEFAULT_TARGET_DIR=foodir 129 | 130 | OLD_PWD=$PWD 131 | cd "${tmpdir2}" || return 1 132 | 133 | get_dest_dir "${my_target}" 134 | assertEquals "${tmpdir2}" "${dest_dir}" 135 | 136 | cd "$OLD_PWD" || return 1 137 | } 138 | 139 | test_get_dest_dir__ABSOLUTE_PATH() { 140 | export ROOT_DIR=/tmp 141 | get_dest_dir "/home/$HOME/my_target" 142 | 143 | assertEquals "/home/$HOME" "${dest_dir}" 144 | } 145 | 146 | ############################################################################### 147 | # Test editor_cmd # 148 | ############################################################################### 149 | test_editor_cmd__VIM() { 150 | export EDITOR="vim" 151 | read editor_cmd < <(editor_cmd "" "" "" "${my_target}") 152 | assertEquals "vim ${my_target}" "${editor_cmd}" 153 | } 154 | 155 | test_editor_cmd__VIM_NSTART() { 156 | export EDITOR="vim" 157 | read editor_cmd < <(editor_cmd "5" "15" "" "${my_target}") 158 | assertEquals "vim -c \"call cursor(5,15)\" ${my_target}" "${editor_cmd}" 159 | } 160 | 161 | test_editor_cmd__VIM_ISTART() { 162 | export EDITOR="vim" 163 | read editor_cmd < <(editor_cmd "5" "15" "INSERT" "${my_target}") 164 | assertEquals "vim -c \"call cursor(5,15)\" +startinsert ${my_target}" "${editor_cmd}" 165 | } 166 | 167 | test_editor_cmd__BAD_ARGS() { 168 | export EDITOR="vim" 169 | read editor_cmd < <(editor_cmd "0" "" "INSERT" "${my_target}") 170 | assertEquals "vim +startinsert ${my_target}" "${editor_cmd}" 171 | } 172 | 173 | test_editor_cmd__NOVIM() { 174 | export EDITOR="nano" 175 | read editor_cmd < <(editor_cmd "5" "15" "INSERT" "${my_target}") 176 | assertEquals "nano ${my_target}" "${editor_cmd}" 177 | } 178 | 179 | ############################################################################### 180 | # Test template_engine # 181 | ############################################################################### 182 | test_template_engine__START() { 183 | read -r -d '' old_contents <<-EOM 184 | FOO 185 | {% INSERT %} 186 | EOM 187 | 188 | template_engine "${old_contents}" 189 | 190 | IFS= read -r -d '' expected <<-EOM 191 | FOO 192 | EOM 193 | 194 | assertEquals "${expected}" "${new_contents}" 195 | 196 | read -r -d '' old_contents <<-EOM 197 | FOO 198 | {% NORMAL %} 199 | EOM 200 | 201 | template_engine "${old_contents}" 202 | assertEquals "${expected}" "${new_contents}" 203 | } 204 | 205 | test_template_engine__START_LINE_AND_COL_CHECK() { 206 | read -r -d '' contents <<-EOM 207 | FOO 208 | {% INSERT %} 209 | EOM 210 | 211 | template_engine "${contents}" 212 | assertEquals "2" "${start_line}" 213 | assertEquals "5" "${start_col}" 214 | 215 | read -r -d '' contents <<-EOM 216 | FOO 217 | 218 | BAR {% NORMAL %} { OTHER STUFF } 219 | EOM 220 | 221 | template_engine "${contents}" 222 | assertEquals "3" "${start_line}" 223 | assertEquals "7" "${start_col}" 224 | 225 | read -r -d '' contents <<-EOM 226 | FOO 227 | {% INSERT %} 228 | EOM 229 | 230 | template_engine "${contents}" 231 | assertEquals "2" "${start_line}" 232 | assertEquals "1" "${start_col}" 233 | } 234 | 235 | test_template_engine__ENVVAR() { 236 | read -r -d '' old_contents <<-EOM 237 | FOO 238 | {{ my_variable }} 239 | EOM 240 | export my_variable="BAR" 241 | template_engine "${old_contents}" 242 | read -r -d '' expected <<-EOM 243 | FOO 244 | BAR 245 | EOM 246 | 247 | assertEquals "${expected}" "${new_contents}" 248 | } 249 | 250 | test_template_engine__CC_ENVVAR() { 251 | read -r -d '' old_contents <<-EOM 252 | FOO 253 | {{ cookiecutter.my_variable }} 254 | EOM 255 | export my_variable="BAR" 256 | template_engine "${old_contents}" 257 | read -r -d '' expected <<-EOM 258 | FOO 259 | BAR 260 | EOM 261 | 262 | assertEquals "${expected}" "${new_contents}" 263 | } 264 | 265 | test_template_engine__ENVVAR_NOT_DEFINED() { 266 | export my_variable= 267 | read -r -d '' old_contents <<-EOM 268 | FOO 269 | {{ my_variable }} 270 | EOM 271 | 272 | read -r -d '' expected <<-EOM 273 | FOO 274 | BAR 275 | EOM 276 | 277 | template_engine "${old_contents}" < <(echo "BAR") 278 | 279 | assertEquals "${expected}" "${new_contents}" 280 | } 281 | 282 | test_template_engine__NO_SPACES() { 283 | read -r -d '' old_contents <<-EOM 284 | FOO 285 | {{my_variable}} 286 | EOM 287 | export my_variable="BAR" 288 | template_engine "${old_contents}" 289 | read -r -d '' expected <<-EOM 290 | FOO 291 | BAR 292 | EOM 293 | 294 | assertEquals "${expected}" "${new_contents}" 295 | } 296 | 297 | ############################################################################### 298 | # Test Exit Handler # 299 | ############################################################################### 300 | test_exit_handler__CREATED_ZERO_EC() { 301 | exit_handler 0 true "${tmpdir}" 302 | assertTrue "${tmpdir} is NOT a directory" "[[ -d ${tmpdir} ]]" 303 | } 304 | 305 | test_exit_handler__NOT_CREATED_NONZERO_EC() { 306 | exit_handler 1 false "${tmpdir}" 307 | assertTrue "${tmpdir} is NOT a directory" "[[ -d ${tmpdir} ]]" 308 | } 309 | 310 | test_exit_handler__CREATED_ZERO_EC() { 311 | exit_handler 1 true "${tmpdir}" 312 | assertFalse "${tmpdir} still exists" "[[ -d ${tmpdir} ]]" 313 | } 314 | 315 | ############################################################################### 316 | # Test main # 317 | ############################################################################### 318 | test_main() { 319 | export EDITOR=":" 320 | export PARENT_DIR=/tmp 321 | export DEFAULT_TARGET_DIR= 322 | export COOKIE_DIR=/tmp 323 | 324 | main "${fake_temp}" "-x" "foobar" &> /dev/null 325 | assertTrue "foobar is NOT a file." "[ -f foobar ]" 326 | assertTrue "foobar is NOT executable." "[ -x foobar ]" 327 | } 328 | 329 | test_main__LIST() { 330 | export COOKIE_DIR="${tmpdir2}" 331 | 332 | mkdir -p "${COOKIE_DIR}" 333 | 334 | template1="${COOKIE_DIR}"/footemp 335 | template2="${COOKIE_DIR}"/bartemp 336 | 337 | echo "FOOBAR" > "${template1}" 338 | touch "${template2}" 339 | 340 | read -r -d '' expected <<-EOM 341 | bartemp 342 | footemp 343 | EOM 344 | 345 | assertEquals "${expected}" "$(main "-l")" 346 | assertEquals "FOOBAR" "$(main "-l" "footemp")" 347 | } 348 | 349 | test_main__TEMPLATE_NOT_EXIST() { 350 | (main fake_template.sh foobar &> /dev/null); EC=$? 351 | assertTrue "cookie fails to die when the template doesn't exist" "[[ ${EC} -ne 0 ]]" 352 | } 353 | 354 | test_main__USAGE_ERROR() { 355 | (main &> /dev/null); EC=$? 356 | assertTrue "cookie failed to die with a usage message" "[[ ${EC} -eq 2 ]]" 357 | } 358 | 359 | test_main__TARGET_ALREADY_EXISTS() { 360 | export COOKIE_DIR=/tmp 361 | 362 | echo ":)" > "${foobar}" 363 | 364 | (main "${fake_temp}" "${foobar}" &> /dev/null); EC=$? 365 | assertTrue "cookie fails when the target already exists" "[[ ${EC} -eq 0 ]]" 366 | assertEquals ":)" "$(cat ${foobar})" 367 | } 368 | 369 | test_main__EXEC_HOOK_CMD_X() { 370 | export EXEC_HOOK_CMD="echo \"Hook Output: \${TARGET}\" > ${foobaz}" 371 | (main "${fake_temp}" -x "${foobar}" &> /dev/null) 372 | 373 | assertEquals "Hook Output: ${foobar}" "$(cat "${foobaz}")" 374 | } 375 | 376 | test_main__EXEC_HOOK_CMD_MASK() { 377 | export EXEC_HOOK_CMD="echo \"Hook Output: \${TARGET}\" > ${foobaz}" 378 | (main "${fake_temp}" -m 755 "${foobar}" &> /dev/null) 379 | 380 | assertEquals "Hook Output: ${foobar}" "$(cat "${foobaz}")" 381 | } 382 | 383 | test_main__BIN_SUBDIR() { 384 | export ROOT_DIR=/tmp 385 | 386 | (main "${fake_temp}" "-D" "foodir" "foobar" &> /dev/null); EC=$? 387 | assertTrue "cookie fails when using -D option" "[[ ${EC} -eq 0 ]]" 388 | assertTrue "cookie does not respect the -D option" "[[ -f /tmp/foodir/foobar ]]" 389 | } 390 | 391 | test_main__DEEP_TARGET() { 392 | export ROOT_DIR=/tmp 393 | 394 | (main "${fake_temp}" "foo/bar" &> /dev/null); EC=$? 395 | assertTrue "cookie fails when using deep target" "[[ ${EC} -eq 0 ]]" 396 | assertTrue "cookie fails to initialize deep target" "[[ -f /tmp/foo/bar ]]" 397 | 398 | rm -rf "/tmp/foo/bar" 399 | } 400 | 401 | test_main__MODE() { 402 | export ROOT_DIR=/tmp 403 | 404 | (main "${fake_temp}" "-m" "666" "${foobar}"); EC=$? 405 | assertTrue "cookie fails when using --mode option" "[[ ${EC} -eq 0 ]]" 406 | assertEquals "666" "$(stat -c "%a" "${foobar}")" 407 | } 408 | 409 | test_main__TEMPLATE_IS_DIRECTORY() { 410 | export ROOT_DIR=/tmp 411 | 412 | (main "$(basename "${tmpdir}")" "${foobar}"); EC=$? 413 | 414 | assertTrue "cookie fails to copy template directory" "[[ -d ${foobar} ]]" 415 | } 416 | 417 | test_main_NO_TARGET() { 418 | export ROOT_DIR=/var/tmp 419 | 420 | (main "${fake_temp}"); EC=$? 421 | assertTrue "cookie fails when target is not given" "[[ -f /var/tmp/$(basename ${fake_temp}) ]]" 422 | } 423 | 424 | source shunit2 425 | -------------------------------------------------------------------------------- /tests/test_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EC=0 4 | capout=/tmp/cookie.capout 5 | 6 | ########### 7 | # Setup # 8 | ########### 9 | make uninstall-all &> /dev/null 10 | 11 | ################## 12 | # make install # 13 | ################## 14 | make install &> "${capout}" 15 | 16 | error_msg= 17 | 18 | if ! [[ -f /usr/bin/cookie && -f /usr/bin/gutils.sh ]]; then 19 | error_msg="/usr/bin/cookie does not exist" 20 | elif ! [[ -x /usr/bin/cookie ]]; then 21 | error_msg="/usr/bin/cookie is not executable" 22 | fi 23 | 24 | if [[ -n "${error_msg}" ]]; then 25 | echo "[FAIL] make install: ${error_msg}" 26 | EC=1 27 | else 28 | echo "[PASS] make install" 29 | fi 30 | 31 | ######################## 32 | # make uninstall-all # 33 | ######################## 34 | make uninstall-all &> "${capout}" 35 | 36 | error_msg= 37 | 38 | if [[ -f /usr/bin/cookie ]]; then 39 | error_msg="/usr/bin/cookie still exists" 40 | fi 41 | 42 | if [[ -f /usr/bin/gutils.sh ]]; then 43 | error_msg="/usr/bin/gutils.sh still exists" 44 | fi 45 | 46 | if [[ -n "${error_msg}" ]]; then 47 | echo "[FAIL] make uninstall-all: ${error_msg}" 48 | EC=1 49 | else 50 | echo "[PASS] make uninstall-all" 51 | fi 52 | 53 | ############## 54 | # Teardown # 55 | ############## 56 | if [[ "${EC}" -ne 0 ]]; then 57 | printf "\n------- Captured Output -------\n" 58 | cat "${capout}" 59 | fi 60 | 61 | make install &> /dev/null # >>> TEARDOWN 62 | 63 | rm "${capout}" 64 | exit "${EC}" 65 | --------------------------------------------------------------------------------