├── LICENSE ├── README.md └── zk /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zettelkasten 2 | A note taking application following the Zettelkasten method. 3 | 4 | ## Installation 5 | ```bash 6 | curl -s -o /usr/local/bin/zk https://raw.githubusercontent.com/AndrewCopeland/zettelkasten/master/zk && chmod +x /usr/local/bin/zk 7 | ``` 8 | 9 | ## Getting started 10 | To initialize the zettelkasten config just run `zk init`. 11 | ``` 12 | $ zk init 13 | Zettelkasten Directory: /Users/acopeland/git/AndrewCopeland/zettelkasten/kasten 14 | Zettelkasten auto git push[yes/no]: no 15 | Zettelkasten git directory(optional): 16 | Default command line text editor[vi/emacs/nano]: vi 17 | $ 18 | ``` 19 | 20 | ## Usage 21 | ``` 22 | Usage: zk COMMAND 23 | 24 | A note taking application following the Zettelkasten method. 25 | 26 | Commands: 27 | n, new Create a new zettel and use default editor 28 | nv, new-vi Create a new zettel and edit with vi 29 | ne, new-emacs Create a new zettel and edit with emacs 30 | nc, new-code Create a new zettel and edit with Visual Studio Code 31 | o, open Open an existing zettel in default editor 32 | ov, open-vi Open an existing zettel in vi 33 | oe, open-emacs Open an existing zettel in emacs 34 | oc, open-code Open an existing zettel in Visual Studio Code 35 | ob, open-browser Open an existing zettel in github using OS default browser 36 | p, push Push zettels to GitHub 37 | c, cat Print an existing zettel to terminal 38 | t, tag Search zettels for a specific tag 39 | s, search Search for a sub string within all zettels 40 | ls, list List all zettels 41 | rm, remove Remove a zettel 42 | ln, link Link 2 zettels together 43 | rml, rm-link Remove a link between 2 zettels 44 | home Display zettelkasten home directory 45 | init Initialize the $HOME/.zettelkasten 46 | variables List all variables defined in $HOME/.zettelkasten 47 | update Update to latest zk script 48 | add, add-media Add specific media (images, music, pdf, etc) to kasten 49 | lsm, list-media List all media zettels 50 | sync Sync local zettelkasten with git zettelkasten 51 | ``` 52 | 53 | To create a zettel perform the following. This will open up vi and once you are done save the zettel. 54 | ```bash 55 | zk new "my first zettel" 56 | ``` 57 | 58 | To open an existing zettel perform the following. This will open up vi on the existing zettel, you can update zettels using this command. This command will search for the zettel using the substring `my-first-zettel` and will open it if only 1 result was found. 59 | ```bash 60 | zk open my-first-zettel 61 | ``` 62 | 63 | To link zettels together perform the following. 64 | ```bash 65 | zk link my-first-zettel my-second-zettel 66 | ``` 67 | 68 | ## Zettelkasten configuration 69 | When executing `zk init` a configuration file is created in your home directory called `.zettelkasten`. 70 | This file contains variables that are used by `zk`. An explaination of each of these variables are below: 71 | - `ZETTELKASTEN_DIR`: Directory in which all zettels reside 72 | - `ZETTELKASTEN_AUTO_GIT_PUSH`: Auto push zettels to git when created, updated or deleted. Valid values are `yes` or `no`. 73 | - `ZETTELKASTEN_GIT_DIR`: Set this variable if the kasten resides within a specific folder in your git repo. This is used when opening a zettel in github. 74 | - `ZETTELKASTEN_ENVIRONMENT`: The OS environment in which the zk application is running. This value should be set automatically by `zk init`. Valid values are `osx` or `wsl`. 75 | - `ZETTELKASTEN_MD5_COMMAND`: The command used when md5 hashing zettels. This value should be set automatically by `zk init` depending on the `ZETTELKASTEN_ENVIRONMENT`. 76 | - `ZETTELKASTEN_BASENAME_COMMAND`: The command used when getting the basename of zettels. This value should be set automatically by `zk init` depending on the `ZETTELKASTEN_ENVIRONMENT`. 77 | - `ZETTELKASTEN_BROWSER_COMMAND`: The command used when executing 'open-browser' action. This value should be set automatically by `zk init` depending on the `ZETTELKASTEN_ENVIRONMENT`. 78 | - `ZETTELKASTEN_DEFAULT_TEXT_EDITOR`: The command used when using 'open'. This is configurable and can be set to `vi`, `vim`, `emacs` or `nano`. 79 | - `ZETTELKASTEN_DATE_TIME_STAMP_FORMAT`: The datetime stamp format used when creating the zettels. This will default to `+s` which is EPOCH time. 80 | - `ZETTELKASTEN_VI_COMMAND`: The command used when executing vi. Default is `vi +2`. 81 | - `ZETTELKASTEN_EMACS_COMMAND`: The command used when execuing emacs. Default is `emacs +2` 82 | - `ZETTELKASTEN_VISUAL_STUDIO_CODE_COMMAND`: The command used when executing Visual Studio Code. Default is `code --new-window --wait` 83 | 84 | ## Uninstall 85 | To remove the application perform the following: 86 | ```bash 87 | rm /usr/local/bin/zk 88 | rm "${HOME}/.zettelkasten" 89 | ``` 90 | 91 | ## Limitations 92 | - Currently only tested on Mac 93 | - Google Chrome and Visual Code must be installed to use `open-code` or `open-browser` 94 | - When `ZETTELKASTEN_AUTO_GIT_PUSH=yes` then `ZETTELKASTEN_DIR` must be a git repo. 95 | -------------------------------------------------------------------------------- /zk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | RED='\033[0;31m' 4 | NC='\033[0m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[0;33m' 7 | zk_config="$HOME/.zettelkasten" 8 | 9 | 10 | # multi-lined strings 11 | USAGE=$(cat <<-END 12 | 13 | Usage: zk COMMAND 14 | 15 | A note taking application following the Zettelkasten method. 16 | 17 | Commands: 18 | n, new Create a new zettel and use default editor 19 | nv, new-vi Create a new zettel and edit with vi 20 | ne, new-emacs Create a new zettel and edit with emacs 21 | nc, new-code Create a new zettel and edit with Visual Studio Code 22 | o, open Open an existing zettel in default editor 23 | ov, open-vi Open an existing zettel in vi 24 | oe, open-emacs Open an existing zettel in emacs 25 | oc, open-code Open an existing zettel in Visual Studio Code 26 | ob, open-browser Open an existing zettel in github using OS default browser 27 | ol, open-link Open the first URL inside of the zettel 28 | p, push Push zettels to GitHub 29 | c, cat Print an existing zettel to terminal 30 | t, tag Search zettels for a specific tag 31 | s, search Search for a sub string within all zettels 32 | ls, list List all zettels 33 | g, grep List zettels and grep on search 34 | rm, remove Remove a zettel 35 | ln, link Link 2 zettels together 36 | rml, rm-link Remove a link between 2 zettels 37 | home Display zettelkasten home directory 38 | init Initialize the $HOME/.zettelkasten 39 | variables List all variables defined in $HOME/.zettelkasten 40 | update Update to latest zk script 41 | add, add-media Add specific media (images, music, pdf, etc) to kasten 42 | lsm, list-media List all media zettels 43 | rmm, remove-media Remove a specific media file 44 | sync Sync local zettelkasten with git zettelkasten 45 | ui Launch the zk-ui from github.com/AndrewCopeland/zk-ui 46 | END 47 | ) 48 | 49 | function new_zettel_default() { 50 | zettel_title="$1" 51 | default_tags="" 52 | zettel_template=$(cat <<-END 53 | $zettel_title 54 | $default_tags 55 | 56 | 57 | 58 | ## Links 59 | END 60 | ) 61 | echo "$zettel_template" 62 | } 63 | 64 | # -- utils -- 65 | function echo_usage() { 66 | echo "$USAGE" 67 | } 68 | 69 | function fail() { 70 | echo -e "${RED}ERROR: $1${NC}" > /dev/tty 71 | exit 1 72 | } 73 | 74 | function echo_green() { 75 | echo -e "${GREEN}${1}${NC}" > /dev/tty 76 | } 77 | 78 | function echo_yellow() { 79 | echo -e "${YELLOW}${1}${NC}" > /dev/tty 80 | } 81 | 82 | function list_zettels() { 83 | set +e 84 | echo "$(find $ZETTELKASTEN_DIR -type f -exec basename {} \;)" > /dev/tty 85 | set -e 86 | } 87 | 88 | function find_zettel() { 89 | # validate search field were procided and create search fields for grep 90 | search_fields="$1" 91 | search_media="${2:-no}" 92 | if [[ -z "$search_fields" ]]; then 93 | list_zettels 94 | fail "Could not find a zettel because search fields were not provided" 95 | fi 96 | 97 | # if search fields starts with "^" then assume relative search 98 | if [[ $search_fields == ^* ]]; then 99 | # when '^' is provided then offset of '1' is assumed 100 | # when '^2' is procided then offset is '2' same applies to 3, 4, etc 101 | offset=$(echo "${search_fields: -1}") 102 | if [ "$offset" == "^" ]; then 103 | offset="1" 104 | fi 105 | zettel=$(ls $ZETTELKASTEN_DIR | tail -n "$offset" | head -n 1) 106 | if [ "$zettel" == "" ]; then 107 | fail "Could not find the previous zettel" 108 | fi 109 | echo "$zettel" 110 | return 111 | fi 112 | 113 | search_fields=$(echo "$search_fields" | sed 's/ /|/g' | sed 's/,/|/g') 114 | 115 | # look for the zettel 116 | set +e 117 | if [ "$search_media" == "yes" ]; then 118 | zettels=$(ls -a $ZETTELKASTEN_DIR | grep -e "$search_fields") 119 | else 120 | zettels=$(ls $ZETTELKASTEN_DIR | grep -e "$search_fields") 121 | fi 122 | set -e 123 | 124 | # validate only 1 zettel was retutned 125 | if [[ -z "$zettels" ]]; then 126 | fail "Failed to find a zettel with search criteria '$search_fields'" 127 | fi 128 | number_zettels=$(echo "$zettels" | wc -l) 129 | if [ "$number_zettels" -gt "1" ]; then 130 | echo "$zettels" > /dev/tty 131 | fail "Multiple zettle's were returned matching the search criteria." 132 | fi 133 | 134 | echo "$zettels" 135 | } 136 | 137 | function git_push() { 138 | sync_zettelkasten 139 | if [[ "$ZETTELKASTEN_AUTO_GIT_PUSH" == "yes" ]]; then 140 | cwd=$(pwd) 141 | cd "$ZETTELKASTEN_DIR" 142 | # echo "$1" > /dev/tty 143 | git add ${@:2} 144 | git commit -m "$1" --quiet 145 | git push --quiet 146 | cd "$cwd" 147 | fi 148 | } 149 | 150 | function remove_zettel_link() { 151 | zettel="$1" 152 | link="$2" 153 | link="\- \[$link\]($link)" 154 | line_number=$(cat "$ZETTELKASTEN_DIR/$zettel" | grep -n "$link" | awk -F ':' '{print $1}' | head -n 1) 155 | sed -i.bak "${line_number}d" "$ZETTELKASTEN_DIR/$zettel" 156 | rm "$ZETTELKASTEN_DIR/$zettel.bak" 157 | } 158 | 159 | function get_os_environment_type() { 160 | # if '/prov/version' files exists then assume running in 'WSL' 161 | if [ -f "/proc/version" ]; then 162 | echo "wsl" 163 | else 164 | # default to osx 165 | echo "osx" 166 | fi 167 | } 168 | 169 | function fail_invalid_environment() { 170 | fail "Invalid environment '${environment}'. Valid environment: osx or wsl" 171 | } 172 | 173 | function get_default() { 174 | target_environment="${1}" 175 | current_environment="${2}" 176 | default_value="${3}" 177 | 178 | if [ "${target_environment}" = "${current_environment}" ]; then 179 | echo "${default_value}" 180 | fi 181 | } 182 | 183 | function get_default_md5() { 184 | environment="${1}" 185 | get_default "osx" "${environment}" "md5" 186 | get_default "wsl" "${environment}" "md5sum" 187 | } 188 | 189 | function get_default_basename() { 190 | environment="${1}" 191 | get_default "osx" "${environment}" "basename --" 192 | get_default "wsl" "${environment}" "basename -a" 193 | } 194 | 195 | function get_default_browser() { 196 | environment="${1}" 197 | get_default "osx" "${environment}" "open" 198 | get_default "wsl" "${environment}" "cmd.exe /C start" 199 | } 200 | 201 | function get_zettel_md5() { 202 | zettel="${1}" 203 | zettel_fname="${ZETTELKASTEN_DIR}/${zettel}" 204 | cat "$zettel_fname" | $ZETTELKASTEN_MD5_COMMAND 205 | } 206 | 207 | function editor_clean_up() { 208 | zettel="${1}" 209 | zettel_path="${ZETTELKASTEN_DIR}/${zettel}" 210 | 211 | rm -f "${zettel_path}~" 212 | } 213 | 214 | function validate_mandatory_parameter() { 215 | parameter="${1}" 216 | usage_message="${2}" 217 | failure_message="${3}" 218 | 219 | if [ -z "${parameter}" ]; then 220 | if [ ! -z "${usage_messages}" ]; then 221 | echo "${usage_message}" 222 | fi 223 | fail "${failure_message}" 224 | fi 225 | } 226 | 227 | function get_datestamp() { 228 | datestamp_format="${ZETTELKASTEN_DATE_TIME_STAMP_FORMAT}" 229 | if [ -z "${datestamp_format}" ]; then 230 | datestamp_format="+%s" 231 | fi 232 | datestamp=$(date "${datestamp_format}") 233 | echo "${datestamp}" 234 | } 235 | 236 | function validate_dir_is_git_repo() { 237 | if [ "${ZETTELKASTEN_AUTO_GIT_PUSH}" = "yes" ]; then 238 | directory="${1}" 239 | set +e 240 | cd "${directory}" && git rev-parse --is-inside-work-tree &>/dev/null 241 | if [ "${?}" != "0" ]; then 242 | fail "'${directory}' is not a git repository. If you do not want to use git set 'ZETTELKASTEN_AUTO_GIT_PUSH' in the '${zk_config}' to no." 243 | fi 244 | set -e 245 | fi 246 | } 247 | 248 | # -- commands -- 249 | function new_editor() { 250 | editor="${1}" 251 | title="${@:2}" 252 | 253 | # validate mandatory parameter 254 | usage="usage: $0 new " 255 | validate_mandatory_parameter "${title}" "${usage}" "Zettel title not provided" 256 | 257 | title=$(echo "$title" | sed 's/ /-/g') 258 | now=$(get_datestamp) 259 | zettel_title="# $now $title" 260 | zettel="$now-$title.md" 261 | zettel_fname="$ZETTELKASTEN_DIR/${zettel}" 262 | if [ -f "$zettel_fname" ]; then 263 | fail "zettel '$zettel_fname' already exists" 264 | fi 265 | 266 | zettel_template=$(new_zettel_default "$zettel_title") 267 | touch "$zettel_fname" 268 | echo "$zettel_template" > "$zettel_fname" 269 | 270 | before=$(get_zettel_md5 "${zettel}") 271 | $editor "$zettel_fname" 272 | after=$(get_zettel_md5 "${zettel}") 273 | 274 | if [[ "$before" != "$after" ]]; then 275 | git_push "added zettel ${zettel}" "${zettel_fname}" 276 | echo_green "created zettel '${zettel}'" 277 | else 278 | rm -f $zettel_fname 279 | echo_yellow "WARNING: Zettel was not created since the contents are empty" 280 | fi 281 | editor_clean_up "${zettel}" 282 | } 283 | 284 | function add_media() { 285 | media_file_path="${1}" 286 | 287 | usage="usage: $0 add-media " 288 | error_message="Media file path not provided" 289 | validate_mandatory_parameter "${media_file_path}" "${usage}" "${error_message}" 290 | 291 | if [ ! -f "${media_file_path}" ]; then 292 | fail "'${media_file_path}' is not a valid file path!" 293 | fi 294 | 295 | # If file is larger than 100MB and we are pushing to git then fail because of github limitations 296 | if [[ "$ZETTELKASTEN_AUTO_GIT_PUSH" == "yes" ]]; then 297 | sizeKB=$(du -k "${media_file_path}" | awk '{print $1}') 298 | if [ "$sizeKB" -gt "99000" ]; then 299 | fail "'${media_file_path}' is larger than 100MB and cannot be added to github" 300 | fi 301 | fi 302 | 303 | basename=$($ZETTELKASTEN_BASENAME_COMMAND $(echo "${media_file_path}")) 304 | now=$(get_datestamp) 305 | 306 | media_zettel_name=".${now}-${basename}" 307 | media_fname="${ZETTELKASTEN_DIR}/${media_zettel_name}" 308 | 309 | cp "${media_file_path}" "${media_fname}" 310 | git_push "added zettel media ${media_zettel_name}" "${media_fname}" 311 | echo_green "added zettel media '${media_zettel_name}'" 312 | } 313 | 314 | function open_editor() { 315 | editor="${1}" 316 | search_fields="${2}" 317 | zettel=$(find_zettel "$search_fields" || exit) 318 | 319 | before=$(get_zettel_md5 "${zettel}") 320 | $editor "$ZETTELKASTEN_DIR/$zettel" 321 | after=$(get_zettel_md5 "${zettel}") 322 | 323 | if [[ "$before" != "$after" ]]; then 324 | git_push "updating zettel $zettel" "$zettel" 325 | echo_green "updated '$zettel'" 326 | fi 327 | editor_clean_up "${zettel}" 328 | } 329 | 330 | function open_media() { 331 | zettel="${1}" 332 | open "$ZETTELKASTEN_DIR/${zettel}" 333 | } 334 | 335 | function open_command() { 336 | editor="${1}" 337 | search_fields="${2}" 338 | 339 | # if found zettel starts with '.' then open as if it were media 340 | zettel=$(find_zettel "$search_fields" "yes" || exit) 341 | if [[ "$zettel" == .* ]]; then 342 | open_media "${zettel}" 343 | exit 0 344 | fi 345 | 346 | open_editor "${editor}" "${search_fields}" 347 | } 348 | 349 | function open_browser() { 350 | search_fields="$1" 351 | zettel=$(find_zettel "$search_fields" "yes" || exit) 352 | remote_url=$(cd $ZETTELKASTEN_DIR && git config --get remote.origin.url) 353 | # if remote_url contains a @ that means the repo was cloned using ssh key 354 | if [[ "$remote_url" = *"@"* ]]; then 355 | github_endpoint=$(echo "${remote_url}" | awk -F ':' '{print $2}') 356 | else 357 | github_endpoint=$(echo "${remote_url}" | awk -F '.com/' '{print $2}') 358 | fi 359 | github_endpoint=$(echo "${github_endpoint}" | sed 's/.git//g') 360 | $ZETTELKASTEN_BROWSER_COMMAND "https://github.com/$github_endpoint/tree/master/$ZETTELKASTEN_GIT_DIR/$zettel" 361 | } 362 | 363 | function open_link() { 364 | search_field="${1}" 365 | zettel=$(find_zettel "${search_field}" || exit) 366 | url=$(open_editor "cat" "${zettel}" | grep "https://") 367 | 368 | $ZETTELKASTEN_BROWSER_COMMAND "${url}" 369 | } 370 | 371 | function push() { 372 | cwd=$(pwd) 373 | cd "$ZETTELKASTEN_DIR" 374 | msg="Regenerated at $(get_datestamp)" 375 | git add . 376 | git commit -m "$msg" 377 | git push --quiet 378 | cd "$cwd"; 379 | } 380 | 381 | function link() { 382 | from_link=$1 383 | to_link=$2 384 | 385 | # validate mandatory parameters 386 | usage="usage: $0 link 1584648495 1584648508" 387 | validate_mandatory_parameter "${from_link}" "${usage}" "Empty from_link parameter" 388 | validate_mandatory_parameter "${to_link}" "${usage}" "Empty to_link parameter" 389 | 390 | from_fname=$(find_zettel "$from_link" "yes") 391 | to_fname=$(find_zettel "$to_link" "yes") 392 | 393 | # if file starts with '.' then assume it is media and it is not possible 394 | # to create link on a media file 395 | if [[ "$to_fname" != .* ]]; then 396 | echo "- [$from_fname]($from_fname)" >> "$ZETTELKASTEN_DIR/$to_fname" 397 | fi 398 | if [[ "$from_fname" != .* ]]; then 399 | echo "- [$to_fname]($to_fname)" >> "$ZETTELKASTEN_DIR/$from_fname" 400 | fi 401 | 402 | git_push \ 403 | "added link between $from_fname and $to_fname" \ 404 | "$from_fname" \ 405 | "$to_fname" 406 | 407 | echo_green "linked zettels $to_fname <---> $from_fname" 408 | } 409 | 410 | function rm_link() { 411 | from_link=$1 412 | to_link=$2 413 | 414 | # validate mandatory parameters 415 | usage="usage: $0 link 1584648495 1584648508" 416 | validate_mandatory_parameter "${from_link}" "${usage}" "Empty from_link parameter" 417 | validate_mandatory_parameter "${to_link}" "${usage}" "Empty to_link parameter" 418 | 419 | # get zettel file names 420 | from_fname=$(find_zettel "$from_link") 421 | to_fname=$(find_zettel "$to_link") 422 | 423 | # remove the zettel links from both files 424 | remove_zettel_link $from_fname $to_fname 425 | remove_zettel_link $to_fname $from_fname 426 | 427 | git_push \ 428 | "remove link between $from_fname and $to_fname" \ 429 | "$from_fname" \ 430 | "$to_fname" 431 | 432 | echo_green "removed link $to_fname <---> $from_fname" 433 | } 434 | 435 | function home() { 436 | new_home_dir=$1 437 | # if new home dir is empty then print out dir 438 | if [[ -z "$new_home_dir" ]]; then 439 | echo "Current Zettel directory: $ZETTELKASTEN_DIR" 440 | else 441 | echo "export ZETTELKASTEN_DIR=\"$new_home_dir\"" 442 | fi 443 | } 444 | 445 | function append_to_zettelkasten_config() { 446 | key="${1}" 447 | value="${2}" 448 | 449 | echo "${key}=\"${value}\"" >> "${zk_config}" 450 | } 451 | 452 | function init() { 453 | read -p "Zettelkasten Directory: " zk_dir 454 | read -p "Zettelkasten auto git push[yes/no]: " zk_auto_git_push 455 | read -p "Zettelkasten git directory(optional): " zk_git_dir 456 | read -p "Default command line text editor[vi/emacs/nano]: " zk_text_editor 457 | 458 | if [ -z "${zk_text_editor}" ]; then 459 | zk_text_editor="vi" 460 | fi 461 | 462 | if [ ! -d "${zk_dir}" ]; then 463 | fail "Directory '${zk_dir}' does not exist" 464 | fi 465 | 466 | zk_auto_git_push=$(echo $zk_auto_git_push | tr '[:upper:]' '[:lower:]') 467 | if [[ "${zk_auto_git_push}" != "yes" ]]; then 468 | zk_auto_git_push="no" 469 | fi 470 | validate_dir_is_git_repo "${zk_git_dir}" 471 | 472 | environment=$(get_os_environment_type) 473 | md5_command=$(get_default_md5 "${environment}") 474 | basename_command=$(get_default_basename "${environment}") 475 | browser_command=$(get_default_browser "${environment}") 476 | rm -f "${zk_config}" 477 | 478 | # the values specified in the '~/.zettelkasten' 479 | # the directory in which all zettels reside 480 | append_to_zettelkasten_config "ZETTELKASTEN_DIR" "${zk_dir}" 481 | # auto push zettels to git when created, updated or deleted [yes|no] 482 | append_to_zettelkasten_config "ZETTELKASTEN_AUTO_GIT_PUSH" "${zk_auto_git_push}" 483 | # the directory in the git repo where the zettels reside. 484 | # if zettels reside on repo root level then this does not need to be provided 485 | # this is only used when executing 'open-browser' 486 | append_to_zettelkasten_config "ZETTELKASTEN_GIT_DIR" "${zk_git_dir}" 487 | # the os environment in which the zk application is being run [osx|wsl] 488 | append_to_zettelkasten_config "ZETTELKASTEN_ENVIRONMENT" "${environment}" 489 | # the command used when md5 hashing zettels 490 | append_to_zettelkasten_config "ZETTELKASTEN_MD5_COMMAND" "${md5_command}" 491 | # the command used when getting the basename of zettels 492 | append_to_zettelkasten_config "ZETTELKASTEN_BASENAME_COMMAND" "${basename_command}" 493 | # the command used when using 'open-browser' 494 | append_to_zettelkasten_config "ZETTELKASTEN_BROWSER_COMMAND" "${browser_command}" 495 | # the command used when using 'open' 496 | append_to_zettelkasten_config "ZETTELKASTEN_DEFAULT_TEXT_EDITOR" "${zk_text_editor}" 497 | # the datetime stamp format used when creating the zettels 498 | append_to_zettelkasten_config "ZETTELKASTEN_DATE_TIME_STAMP_FORMAT" "+%s" 499 | # the vi command 500 | append_to_zettelkasten_config "ZETTELKASTEN_VI_COMMAND" "vi +2" 501 | # the emacs command 502 | append_to_zettelkasten_config "ZETTELKASTEN_EMACS_COMMAND" "emacs +2" 503 | # the visual studio code command 504 | append_to_zettelkasten_config "ZETTELKASTEN_VISUAL_STUDIO_CODE_COMMAND" "code --new-window --wait" 505 | } 506 | 507 | # perform a 'git pull' in the kasten directory 508 | function sync_zettelkasten() { 509 | if [[ "$ZETTELKASTEN_AUTO_GIT_PUSH" == "yes" ]]; then 510 | cd $ZETTELKASTEN_DIR && git pull 511 | # after performing a git pull push all files that have been left behind 512 | # this should not need to be done but currently every once and a while a zettel is not pushed 513 | # after it has been created 514 | # untrackedFiles=$(cd $ZETTELKASTEN_DIR && git status -s || awk '{print $2}') 515 | # for file in $untrackedFiles; do 516 | # cd $ZETTELKASTEN_DIR && git_push "Added zettel ${file}" "${file}" 517 | # done 518 | fi 519 | } 520 | 521 | # remove a specific zettel and make sure this is reflected in git 522 | function remove() { 523 | search_fields="$1" 524 | zettel=$(find_zettel "$search_fields" || exit) 525 | rm -f "$ZETTELKASTEN_DIR/$zettel" 526 | git_push "removing zettel $zettel" "$zettel" 527 | echo_green "removed zettel $zettel" 528 | } 529 | 530 | function find_tag() { 531 | tag="${1}" 532 | tag="#${tag}\s*" 533 | search_content "${tag}" 534 | } 535 | 536 | function search_content() { 537 | sub_string="${1}" 538 | result=$(grep -rnl "$ZETTELKASTEN_DIR" -e "${sub_string}" | sort | grep "^[^.]") 539 | if [[ ! -z $result ]]; then 540 | $ZETTELKASTEN_BASENAME_COMMAND $(echo "$result") 541 | fi 542 | } 543 | 544 | function list() { 545 | ls $ZETTELKASTEN_DIR | grep "^[^.]" 546 | } 547 | 548 | function list_grep() { 549 | search_field="${1}" 550 | list | grep "$search_field" 551 | } 552 | 553 | function list_media() { 554 | set +e 555 | find $ZETTELKASTEN_DIR -type f -exec basename {} \; | sort | grep "^\." 556 | set -e 557 | } 558 | 559 | function remove_media() { 560 | search_fields="$1" 561 | media_zettel=$(find_zettel "$search_fields" "yes" || exit) 562 | rm -f "$ZETTELKASTEN_DIR/$media_zettel" 563 | git_push "removing media zettel $media_zettel" "$media_zettel" 564 | echo_green "removed media zettel $media_zettel" 565 | } 566 | 567 | function variables() { 568 | cat $HOME/.zettelkasten 569 | } 570 | 571 | function update() { 572 | SOURCE_PATH="/usr/local/bin/zk" 573 | SOURCE_URL="https://raw.githubusercontent.com/AndrewCopeland/zettelkasten/master/zk" 574 | current_zk_hash=$(cat $SOURCE_PATH | $ZETTELKASTEN_MD5_COMMAND) 575 | latest_zk=$(curl --fail -s $SOURCE_URL) 576 | latest_zk_hash=$(echo "${latest_zk}" | $ZETTELKASTEN_MD5_COMMAND) 577 | 578 | if [ "${current_zk_hash}" = "${latest_zk_hash}" ]; then 579 | echo_yellow "Latest '$SOURCE_PATH' script is already installed" 580 | exit 0 581 | fi 582 | 583 | echo "${latest_zk}" > $SOURCE_PATH 584 | chmod +x $SOURCE_PATH 585 | 586 | echo_green "Successfully updated '$SOURCE_PATH' to the latest version" 587 | } 588 | 589 | function random() { 590 | zettel=$(ls $ZETTELKASTEN_DIR | sort -R | head -n 1) 591 | open_browser "${zettel}" 592 | } 593 | 594 | function ui() { 595 | GITHUB_URL="https://github.com/AndrewCopeland/zk-ui.git" 596 | cwd=$(pwd) 597 | cd /tmp 598 | rm -rf ./zk-ui 599 | git clone "$GITHUB_URL" 600 | cd zk-ui 601 | python3 -m venv venv 602 | source venv/bin/activate 603 | pip install -r requirements.txt 604 | python serve.py & > logs.txt 605 | cd "$cwd" 606 | 607 | $ZETTELKASTEN_BROWSER_COMMAND "http://localhost:5000" 608 | } 609 | 610 | function main() { 611 | action="${1}" 612 | 613 | # if action is init create the ~/.zettelkasten config file and exit 614 | if [ "${action}" = "init" ]; then 615 | init 616 | exit 0 617 | fi 618 | # if config does not exist then prompt user to run 'zk init' 619 | if [ ! -f "${zk_config}" ]; then 620 | echo "'${zk_config}' could not be found" 621 | echo "run 'zk init' to initialize the zettelkasten config" 622 | exit 1 623 | fi 624 | source "${zk_config}" 625 | validate_dir_is_git_repo "${ZETTELKASTEN_DIR}" 626 | 627 | # perform zettel actions 628 | # sync_zettelkasten &>/dev/null 629 | case $action in 630 | "new" | "n") 631 | new_editor "${ZETTELKASTEN_DEFAULT_TEXT_EDITOR}" "${@:2}" 632 | ;; 633 | 634 | "new-vi" | "nv") 635 | new_editor "${ZETTELKASTEN_VI_COMMAND}" "${@:2}" 636 | ;; 637 | 638 | "new-emacs" | "ne") 639 | new_editor "${ZETTELKASTEN_EMACS_COMMAND}" "${@:2}" 640 | ;; 641 | 642 | "new-code" | "nc") 643 | new_editor "${ZETTELKASTEN_VISUAL_STUDIO_CODE_COMMAND}" "${@:2}" 644 | ;; 645 | 646 | "open" | "o") 647 | open_command "${ZETTELKASTEN_DEFAULT_TEXT_EDITOR}" "${2}" 648 | ;; 649 | 650 | "open-vi" | "ov") 651 | open_editor "${ZETTELKASTEN_VI_COMMAND}" "${2}" 652 | ;; 653 | 654 | "open-emacs" | "oe") 655 | open_editor "${ZETTELKASTEN_EMACS_COMMAND}" "${2}" 656 | ;; 657 | 658 | "open-code" | "oc") 659 | open_editor "${ZETTELKASTEN_VISUAL_STUDIO_CODE_COMMAND}" "$2" 660 | ;; 661 | 662 | "open-browser" | "ob") 663 | open_browser "$2" 664 | ;; 665 | 666 | "open-link" | "ol") 667 | open_link "${2}" 668 | ;; 669 | 670 | "push" | "p") 671 | push 672 | ;; 673 | 674 | "cat" | "c") 675 | open_editor "cat" "$2" 676 | ;; 677 | 678 | "link" | "ln") 679 | link "$2" "$3" 680 | ;; 681 | 682 | "remove-link" | "rm-link" | "rml") 683 | rm_link "$2" "$3" 684 | ;; 685 | 686 | "remove" | "rm") 687 | remove "$2" 688 | ;; 689 | 690 | "tag" | "t") 691 | find_tag "$2" 692 | ;; 693 | 694 | "search"| "s") 695 | search_content "${2}" 696 | ;; 697 | 698 | "list" | "ls") 699 | list 700 | ;; 701 | 702 | "grep" | "g") 703 | list_grep "${2}" 704 | ;; 705 | 706 | "home") 707 | home "$2" 708 | ;; 709 | 710 | "variables") 711 | variables 712 | ;; 713 | 714 | "update") 715 | update 716 | ;; 717 | 718 | "add-media" | "add") 719 | add_media "$2" 720 | ;; 721 | 722 | "list-media" | "lsm") 723 | list_media 724 | ;; 725 | 726 | "remove-media" | "rmm") 727 | remove_media "$2" 728 | ;; 729 | 730 | "sync") 731 | sync_zettelkasten 732 | ;; 733 | 734 | "random") 735 | random 736 | ;; 737 | 738 | "ui") 739 | ui 740 | ;; 741 | 742 | *) 743 | echo "Invalid action '$action' check usage below." 744 | echo_usage 745 | ;; 746 | esac 747 | } 748 | 749 | main $@ 750 | --------------------------------------------------------------------------------