diff --git a/bin/commit b/bin/commit
new file mode 100755
index 0000000..55aff07
--- /dev/null
+++ b/bin/commit
@@ -0,0 +1,13 @@
+#!/bin/sh
+TYPE=$(gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore" "revert")
+SCOPE=$(gum input --placeholder "scope")
+
+# Since the scope is optional, wrap it in parentheses if it has a value.
+test -n "$SCOPE" && SCOPE="($SCOPE)"
+
+# Pre-populate the input with the type(scope): so that the user may change it
+SUMMARY=$(gum input --value "$TYPE$SCOPE: " --placeholder "Summary of this change")
+DESCRIPTION=$(gum write --placeholder "Details of this change (CTRL+D to finish)")
+
+# Commit these changes
+gum confirm "Commit changes?" && git add -A && git commit -m "$SUMMARY" -m "$DESCRIPTION"
diff --git a/bin/nb b/bin/nb
new file mode 100755
index 0000000..340e57b
--- /dev/null
+++ b/bin/nb
@@ -0,0 +1,25477 @@
+#!/usr/bin/env bash
+###############################################################################
+# __ _
+# \ \ _ __ | |__
+# \ \ | '_ \| '_ \
+# / / | | | | |_) |
+# /_/ |_| |_|_.__/
+#
+# [nb] Command line and local web note-taking, bookmarking, and archiving with
+# plain text data storage, encryption, filtering and search, pinning, #tagging,
+# Git-backed versioning and syncing, Pandoc-backed conversion, global and local
+# notebooks, customizable color themes, [[wiki-style linking]], plugins, and
+# more in a single portable, user-friendly script.
+#
+# โฏ https://github.com/xwmx/nb
+# โฏ https://xwmx.github.io/nb
+#
+# Based on Bash Boilerplate: https://github.com/xwmx/bash-boilerplate
+#
+# Copyright (c) 2015-present William Melody โฏ hi@williammelody.com
+# โ https://www.williammelody.com
+#
+# Overview
+# ========
+#
+# - Configuration and Setup
+# - Helpers: Group 1 (alphabetical order)
+# - Subcommands: Group 1 (alphabetical order)
+# - Plugins
+# - Option Parsing
+# - _main() / Dispatch
+# - Helpers: Group 2 (alphabetical order)
+# - Subcommands: Group 2 (alphabetical order)
+# - Subcommands: Group 3 (alphabetical order)
+#
+# Target Bash Version: 5 (target, recommended)
+# Minimum Recommended Bash Version: 4 (fallback, recommended)
+# Minimum Tested Bash Version: 3.2 (fallback, deprecated)
+#
+# AGPLv3
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+###############################################################################
+
+###############################################################################
+# Shell Options & Strict Mode
+#
+# More Information:
+# https://github.com/xwmx/bash-boilerplate#bash-strict-mode
+# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
+# https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
+# https://tldp.org/LDP/abs/html/options.html
+###############################################################################
+
+set -o errexit
+set -o noglob
+set -o nounset
+set -o pipefail
+
+set +o noclobber
+
+shopt -s extglob
+
+IFS=$'\n\t'
+
+###############################################################################
+# Environment
+###############################################################################
+
+# $_VERSION
+#
+# The most recent program version.
+_VERSION="7.1.1"
+
+# $_ME
+#
+# This program's basename.
+_ME="$(basename "${0}")"
+
+# $_MY_DIR
+#
+# The directory containing $_ME.
+_MY_DIR="$(cd "$(dirname "${0}")"; pwd)"
+
+# $_MY_PATH
+#
+# This program's full path.
+_MY_PATH="${_MY_DIR}/${_ME}"
+
+# $_CURRENT_WORKING_DIR
+#
+# The current working directory in which the program was invoked.
+_CURRENT_WORKING_DIR="${PWD}"
+
+# $_REPO
+#
+# The / identifier for this project's git repository.
+_REPO="xwmx/nb"
+
+# $_REPO_MAIN_BRANCH
+#
+# The name of the main branch in this project's git repository.
+_REPO_MAIN_BRANCH="master"
+
+# $_REPO_RAW_URL
+#
+# The base URL for raw files.
+_REPO_RAW_URL="https://raw.githubusercontent.com/${_REPO}/${_REPO_MAIN_BRANCH}"
+
+###############################################################################
+# Utilities
+###############################################################################
+
+# _command_exists()
+#
+# Usage:
+# _command_exists
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If a command is defined in the current environment.
+# 1 (error, false) If not.
+#
+# More Information:
+# http://stackoverflow.com/a/677212
+_command_exists() {
+ if [[ "${1:-}" == "w3m" ]]
+ then # Detect WSL 1 (https://stackoverflow.com/a/38859331), where w3m errors.
+ [[ -f /proc/version ]] && grep -q Micro /proc/version && return 1
+ fi
+
+ hash "${1}" 2>/dev/null
+}
+
+# _contains()
+#
+# Usage:
+# _contains ...
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If the item is included in the list.
+# 1 (error, false) If not.
+#
+# Example:
+# _contains "${_query}" "${_list[@]}"
+_contains() {
+ local _query="${1:-}"
+
+ shift
+
+ if [[ -z "${_query}" ]] ||
+ [[ -z "${*:-}" ]]
+ then
+ return 1
+ fi
+
+ local __element=
+ for __element in "${@}"
+ do
+ [[ "${__element}" == "${_query}" ]] && return 0
+ done
+
+ return 1
+}
+
+# _resolve_symlink()
+#
+# Usage:
+# _resolve_symlink
+#
+# Description:
+# Resolve the real path or target for a symbolic link.
+_resolve_symlink() {
+ if hash "realpath" 2>/dev/null
+ then
+ realpath "${@:-}"
+ else
+ readlink "${@:-}"
+ fi
+}
+
+# _sed_i()
+#
+# Usage:
+# _sed_i ...
+#
+# Description:
+# `sed -i` takes an extension on macOS, but that extension can cause errors
+# in GNU `sed`. Detect which `sed` is available and call it with the
+# appropriate arguments.
+#
+# More Information:
+# https://stackoverflow.com/q/43171648
+# https://stackoverflow.com/a/16746032
+_sed_i() {
+ if sed --help >/dev/null 2>&1
+ then # GNU
+ sed -i "${@}"
+ else # BSD
+ sed -i '' "${@}"
+ fi
+}
+
+# _tput()
+#
+# Usage:
+# _tput ...
+#
+# Description:
+# Run `tput` commands, with fallbacks to termcap names for FreeBSD and
+# `stty` and escape sequences when `tput` is not found.
+_tput() {
+ if _command_exists "tput"
+ then
+ # More info: https://stackoverflow.com/a/64214019
+ case "${1:-}" in
+ cols)
+ if tput cols &>/dev/null
+ then
+ tput cols
+ elif tput co &>/dev/null
+ then
+ tput co
+ else
+ printf "80\\n"
+ fi
+
+ return 0
+ ;;
+ lines)
+ if tput lines &>/dev/null
+ then
+ tput lines
+ elif tput li &>/dev/null
+ then
+ tput li
+ else
+ printf "20\\n"
+ fi
+
+ return 0
+ ;;
+ setaf|smul)
+ ((${_COLOR_ENABLED:-1})) || return 0
+ ;;
+ esac
+
+ tput "${@:-}" 2>/dev/null || {
+ case "${1:-}" in
+ sgr0)
+ tput me
+ ;;
+ smul)
+ tput us
+ ;;
+ setaf)
+ tput AF "${@:2}"
+ ;;
+ esac
+ } 2>/dev/null || printf ""
+ else
+ case "${1:-}" in
+ cols|lines)
+ {
+ stty size 2>/dev/null || printf "20 80\\n"
+ } | if [[ "${1:-}" == "cols" ]]
+ then
+ cut -d " " -f 2
+ else
+ cut -d " " -f 1
+ fi
+ ;;
+ setaf)
+ printf '\033[38;5;%sm' "${2:-}"
+ ;;
+ sgr0)
+ printf '\033[m'
+ ;;
+ smul)
+ printf '\033[4m'
+ ;;
+ esac
+ fi
+}
+
+###############################################################################
+# Debug
+###############################################################################
+
+# _debug()
+#
+# Usage:
+# _debug ...
+#
+# Description:
+# Execute a command and print to standard error. The command is expected to
+# print a message and should typically be either `echo`, `printf`, or `cat`.
+#
+# Example:
+# _debug printf "Debug info. Variable: %s\\n" "$0"
+__DEBUG_COUNTER=0
+__DEBUG_START_TIME=
+_debug() {
+ # Usage: __debug_get_timestamp
+ __debug_get_timestamp() {
+ if hash "gdate" 2>/dev/null
+ then
+ gdate +%s%3N
+ elif date --version >/dev/null 2>&1
+ then
+ date +%s%3N
+ else
+ return 1
+ fi
+ }
+
+ if ((${_USE_DEBUG:-0}))
+ then
+ __DEBUG_COUNTER=$((__DEBUG_COUNTER+1))
+ printf "๐ %s" "${__DEBUG_COUNTER} "
+
+ "${@}"
+
+ if [[ -n "${__DEBUG_START_TIME:-}" ]]
+ then
+ printf "โฑ %s\\n" "$(($(__debug_get_timestamp)-__DEBUG_START_TIME))"
+ elif __DEBUG_START_TIME="$(__debug_get_timestamp)"
+ then
+ printf "โฑ 0\\n"
+ fi
+
+ printf "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\\n"
+ fi 1>&2
+}
+
+###############################################################################
+# Error Messages
+###############################################################################
+
+# _exit_1()
+#
+# Usage:
+# _exit_1
+#
+# Description:
+# Exit with status 1 after executing the specified command with output
+# redirected to standard error. The command is expected to print a message
+# and should typically be either `echo`, `printf`, or `cat`.
+_exit_1() {
+ {
+ [[ "${1:-}" == "_help" ]] || printf "%s " "$(_tput setaf 1)!$(_tput sgr0)"
+
+ "${@}"
+ } 1>&2
+
+ exit 1
+}
+
+# _warn()
+#
+# Usage:
+# _warn
+#
+# Description:
+# Print the specified command with output redirected to standard error.
+# The command is expected to print a message and should typically be either
+# `echo`, `printf`, or `cat`.
+_warn() {
+ {
+ printf "%s " "$(_tput setaf 1)!$(_tput sgr0)"
+
+ "${@}"
+ } 1>&2
+}
+
+###############################################################################
+# Option Helpers
+###############################################################################
+
+# _option_get_value()
+#
+# Usage:
+# _option_get_value
+#
+# Description:
+# Given a flag (e.g., -e | --example) return the value or exit 1 if value
+# is blank or appears to be another option.
+_option_get_value() {
+ local _option="${1:-}"
+ local _value="${2:-}"
+
+ if [[ -n "${_value:-}" ]] && [[ ! "${_value:-}" =~ ^- ]]
+ then
+ printf "%s\\n" "${_value}"
+ else
+ _exit_1 printf \
+ "%s requires a valid argument.\\n" \
+ "$(_color_primary "${_option}")"
+ fi
+}
+
+# _option_value_is_present()
+#
+# Usage:
+# _option_value_is_present
+#
+# Exit / Error / Return Status:
+# 0 (success, true) The argument is present and does not match as an option
+# flag.
+# 1 (error, false) The argument is blank or matches as an option flag.
+_option_value_is_present() {
+ [[ -n "${1:-}" ]] && [[ ! "${1:-}" =~ ^- ]]
+}
+
+###############################################################################
+# Configuration
+###############################################################################
+
+# Configuration File ####################################### Configuration File
+
+# $NBRC_PATH
+#
+# Default: `$HOME/.nbrc`
+#
+# The location of the .nbrc configuration file.
+export NBRC_PATH="${NBRC_PATH:-"${HOME}/.${_ME}rc"}"
+
+# Handle symlinked NBRC_PATH.
+if [[ -L "${NBRC_PATH}" ]]
+then
+ NBRC_PATH="$(_resolve_symlink "${NBRC_PATH}")"
+fi
+
+# Source rc file.
+if [[ -e "${NBRC_PATH}" ]]
+then
+ source "${NBRC_PATH}"
+fi
+
+# Base Configuration ####################################### Base Configuration
+
+# $NB_DIR
+#
+# Default: `$HOME/.nb`
+#
+# The location of the directory that contains the notebooks.
+export NB_DIR="${NB_DIR:-"${HOME}/.${_ME}"}"
+
+# Handle symlinked NB_DIR.
+if [[ -L "${NB_DIR:-}" ]]
+then
+ NB_DIR="$(_resolve_symlink "${NB_DIR}")"
+fi
+
+# Validate that NB_DIR exists and is writable.
+if [[ -z "${NB_DIR:?}" ]] ||
+ [[ "${NB_DIR}" == "/" ]] ||
+ {
+ [[ -e "${NB_DIR}" ]] &&
+ [[ ! -w "${NB_DIR}" ]]
+ }
+then
+ _exit_1 cat <`.
+#
+# Supported Values:
+# -
+# - auto-
+# - auto-
+#
+# Example Values:
+# - 15
+# - auto
+# - auto-2
+case "${NB_LIMIT:-}" in
+ auto*)
+ export _AUTO_LIMIT="${NB_LIMIT#auto}"
+ _AUTO_LIMIT="${_AUTO_LIMIT:-0}"
+
+ export NB_LIMIT="${NB_LIMIT}"
+ ;;
+ *)
+ export _AUTO_LIMIT=
+ export NB_LIMIT="${NB_LIMIT:-15}"
+ ;;
+esac
+
+# $EDITOR ############################################################# $EDITOR
+
+# $EDITOR
+#
+# The terminal editor command for editing items.
+export EDITOR="${EDITOR:-}"
+
+__set_editor() {
+ local _editors=(
+ code
+ subl
+ micro
+ mate
+ macdown
+ nano
+ pico
+ vim
+ vi
+ emacs
+ )
+
+ if [[ -z "${EDITOR:-}" ]]
+ then
+ if [[ -n "${VISUAL:-}" ]]
+ then
+ EDITOR="${VISUAL}"
+ else
+ local __editor=
+ for __editor in "${_editors[@]}"
+ do
+ if hash "${__editor}" 2>/dev/null
+ then
+ EDITOR="${__editor}"
+
+ break
+ fi
+ done
+ fi
+
+ if [[ -z "${EDITOR:-}" ]]
+ then
+ cat <
+#
+# Description:
+# Reassign or turn off file type indicator icons.
+#
+# Examples:
+# # reassign pinned indicator
+# export NB_INDICATOR_PINNED="๐"
+#
+# # turn off bookmark indicators
+# export NB_INDICATOR_BOOKMARK=""
+#
+# Parameter Expansion:
+# | | set / not blank | set / blank | unset |
+# ----------------------------------------------------
+# | - | parameter | blank | replacement |
+# | :+ | replacement | blank | blank |
+
+# $NB_INDICATOR_AUDIO
+export NB_INDICATOR_AUDIO="${NB_INDICATOR_AUDIO-๐}"
+export _NB_INDICATOR_AUDIO="${NB_INDICATOR_AUDIO:+"${NB_INDICATOR_AUDIO} "}"
+
+# $NB_INDICATOR_BOOKMARK
+export NB_INDICATOR_BOOKMARK="${NB_INDICATOR_BOOKMARK-๐}"
+export _NB_INDICATOR_BOOKMARK="${NB_INDICATOR_BOOKMARK:+"${NB_INDICATOR_BOOKMARK} "}"
+
+# $NB_INDICATOR_DOCUMENT
+export NB_INDICATOR_DOCUMENT="${NB_INDICATOR_DOCUMENT-๐}"
+export _NB_INDICATOR_DOCUMENT="${NB_INDICATOR_DOCUMENT:+"${NB_INDICATOR_DOCUMENT} "}"
+
+# $NB_INDICATOR_EBOOK
+export NB_INDICATOR_EBOOK="${NB_INDICATOR_EBOOK-๐}"
+export _NB_INDICATOR_EBOOK="${NB_INDICATOR_EBOOK:+"${NB_INDICATOR_EBOOK} "}"
+
+# $NB_INDICATOR_ENCRYPTED
+export NB_INDICATOR_ENCRYPTED="${NB_INDICATOR_ENCRYPTED-๐}"
+export _NB_INDICATOR_ENCRYPTED="${NB_INDICATOR_ENCRYPTED:+"${NB_INDICATOR_ENCRYPTED} "}"
+
+# $NB_INDICATOR_FOLDER
+export NB_INDICATOR_FOLDER="${NB_INDICATOR_FOLDER-๐}"
+export _NB_INDICATOR_FOLDER="${NB_INDICATOR_FOLDER:+"${NB_INDICATOR_FOLDER} "}"
+
+# $NB_INDICATOR_IMAGE
+export NB_INDICATOR_IMAGE="${NB_INDICATOR_IMAGE-๐}"
+export _NB_INDICATOR_IMAGE="${NB_INDICATOR_IMAGE:+"${NB_INDICATOR_IMAGE} "}"
+
+# $NB_INDICATOR_PINNED
+export NB_INDICATOR_PINNED="${NB_INDICATOR_PINNED-๐}"
+export _NB_INDICATOR_PINNED="${NB_INDICATOR_PINNED:+"${NB_INDICATOR_PINNED} "}"
+
+# $NB_INDICATOR_TODO
+export NB_INDICATOR_TODO="${NB_INDICATOR_TODO-โ๏ธ }"
+export _NB_INDICATOR_TODO="${NB_INDICATOR_TODO:+"${NB_INDICATOR_TODO} "}"
+
+# $NB_INDICATOR_TODO_DONE
+export NB_INDICATOR_TODO_DONE="${NB_INDICATOR_TODO_DONE-โ
}"
+export _NB_INDICATOR_TODO_DONE="${NB_INDICATOR_TODO_DONE:+"${NB_INDICATOR_TODO_DONE} "}"
+
+# $NB_INDICATOR_VIDEO
+export NB_INDICATOR_VIDEO="${NB_INDICATOR_VIDEO-๐น}"
+export _NB_INDICATOR_VIDEO="${NB_INDICATOR_VIDEO:+"${NB_INDICATOR_VIDEO} "}"
+
+# Color ################################################################# Color
+
+# $_COLOR_ENABLED
+#
+# Default: '1'
+#
+# Supported Values: '0' '1'
+export _COLOR_ENABLED="${_COLOR_ENABLED:-1}"
+
+# $NB_COLOR_THEME
+#
+# Default: 'nb'
+#
+# The color theme.
+export NB_COLOR_THEME="${NB_COLOR_THEME:-nb}"
+
+case "${NB_COLOR_THEME}" in
+ nb)
+ : # Colors assigned as default values below.
+ ;;
+ blacklight)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-39}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-56}"
+ ;;
+ console)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-40}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-28}"
+ ;;
+ desert)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-179}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-95}"
+ ;;
+ electro)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-200}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-62}"
+ ;;
+ forest)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-29}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-59}"
+ ;;
+ lavender)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-183}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-61}"
+ ;;
+ mage)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-199}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-55}"
+ ;;
+ mint)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-43}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-60}"
+ ;;
+ ocean)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-75}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-26}"
+ ;;
+ raspberry)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-162}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-90}"
+ ;;
+ smoke|monochrome)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-248}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-241}"
+ ;;
+ unicorn)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-183}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-153}"
+ ;;
+ utility)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-227}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-8}"
+ ;;
+ 1)
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-164}"
+ export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-60}"
+ ;;
+esac
+
+export _NB_COLOR_THEMES=(
+ blacklight
+ console
+ desert
+ electro
+ forest
+ nb
+ ocean
+ raspberry
+ smoke
+ unicorn
+ utility
+)
+
+# User defined themes can be installed in the $NB_DIR/.plugins directory.
+# Themes have an .nb-theme extension and contain a single if statment
+# assigning the color environment variables to tput ANSI color numbers.
+#
+# Example:
+#
+# # filename: ~/.nb/.plugins/example.nb-theme
+# if [[ "${NB_COLOR_THEME}" == "example" ]]
+# then
+# export NB_COLOR_PRIMARY=68
+# export NB_COLOR_SECONDARY=8
+# fi
+#
+# To view a list of available color numbers, run `nb settings colors`
+__load_themes() {
+ if [[ -d "${NB_DIR}/.themes" ]] &&
+ [[ ! -e "${NB_DIR}/.plugins" ]]
+ then # migrate legacy .themes directory. TODO: Remove.
+ mv "${NB_DIR}/.themes" "${NB_DIR}/.plugins"
+ fi
+
+ if [[ -d "${NB_DIR}/.plugins" ]]
+ then
+ set +f
+ local __file=
+ for __file in "${NB_DIR}/.plugins"/*."${_ME}"-theme*
+ do
+ if [[ -e "${__file}" ]]
+ then
+ local _basename=
+ _basename="$(basename "${__file}")"
+
+ local _name="${_basename%%.*}"
+
+ _NB_COLOR_THEMES+=("${_name}")
+
+ source "${__file}"
+ fi
+ done
+ set -f
+ fi
+}; __load_themes
+
+# $NB_COLOR_PRIMARY
+#
+# Default: Value depends on terminal capabilities.
+#
+# Set highlighting color. This should be set to an xterm color number, usually
+# a value between 1 and 256. For a table of common colors and their numbers
+# run:
+# nb settings colors
+#
+# Supported Values: [0..255+]
+__set_color_primary() {
+ local _colors=
+ _colors="$(_tput colors)"
+
+ if [[ -n "${_colors}" ]] && [[ "${_colors}" -gt 8 ]]
+ then
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-69}"
+ else
+ export NB_COLOR_PRIMARY="${NB_COLOR_PRIMARY:-4}"
+ fi
+}; __set_color_primary
+
+# $NB_COLOR_SECONDARY
+#
+# Default: '8'
+#
+# Color for lines and other accents. This should be set to an xterm color
+# number, usually a value between 1 and 256. For a table of common colors and
+# their numbers, run:
+# nb settings colors
+#
+# Supported Values: [0..255+]
+export NB_COLOR_SECONDARY="${NB_COLOR_SECONDARY:-8}"
+
+# Color Variables
+#
+# Avoid multiple tput calls by assigning output to variables.
+export _TPUT_SGR0=
+if [[ -n "${TMUX:-}" ]]
+then
+ # Avoid `tmux` and `less` quirks: https://unix.stackexchange.com/a/446541
+ _TPUT_SGR0="$(printf '\033[m')"
+else
+ _TPUT_SGR0="$(_tput sgr0)"
+fi
+
+export _TPUT_COLOR_PRIMARY=
+ _TPUT_COLOR_PRIMARY="$(_tput setaf "${NB_COLOR_PRIMARY}")"
+
+export _TPUT_COLOR_SECONDARY=
+ _TPUT_COLOR_SECONDARY="$(_tput setaf "${NB_COLOR_SECONDARY}")"
+
+export _TPUT_SETAF_8=
+ _TPUT_SETAF_8="$(_tput setaf 8)"
+
+export _TPUT_SMUL=
+ _TPUT_SMUL="$(_tput smul)"
+
+# _color_brackets()
+#
+# Usage:
+# _color_brackets
+#
+# Description:
+# Print with surrounding color brackets.
+_color_brackets() {
+ if ! ((_COLOR_ENABLED))
+ then
+ printf "[%s]" "${1:-}"
+
+ return 0
+ fi
+
+ printf "%s%s[%s%s%s]%s" \
+ "${_TPUT_SGR0}" \
+ "${_TPUT_SETAF_8}" \
+ "${_TPUT_COLOR_PRIMARY}" \
+ "${1:-}" \
+ "${_TPUT_SETAF_8}" \
+ "${_TPUT_SGR0}"
+}
+
+# _color_muted()
+#
+# Usage:
+# _color_muted
+#
+# Print the given string with the muted color.
+_color_muted() {
+ if ! ((_COLOR_ENABLED))
+ then
+ printf "%s" "${1:-}"
+
+ return 0
+ fi
+
+ printf "%s%s%s%s" \
+ "${_TPUT_SGR0}" \
+ "${_TPUT_SETAF_8}" \
+ "${1:-}" \
+ "${_TPUT_SGR0}"
+}
+
+# _color_primary()
+#
+# Usage:
+# _color_primary [--underline]
+#
+# Description:
+# Use `tput` to highlight the given string.
+_color_primary() {
+ if ! ((_COLOR_ENABLED))
+ then
+ printf "%s\\n" "${1:-}"
+
+ return 0
+ fi
+
+ if [[ "${2:-}" == "--underline" ]]
+ then
+ printf "%s%s%s%s%s\\n" \
+ "${_TPUT_SGR0}" \
+ "${_TPUT_SMUL}" \
+ "${_TPUT_COLOR_PRIMARY}" \
+ "${1:-}" \
+ "${_TPUT_SGR0}"
+ else
+ printf "%s%s%s%s\\n" \
+ "${_TPUT_SGR0}" \
+ "${_TPUT_COLOR_PRIMARY}" \
+ "${1:-}" \
+ "${_TPUT_SGR0}"
+ fi
+}
+
+# _color_secondary()
+#
+# Usage:
+# _color_secondary
+#
+# Description:
+# Highlight the given string using the accent color.
+_color_secondary() {
+ if ! ((_COLOR_ENABLED))
+ then
+ printf "%s\\n" "${1:-}"
+
+ return 0
+ fi
+
+ printf "%s%s%s\\n" "${_TPUT_COLOR_SECONDARY}" "${1:-}" "${_TPUT_SGR0}"
+}
+
+# Unset CLICOLOR_FORCE to avoid unexpected color output in `ls` et al.
+unset CLICOLOR_FORCE
+
+# Current Notebook ########################################### Current Notebook
+
+# $NB_NOTEBOOK_PATH
+#
+# Default: `$NB_DIR/home`
+#
+# The path to the current notebook.
+export NB_NOTEBOOK_PATH="${NB_NOTEBOOK_PATH:-"${NB_DIR}/home"}"
+
+# $_GLOBAL_NOTEBOOK_PATH
+#
+# The path of the current global notebook.
+export _GLOBAL_NOTEBOOK_PATH=
+
+# $_LOCAL_NOTEBOOK_PATH
+#
+# The path of the local notebook, if one is found. The current working
+# directory and all parents are searched.
+export _LOCAL_NOTEBOOK_PATH=
+
+# __set_notebook_paths()
+#
+# Usage:
+# __set_notebook_paths
+#
+# Description:
+# Set the the notebook path variables to reflect the current environment.
+__set_notebook_paths() {
+ __set_local_notebook_path() {
+ if [[ "${PWD}" == "/" ]]
+ then
+ cd "${_CURRENT_WORKING_DIR}"
+
+ return 0
+ elif [[ -d "${PWD}/.git" ]] &&
+ [[ -f "${PWD}/.index" ]]
+ then
+ _LOCAL_NOTEBOOK_PATH="${PWD}"
+
+ cd "${_CURRENT_WORKING_DIR}"
+
+ return 0
+ else
+ cd ..
+ __set_local_notebook_path
+ fi
+ }; __set_local_notebook_path
+
+ __set_global_notebook_path() {
+ if [[ -e "${NB_DIR}/.current" ]]
+ then
+ local _global_notebook_name=
+ _global_notebook_name="$(<"${NB_DIR}/.current")"
+
+ if [[ -d "${NB_DIR}/${_global_notebook_name}" ]]
+ then
+ _GLOBAL_NOTEBOOK_PATH="${NB_DIR}/${_global_notebook_name}"
+ fi
+ fi
+ }; __set_global_notebook_path
+
+ if [[ -n "${_LOCAL_NOTEBOOK_PATH:-}" ]]
+ then
+ NB_NOTEBOOK_PATH="${_LOCAL_NOTEBOOK_PATH}"
+ else
+ NB_NOTEBOOK_PATH="${_GLOBAL_NOTEBOOK_PATH}"
+ fi
+}; __set_notebook_paths
+
+# Cache & Temp ################################################### Cache & Temp
+
+# $_NB_CACHE_PATH
+#
+# The full path to the cache directory.
+export _NB_CACHE_PATH="${NB_DIR}/.cache"
+
+if [[ -d "${NB_DIR}" ]] &&
+ [[ ! -e "${_NB_CACHE_PATH}" ]]
+then
+ mkdir -p "${_NB_CACHE_PATH}"
+fi
+
+# $TMPDIR
+#
+# Reset $TMPDIR if it's set to the current working directory by npm.
+if [[ "${TMPDIR:-}" == "${PWD}" ]]
+then
+ export TMPDIR="/tmp"
+fi
+
+# $_NB_TEMP_DIRECTORY
+#
+# The full path to the temp directory.
+export _NB_TEMP_DIRECTORY
+_NB_TEMP_DIRECTORY="$(mktemp -d)"
+
+# $_NB_TEMP_PIDS
+#
+# A list of PIDS that should be terminated on exit.
+_NB_TEMP_PIDS=()
+
+# _temp()
+#
+# Usage:
+# _temp cache [clear]
+# _temp cleanup
+# _temp directory
+# _temp file [ | | ] [--touch]
+# _temp pid
+#
+# Subcommands:
+# cache Print the full path to the cache directory, or optionally clear
+# the cache.
+# cleanup Force delete the temp directory.
+# directory Print the full path the the temp directory.
+# file Initialize a temporary file path.
+# pid Specify a process ID that should be terminated on exit.
+#
+# Description:
+# Manage temp and cache files and directories.
+_temp() {
+ # Usage: _temp_validate_path
+ _temp_validate_path() {
+ [[ -n "${1:?}" ]] &&
+ [[ "${1}" != "/" ]] &&
+ [[ "${1}" != "${HOME}" ]] &&
+ [[ "${1}" != "${PWD}" ]] &&
+ [[ -e "${1}" ]]
+ }
+
+ local _subcommand="${1:-}"
+
+ case "${_subcommand}" in
+ cache)
+ if [[ "${2:-}" == "clear" ]]
+ then
+ if _temp_validate_path "${_NB_CACHE_PATH:?}"
+ then
+ rm -r "${_NB_CACHE_PATH:?}"
+ mkdir -p "${_NB_CACHE_PATH}"
+ fi
+ else
+ printf "%s\\n" "${_NB_CACHE_PATH}"
+ fi
+ ;;
+ cleanup)
+ if _temp_validate_path "${_NB_TEMP_DIRECTORY:?}"
+ then
+ rm -rf "${_NB_TEMP_DIRECTORY:?}"
+ fi
+
+ local __pid=
+ for __pid in "${_NB_TEMP_PIDS[@]:-}"
+ do
+ [[ -n "${__pid:-}" ]] || continue
+
+ kill "${__pid}" 2>/dev/null || :
+ done
+ ;;
+ directory)
+ printf "%s\\n" "${_NB_TEMP_DIRECTORY:-}"
+ ;;
+ file)
+ local _extension=
+ local _relative_path=
+ local _touch=0
+
+ local __arg=
+ for __arg in "${2:-}" "${3:-}"
+ do
+ case "${__arg}" in
+ "") : ;;
+ --touch) _touch=1 ;;
+ \.*) _extension="${__arg:-}" ;;
+ *) _relative_path="${__arg:-}" ;;
+ esac
+ done
+
+ local _temp_file_path=
+
+ if [[ -z "${_relative_path:-}" ]]
+ then
+ _temp_file_path="${_NB_TEMP_DIRECTORY}/${RANDOM}${_extension:-}"
+ else
+ _temp_file_path="${_NB_TEMP_DIRECTORY}/${_relative_path:-}"
+ fi
+
+ if [[ "${_relative_path:-}" =~ / ]]
+ then
+ mkdir -p "${_temp_file_path%/*}"
+ fi
+
+ if ((_touch))
+ then
+ touch "${_temp_file_path}"
+ fi
+
+ printf "%s\\n" "${_temp_file_path:-}"
+ ;;
+ pid)
+ _NB_TEMP_PIDS+=("${2:?}")
+ ;;
+ esac
+}
+
+trap "_temp cleanup" EXIT
+trap "exit" INT
+trap 'printf "\\n"; _temp cleanup; exit 0' SIGINT
+trap 'printf "\\n"; _temp cleanup; exit 0' SIGTERM
+
+###############################################################################
+# Configuration Validation
+###############################################################################
+
+# _validate_configuration()
+#
+# Usage:
+# _validate_configuration
+#
+# Description:
+# Check the runtime environment and display an error message when running in
+# an unsupported configuration.
+_validate_configuration() {
+ if { ((${NB_BASH_UPDATE_PROMPT_ENABLED:-0})) ||
+ {
+ [[ "${BASH_VERSINFO[0]:-999999999}" -lt 4 ]] &&
+ ((${NB_BASH_UPDATE_PROMPT_ENABLED:-1}))
+ }
+ } &&
+ _command_exists "brew" &&
+ {
+ # command initiated by user
+ [[ -t 1 ]] ||
+ ((${NB_TEST_MOCK_TTY:-0}))
+ }
+ then
+ local _config_file_path="${NB_DIR}/.nb-bash-3-enabled"
+
+ if [[ -e "${_config_file_path}" ]]
+ then
+ local _config_file_version=
+ _config_file_version="$(head -n 1 "${_config_file_path:-}")"
+
+ if [[ "${_config_file_version:-}" != "${_VERSION}" ]] ||
+ ((${NB_BASH_UPDATE_PROMPT_ENABLED:-0}))
+ then
+ rm "${_config_file_path:?}"
+ fi
+ fi
+
+ if [[ ! -e "${_config_file_path:-}" ]] &&
+ ((${NB_BASH_UPDATE_PROMPT_ENABLED:-1}))
+ then
+ local _color_me=
+ _color_me="$(_color_muted '`')$(_color_primary "${_ME}")$(_color_muted '`')"
+
+ {
+ printf "Update Bash\\n"
+ printf "%s\\n" "$(_color_muted "-------------")"
+
+ cat < "${_config_file_path:?}"
+${_VERSION}
+
+This presence of this file indicates that Bash 3 support is enabled.
+HEREDOC
+
+ printf "\\n"
+
+ {
+ printf "%s\\n" "$(_color_muted "-------------------------")"
+ printf " Bash 3.2 support enabled.\\n"
+ printf "%s\\n" "$(_color_muted " -------------------------")"
+ } | _warn printf "%s\\n" "$(cat)"
+
+ printf "\\n"
+
+ break
+ ;;
+ esac
+ done
+ fi
+ fi
+}
+
+if [[ -e "${NB_DIR:-}" ]]
+then
+ _validate_configuration "${@:-}"
+fi
+
+###############################################################################
+# Helpers: Group 1 Helpers: Group 1
+# ---------------- ----------------
+###############################################################################
+# --------------------------------------------------------------------------- #
+
+# $_BT
+#
+# Backtick with ANSI-C quoting for string building.
+_BT=$'`'
+
+# $_CHEVRON_LEFT
+#
+# A left-pointing chevron character.
+_CHEVRON_LEFT="โฎ"
+
+# $_CHEVRON_RIGHT
+#
+# A right-pointing chevron character.
+_CHEVRON_RIGHT="โฏ"
+
+# $_COLUMNS
+#
+# The terminal width in number of columns (characters).
+_COLUMNS="$(_tput cols)"
+
+# $_FILE_TYPES
+#
+# An array of supported aggregate file types.
+_FILE_TYPES=(archive audio bookmark document folder image text video)
+
+# $_IGNORE_PATTERNS
+#
+# An array of filename patterns to ignore, formatted as `sed -e` arguments.
+_IGNORE_PATTERNS=(
+ -e '/~$/d'
+ -e '/^#.*#$/d'
+ -e '/\.swa?p$/d'
+ -e '/^\.#*/d'
+)
+
+# $_MD
+#
+# Middle dot character.
+_MD="ยท"
+
+# $_NBSP
+#
+# No-break space character.
+_NBSP="ย "
+
+# $_NEWLINE
+#
+# Newline with ANSI-C quoting for string building.
+_NEWLINE=$'\n'
+
+# $_SLASH
+#
+# Slash with ANSI-C quoting for string building support and macOS Bash
+# string substitution.
+_SLASH=$'/'
+
+# _URL_PATTERNS
+#
+# Patterns for matching URLs.
+#
+# https://en.wikipedia.org/wiki/List_of_URI_schemes
+export _URL_PATTERNS=(
+ "([.a-z0-9+-]+)://[^[:space:]]+"
+ "(afp|attachment|platform):/[^[:space:]]+"
+ "aim:[^[:space:]]+\?[^[:space:]]+"
+ "(dab|fm):[^[:space:]]+\.[^[:space:]]+\.[^[:space:]]+"
+ "(data|geo):[^[:space:]]+,[^[:space:]]+"
+ "doi:10\.[^[:space:]]+"
+ "(msnim|things):[^[:space:]]+\?[^[:space:]]+"
+ "(feed|mvn|paparazzi|view-source):https?://[^[:space:]]+"
+ "git@[^[:space:]]+:[^[:space:]]+"
+ "gtalk:chat[^[:space:]]+"
+ "(im|mailto|reload|sips?|xmpp):[^[:space:]]+@[^[:space:]]+"
+ "jar:[^[:space:]]+!/[^[:space:]]+"
+ "magnet:\?xt=urn:[^[:space:]]+"
+ "maps:q=[^[:space:]]+"
+ "(sms|tel):\+[0-9]+"
+ "(jdbc|spotify|urn):[^[:space:]]+:[^[:space:]]+"
+ "tag:[^[:space:]]+,[^[:space:]]+:[^[:space:]]+"
+ "ymsgr:sendIM?[^[:space:]]+"
+ # TODO: develop strategy to avoid matching notebook: selectors
+ # "(apt|amss|barion|bitcoin|callto|cid|dns|drm|fax|gg):[^[:space:]]+"
+ # "(go|iax|javascript|message|mid|mvn|news|openpgp4fpr):[^[:space:]]+"
+ # "(palm|pres|proxy|psyc|query|session|shc|skype):[^[:space:]]+"
+ # "(stuns?|trueconf|turns?|ws|xfire):[^[:space:]]+"
+ # "itms:"
+)
+
+# Fallbacks for non-unicode environments.
+if [[ ! "${LANG:-x}" =~ (utf-|UTF-) ]]
+then
+ _CHEVRON_LEFT="<"
+ _CHEVRON_RIGHT=">"
+ _MD="-"
+fi
+
+# _alias_subcommand()
+#
+# Usage:
+# _alias_subcommand
+#
+# Description:
+# Create an of . NOTE: aliases also have to be added to
+# the $_SUBCOMMANDS variable.
+_alias_subcommand() {
+ local _alias="${2:-}"
+ local _subcommand="${1:-}"
+
+ if [[ -z "${_alias:-}" ]]
+ then
+ return 1
+ fi
+
+ shopt -u extglob
+
+ eval "_describe_${_alias}() { _describe_${_subcommand} \"\${@:-}\"; }"
+ eval "_${_alias}() { _${_subcommand} \"\${@:-}\"; }"
+
+ shopt -s extglob
+}
+
+# _file_is_archive()
+#
+# Usage:
+# _file_is_archive
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is an archive.
+# 1 (error, false) if not.
+export _ARCHIVE_FILE_EXTENSIONS=(
+ 7z
+ apk
+ bz2
+ dmg
+ gz
+ lz
+ rar
+ s7z
+ sit
+ sitx
+ sparsebundle
+ tar
+ tgz
+ tbz2
+ tlz
+ txz
+ xz
+ zip
+ zipx
+ Z
+)
+_file_is_archive() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ LC_ALL=C _contains "${_file_type}" "${_ARCHIVE_FILE_EXTENSIONS[@]}"
+}
+
+# _file_is_audio()
+#
+# Usage:
+# _file_is_audio ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is an audio file.
+# 1 (error, false) if not.
+export _AUDIO_FILE_EXTENSIONS=(
+ aac
+ aiff
+ flac
+ m4a
+ mp3
+ ogg
+ wav
+)
+_file_is_audio() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ LC_ALL=C _contains "${_file_type}" "${_AUDIO_FILE_EXTENSIONS[@]}"
+}
+
+# _file_is_bookmark()
+#
+# Usage:
+# _file_is_bookmark ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is a bookmark.
+# 1 (error, false) if not.
+export _BOOKMARK_FILE_EXTENSIONS=(
+ bookmark.md
+ bookmark.md.enc
+)
+_file_is_bookmark() {
+ # Use explicit matching for legacy bookmark name support.
+ [[ "${1:-}" =~ ([(-|.)]bookmark.md|[(-|.)]bookmark.md.enc) ]]
+}
+
+# _file_is_document()
+#
+# Usage:
+# _file_is_document ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is a Word, Open Office, PDF, or other document.
+# 1 (error, false) if not.
+export _DOCUMENT_FILE_EXTENSIONS=(
+ doc
+ docx
+ odt
+ pdf
+ rtf
+ xls
+ xlsx
+)
+_file_is_document() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ LC_ALL=C _contains "${_file_type}" "${_DOCUMENT_FILE_EXTENSIONS[@]}"
+}
+
+# _file_is_encrypted()
+#
+# Usage:
+# _file_is_encrypted
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is encrypted.
+# 1 (error, false) if not.
+export _ENCRYPTED_FILE_EXTENSIONS=(
+ enc
+)
+_file_is_encrypted() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ # `file` with 'soft' test is slow, so avoid calling it.
+ LC_ALL=C _contains "${_file_type}" "${_ENCRYPTED_FILE_EXTENSIONS[@]}" ||
+ {
+ ! LC_ALL=C _contains "${_file_type}" \
+ "${_TEXT_FILE_EXTENSIONS[@]}" \
+ "${_IMAGE_FILE_EXTENSIONS[@]}" \
+ "${_DOCUMENT_FILE_EXTENSIONS[@]}" \
+ "${_AUDIO_FILE_EXTENSIONS[@]}" \
+ "${_VIDEO_FILE_EXTENSIONS[@]}" &&
+ if _command_exists "file"
+ then
+ [[ "$(file "${_file_path:-}" \
+ --exclude=apptype \
+ --exclude=encoding \
+ --exclude=tokens \
+ --exclude=cdf \
+ --exclude=compress \
+ --exclude=elf \
+ --exclude=tar)" =~ (encrypted|openssl) ]]
+ else
+ return 1
+ fi
+ }
+}
+
+# _file_is_image()
+#
+# Usage:
+# _file_is_image ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is an image.
+# 1 (error, false) if not.
+export _IMAGE_FILE_EXTENSIONS=(
+ afphoto
+ ai
+ bmp
+ gif
+ heic
+ ind
+ indd
+ jpg
+ jpeg
+ png
+ psd
+ raw
+ svg
+ svgz
+ tif
+ tiff
+ webp
+)
+_file_is_image() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ LC_ALL=C _contains "${_file_type}" "${_IMAGE_FILE_EXTENSIONS[@]}"
+}
+
+# _file_is_text()
+#
+# Usage:
+# _file_is_text
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is text.
+# 1 (error, false) if not.
+#
+# Resources:
+# Markdown: https://superuser.com/a/285878
+export _TEXT_FILE_EXTENSIONS=(
+ bash
+ bib
+ coffee
+ css
+ go
+ html
+ js
+ json
+ jsx
+ latex
+ markdown
+ md
+ mdown
+ mdwn
+ mdtxt
+ mdtext
+ mkd
+ mkdn
+ org
+ py
+ rb
+ Rmd
+ rst
+ scss
+ sh
+ sql
+ tex
+ text
+ textile
+ ts
+ tsx
+ txt
+ xml
+)
+_file_is_text() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ # Avoid calling `file` for better performance.
+ LC_ALL=C _contains "${_file_type}" "${_TEXT_FILE_EXTENSIONS[@]}" ||
+ {
+ ! LC_ALL=C _contains "${_file_type}" \
+ "${_IMAGE_FILE_EXTENSIONS[@]}" \
+ "${_DOCUMENT_FILE_EXTENSIONS[@]}" \
+ "${_ENCRYPTED_FILE_EXTENSIONS[@]}" \
+ "${_AUDIO_FILE_EXTENSIONS[@]}" \
+ "${_VIDEO_FILE_EXTENSIONS[@]}" \
+ "${_ARCHIVE_FILE_EXTENSIONS[@]}" &&
+ [[ ! "${_file_type}" =~ \.epub$ ]] &&
+ [[ ! -d "${_file_path:-}" ]] &&
+ if _command_exists "file"
+ then
+ [[ "$(file "${_file_path:-}" \
+ --exclude=apptype \
+ --exclude=encoding \
+ --exclude=tokens \
+ --exclude=cdf \
+ --exclude=compress \
+ --exclude=elf \
+ --exclude=tar \
+ -b --mime-type)" =~ ^text ]]
+ else
+ return 0
+ fi
+ }
+}
+
+# _file_is_todo()
+#
+# Usage:
+# _file_is_todo ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is a todo.
+# 1 (error, false) if not.
+export _TODO_FILE_EXTENSIONS=(
+ todo.md
+ todo.md.enc
+)
+_file_is_todo() {
+ [[ "${1:-}" =~ (todo\.md|todo\.md\.enc) ]]
+}
+
+# _file_is_video()
+#
+# Usage:
+# _file_is_video ( | )
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If file is a video file.
+# 1 (error, false) if not.
+export _VIDEO_FILE_EXTENSIONS=(
+ avi
+ flv
+ m4p
+ m4v
+ mp2
+ mp4
+ mov
+ mpeg
+ mpg
+ qt
+ webm
+ wmv
+)
+_file_is_video() {
+ local _file_path="${1:-}"
+ local _file_type="${_file_path##*.}"
+
+ LC_ALL=C _contains "${_file_type}" "${_VIDEO_FILE_EXTENSIONS[@]}"
+}
+
+# _get_content()
+#
+# Usage:
+# _get_content [--title]
+#
+# Options:
+# --title Print the title or nothing if a title is not found.
+#
+# Description:
+# Print the title or first line of the text document at . When a title
+# is not found, print the first line, prefixed with '__first_line:'.
+#
+# Supports both Markdown h1 styles, YAML front matter, Org Mode, and LaTeX.
+#
+# - Markdown
+# - Supported:
+# - # This is a Title
+# - This is a Title
+# ===============
+# - ---
+# title: This is a Title
+# ---
+# - https://daringfireball.net/projects/markdown/syntax#header
+# - https://jekyllrb.com/docs/front-matter/
+# - Org Mode
+# - Supported:
+# - #+TITLE: This is a Title
+# - https://orgmode.org/guide/Export-Settings.html
+# - LaTeX
+# - Supported:
+# - \title{This is a Title}
+_get_content() {
+ local _counter=0
+ local _first_line=
+ local _in_code_block=0
+ local _in_front_matter=0
+ local _maybe_title=
+ local _path="${1:-}"
+ local _title=
+
+ if [[ "${2:-}" == --title ]]
+ then
+ local _only_title=1
+ else
+ local _only_title=0
+ fi
+
+ if [[ -z "${_path}" ]]
+ then
+ return 1
+ elif [[ -d "${_path}" ]]
+ then
+ return 0
+ elif [[ -e "${_path}" ]]
+ then
+
+ # `awk` `_get_first_line` alternative
+ # -----------------------------------
+ # awk -f - "${_path}" <<'HEREDOC'
+ # (state == 0) && /^---$/ { state=1; next }
+ # (state == 1) && /^---$/ { state=0; next }
+ # (state == 0) && /^```/ { state=1; next }
+ # (state == 1) && /^```/ { state=0; next }
+ #
+ # (state == 0) && /^./ { print; exit }
+ # HEREDOC
+
+ if [[ "${_path}" =~ (\.md$|\.markdown$) ]]
+ then
+ local __line=
+ while IFS= read -r __line || [[ -n "${__line:-}" ]]
+ do
+ if [[ "${__line}" =~ ^([[:space:]]*#[[:space:]]+) ]] &&
+ ! ((_in_code_block))
+ then # line starts with an atx-style H1
+ _title="${__line#"${BASH_REMATCH[1]:-}"}"
+
+ if [[ "${_title}" =~ ([[:space:]]+#[[:space:]]*)$ ]]
+ then
+ _title="${_title%"${BASH_REMATCH[1]:-}"}"
+ fi
+
+ # `sed` alternative
+ # -----------------
+ # _title="$(
+ # printf "%s\\n" "${__line}" \
+ # | sed \
+ # -e 's/^[[:space:]]*# //' \
+ # -e 's/ #$//'
+ # )"
+
+ break
+ elif [[ "${__line}" =~ ^---$ ]]
+ then # start or end of front matter block
+ if ((_in_front_matter))
+ then
+ _in_front_matter=0
+ else
+ _in_front_matter=1
+ fi
+ elif [[ "${__line}" =~ ^\`\`\` ]]
+ then # start or end of code block
+ if ((_in_code_block))
+ then
+ _in_code_block=0
+ else
+ _in_code_block=1
+ fi
+ elif ((_in_front_matter)) && [[ "${__line}" =~ ^title\: ]]
+ then # front matter title
+ _title="$(printf "%s\\n" "${__line}" | LC_ALL=C sed 's/^title: //')"
+
+ break
+ elif ! ((_in_front_matter)) && ! ((_in_code_block))
+ then
+ if [[ -z "${_maybe_title:-}" ]] && [[ -n "${__line:-}" ]]
+ then # potential setext-style H1
+ _maybe_title="${__line}"
+ elif [[ -n "${_maybe_title:-}" ]] &&
+ [[ "${__line}" =~ ^[[:space:]]*\=+$ ]]
+ then # underline for setext-style H1 on previous line
+ _title="$(
+ printf "%s\\n" "${_maybe_title}" | LC_ALL=C sed 's/^[[:space:]]*//'
+ )"
+
+ break
+ elif [[ -n "${_maybe_title:-}" ]]
+ then # in normal content, title not found
+ if ! ((_only_title))
+ then
+ if [[ "${#_maybe_title}" -gt 500 ]]
+ then
+ _first_line="${_maybe_title:0:500}..."
+ else
+ _first_line="${_maybe_title}"
+ fi
+ fi
+
+ if [[ ! "${_maybe_title}" =~ ^\[ ]]
+ then # only break with first non-link line
+ break
+ fi
+ else
+ continue
+ fi
+ fi
+ done < "${_path}"
+
+ if [[ -z "${_title:-"${_first_line:-}"}" ]] &&
+ [[ -n "${_maybe_title:-}" ]]
+ then
+ _first_line="${_maybe_title:-}"
+ fi
+ elif [[ "${_path}" =~ (\.latex$|\.tex$) ]]
+ then
+ local __line=
+ while IFS= read -r __line || [[ -n "${__line:-}" ]]
+ do
+ _counter="$((_counter+1))"
+
+ if [[ "${__line}" =~ ^\\title\{ ]]
+ then
+ _title="$(
+ printf "%s\\n" "${__line}" | LC_ALL=C sed 's/\\title{\(.*\)}/\1/'
+ )"
+
+ break
+ elif [[ -n "${__line:-}" ]]
+ then
+ if [[ -z "${_first_line:-}" ]]
+ then
+ _first_line="${__line}"
+ fi
+
+ if [[ ! "${__line}" =~ ^\\ ]] &&
+ [[ "${_counter}" -gt 20 ]]
+ then
+ break
+ fi
+ else
+ continue
+ fi
+ done < "${_path}"
+ elif [[ "${_path}" =~ \.org$ ]]
+ then
+ local __line=
+ while IFS= read -r __line || [[ -n "${__line}" ]]
+ do
+ _counter="$((_counter+1))"
+
+ # NOTE: Titles can be defined with multiple #+TITLE: lines.
+ if [[ "${__line}" =~ ^\#\+TITLE\: ]]
+ then
+ if [[ -n "${_title:-}" ]]
+ then
+ _title="${_title} "
+ fi
+
+ _title+="$(printf "%s\\n" "${__line}" | LC_ALL=C sed 's/^#+TITLE: //')"
+ elif [[ -n "${__line:-}" ]]
+ then
+
+ if [[ -z "${_first_line:-}" ]]
+ then
+ _first_line="${__line}"
+ fi
+
+ if [[ ! "${__line}" =~ ^\# ]] &&
+ [[ "${_counter}" -gt 10 ]]
+ then
+ break
+ fi
+ else
+ continue
+ fi
+ done < "${_path}"
+ elif ! ((_only_title)) &&
+ LC_ALL=C _contains "${_path##*.}" "${_TEXT_FILE_EXTENSIONS[@]}"
+ then
+ local __line=
+ while IFS= read -r __line || [[ -n "${__line:-}" ]]
+ do
+ if [[ -n "${__line:-}" ]]
+ then
+ if [[ "${#__line}" -gt 500 ]]
+ then
+ _first_line="${__line:0:500}..."
+ else
+ _first_line="${__line}"
+ fi
+
+ break
+ fi
+ done < "${_path}"
+ fi
+
+ if [[ -n "${_title:-}" ]]
+ then
+ printf "%s\\n" "${_title/%$'\r'}"
+ elif ! ((_only_title)) && [[ -n "${_first_line:-}" ]]
+ then
+ printf "%s\\n" "__first_line:${_first_line/%$'\r'}"
+ fi
+ fi
+}
+
+# _get_id_selector()
+#
+# Usage:
+# _get_id_selector | [--notebook]
+#
+# Options:
+# --notebook Always include the notebook name in the output.
+#
+# Description:
+# Convert a or to valid selector constructed with ids,
+# which are resolved first during selector resolution. This function gives
+# precedence to folder names in input.
+_get_id_selector() {
+ local _folder_path=
+ local _id_selector=
+ local _notebook_name=
+ local _notebook_path=
+ local _relative_path=
+ local _selector="${1:-}"
+ local _selector_relative_path=
+ local _trailing_slash=0
+
+ case "${2:-}" in
+ --notebook) local _always_include_notebook=1 ;;
+ *) local _always_include_notebook=0 ;;
+ esac
+
+ if [[ -z "${_selector:-}" ]]
+ then
+ return 0
+ fi
+
+ if [[ "${_selector:-}" =~ /$ ]]
+ then
+ _trailing_slash=1
+ fi
+
+ if [[ "${_selector:-}" =~ ^/ ]]
+ then # selector is full path
+ if [[ "${_selector}" =~ ^${NB_DIR} ]]
+ then
+ local _path_with_notebook="${_selector#"${NB_DIR}"/}"
+ _notebook_name="${_path_with_notebook%%/*}"
+ _notebook_path="${NB_DIR}/${_notebook_name}"
+
+ if [[ "${_path_with_notebook:-}" =~ / ]]
+ then
+ _selector_relative_path="${_path_with_notebook#*/}"
+ fi
+ else
+ _notebook_name="local"
+
+ _notebook_path="$(
+ cd "${_selector%/*}" &&
+ __set_notebook_paths &&
+ _notebooks current --path --no-color
+ )"
+
+ if [[ "${_selector:-}" == "${_notebook_path:-}" ]]
+ then
+ _selector_relative_path=
+ else
+ _selector_relative_path="${_selector#"${_notebook_path}"/}"
+ fi
+ fi
+ else
+ if [[ "${_selector:-}" =~ : ]]
+ then
+ _notebook_name="${_selector%%:*}"
+ _selector_relative_path="${_selector#*:}"
+ else
+ _selector_relative_path="${_selector:-}"
+ fi
+ fi
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ case "${_notebook_name:-}" in
+ local) _notebook_path="$(_notebooks --local --path --no-color)" ;;
+ *) _notebook_path="${NB_DIR}/${_notebook_name}" ;;
+ esac
+ fi
+
+ local _parent_folder_path=
+
+ if [[ -n "${_notebook_name:-}" ]]
+ then
+ _id_selector+="${_notebook_name}:"
+ _parent_folder_path="${_notebook_path:-}"
+ else
+ _parent_folder_path="$(_notebooks current --path)"
+
+ if ((_always_include_notebook))
+ then
+ if _notebooks current --local
+ then
+ _id_selector+="local:"
+ else
+ _id_selector+="${_parent_folder_path##*/}:"
+ fi
+ fi
+ fi
+
+ local _id_path=
+
+ if [[ -n "${_selector_relative_path:-}" ]]
+ then
+ local _selector_segments=()
+
+ IFS="/" read -ra _selector_segments <<< "${_selector_relative_path:-}"
+
+ local i=
+ for ((i=0; i < ${#_selector_segments[@]}; i++))
+ do
+ local _segment_id=
+
+ if [[ "${i}" -eq "$((${#_selector_segments[@]} - 1))" ]]
+ then # it's the last segment
+ if [[ "${_selector_segments[i]:-}" =~ ^[0-9]+$ ]] &&
+ ! ((_trailing_slash))
+ then
+ _segment_id="${_selector_segments[i]:-}"
+ fi
+ fi
+
+ if [[ -z "${_segment_id:-}" ]]
+ then
+ _segment_id="$(
+ _index get_id \
+ "${_selector_segments[i]:-}" \
+ "${_parent_folder_path}" || :
+ )"
+ fi
+
+ local _segment_basename=
+
+ if [[ -z "${_segment_id:-}" ]] &&
+ [[ "${_selector_segments[i]:-}" =~ ^[0-9]+$ ]]
+ then
+ _segment_basename="$(
+ _index get_basename \
+ "${_selector_segments[i]:-}" \
+ "${_parent_folder_path}" 2>/dev/null || :
+ )"
+
+ if [[ -n "${_segment_basename:-}" ]]
+ then
+ _segment_id="${_selector_segments[i]:-}"
+ fi
+ fi
+
+ if [[ -z "${_segment_id:-}" ]]
+ then
+ return 1
+ fi
+
+ if [[ -z "${_id_path:-}" ]]
+ then
+ _id_path+="${_segment_id}"
+ else
+ _id_path+="/${_segment_id}"
+ fi
+
+ _parent_folder_path+="/${_segment_basename:-${_selector_segments[i]:-}}"
+ done
+ fi
+
+ if [[ -n "${_id_path:-}" ]] &&
+ [[ ! "${_id_path}" == "/" ]]
+ then
+ _id_selector+="${_id_path}"
+ fi
+
+ if ((_trailing_slash))
+ then
+ _id_selector+="/"
+ fi
+
+ printf "%s\\n" "${_id_selector:-}"
+}
+
+# _get_visible_length()
+#
+# Usage:
+# _get_visible_length []
+#
+# Describe:
+# Calculate the apparent length of , with byte and character
+# calculations for handling unicode characters.
+#
+# More Information:
+# https://stackoverflow.com/a/31009961
+_get_visible_length() {
+ local _extra_length="${2:-0}"
+ local _string="${1:-}"
+
+ _LANG="${LANG:-}" _LC_ALL="${LC_ALL:-}"
+
+ local _length_chars=
+ _length_chars="${#_string}"
+
+ LANG=C LC_ALL=C
+
+ local _length_bytes=
+ _length_bytes="${#_string}"
+
+ LANG="${_LANG:-}" LC_ALL="${_LC_ALL:-}"
+
+ local _difference=
+ _difference=$(((_length_bytes-_length_chars)/3))
+
+ local _calculated_length=
+ _calculated_length=$((_length_chars+_difference))
+
+ if ((_extra_length))
+ then
+ _calculated_length=$((_calculated_length+_extra_length))
+ fi
+
+ printf "%s\\n" "${_calculated_length:-0}"
+}
+
+# _highlight_syntax_if_available()
+#
+# Usage:
+# _highlight_syntax_if_available [ | ]
+#
+# Description:
+# If `bat` or Pygments is available, use it to highlight syntax. When neither
+# is available, pipe through `cat`.
+#
+# References:
+# https://github.com/sharkdp/bat
+# https://pygments.org/
+_highlight_syntax_if_available() {
+ # Usage: _highlight_syntax_with_extension
+ _highlight_syntax_with_extension() {
+ ! _contains "${1:-}" "db" # TODO
+ }
+
+ local _extension=
+ local _path=
+
+ if [[ "${1:-}" =~ / ]]
+ then
+ _path="${1}"
+ elif [[ -n "${1:-}" ]]
+ then
+ _extension="${1}"
+ else
+ _extension="md"
+ fi
+
+ if ! ((_COLOR_ENABLED))
+ then
+ if [[ -n "${_path:-}" ]]
+ then
+ cat "${_path}"
+ else
+ cat
+ fi
+
+ return 0
+ fi
+
+ if hash "bat" 2>/dev/null
+ then
+ local _arguments=()
+
+ if [[ -n "${_path:-}" ]]
+ then
+ _arguments+=("${_path}")
+ elif _highlight_syntax_with_extension "${_extension}"
+ then
+ _arguments+=("--language" "${_extension}")
+
+ if ! printf "" | bat --color always \
+ --language "${_extension}" \
+ --paging never \
+ --plain \
+ 2>/dev/null
+ then
+ cat
+
+ return 0
+ fi
+ fi
+
+ bat "${_arguments[@]}" \
+ --color always \
+ --paging never \
+ --plain \
+ --theme "${NB_SYNTAX_THEME:-base16}"
+ elif hash "highlight" 2>/dev/null
+ then
+ local _arguments=("-O" "ansi")
+
+ if [[ -n "${_path:-}" ]]
+ then
+ _arguments+=("${_path}")
+ elif _highlight_syntax_with_extension "${_extension}"
+ then
+ _arguments+=("--syntax" "${_extension}")
+ fi
+
+ highlight "${_arguments[@]}"
+ elif ((_COLOR_ENABLED)) && hash "pygmentize" 2>/dev/null
+ then # pygments is installed.
+ local _arguments=("-O" "style=default" "-f" "terminal")
+
+ if [[ -n "${_path:-}" ]]
+ then
+ _arguments+=("${_path}")
+ elif _highlight_syntax_with_extension "${_extension}"
+ then
+ _arguments+=("-l" "${_extension}")
+ fi
+
+ pygmentize "${_arguments[@]}" 2>/dev/null
+ else
+ if [[ -n "${_path:-}" ]]
+ then
+ cat "${_path}"
+ else
+ cat
+ fi
+ fi
+}
+
+# _join()
+#
+# Usage:
+# _join ...
+#
+# Description:
+# Print a string containing all arguments separated by
+# .
+#
+# Example:
+# _join "${_delimeter}" "${_list[@]}"
+#
+# More Information:
+# https://stackoverflow.com/a/17841619
+_join() {
+ local _delimiter="${1:-}"
+
+ shift
+
+ local _joined_string="${1:-}"
+
+ shift
+
+ local __element=
+ for __element in "${@:-}"
+ do
+ [[ -n "${__element:-}" ]] || continue
+
+ _joined_string+="${_delimiter:-}${__element:-}"
+ done
+
+ printf "%s\\n" "${_joined_string}"
+}
+
+# _less_prompt()
+#
+# Usage:
+# _less_prompt
+#
+# Description:
+# The prompt to display in `less`.
+_less_prompt() {
+ local _prompt="\
+> scroll for more, h for help, or q to quit"
+
+ if [[ "${_COLUMNS}" -gt 81 ]]
+ then
+ _prompt="\
+> scroll for more, f / b to jump โ / โ, h for help, or q to quit"
+ fi
+
+ printf "%s\\n" "${_prompt}"
+}
+
+# _list_files()
+#
+# Usage:
+# _list_files [--reverse] [--sort] [--standard-input] [--type ]
+#
+# Description:
+# List files in the current notebook or filenames from standard input.
+_list_files() {
+ local _path=
+ local _read_from_standard_input=0
+ local _reverse=0
+ local _sort=0
+ local _type=
+
+ while ((${#}))
+ do
+ case "${1:-}" in
+ --reverse)
+ _reverse=1
+ ;;
+ --sort)
+ _sort=1
+ ;;
+ --standard-input)
+ _read_from_standard_input=1
+ ;;
+ --type)
+ if _option_value_is_present "${2:-}"
+ then
+ _type="${2}"
+
+ shift
+ fi
+ ;;
+ --[A-Za-z]*)
+ _type="${1:2}"
+ ;;
+ [^-]*)
+ if [[ -z "${_path:-}" ]] && [[ "${1:-}" =~ ^/ ]]
+ then
+ _path="${1}"
+ fi
+ esac
+
+ shift
+ done
+
+ if [[ -z "${_path:-}" ]]
+ then
+ _path="$(_notebooks current --path)"
+ fi
+
+ if ((_read_from_standard_input)) && _piped_input
+ then
+ cat
+ elif ((_sort))
+ then
+ cat "${_path}/.index"
+ else
+ ls -t -1 "${_path}"
+ fi | {
+ if ((_reverse))
+ then
+ # https://stackoverflow.com/a/744093
+ sed '1!G;h;$!d'
+ else
+ cat
+ fi
+ } | {
+ if [[ -n "${_type:-}" ]]
+ then
+ local __extension=
+ local _grep_patterns=()
+
+ case "${_type}" in
+ bookmark|bookmarks)
+ grep --color=never \
+ -e '\.bookmark\.' \
+ -e '\-bookmark\.'
+ ;;
+ book|books)
+ grep --color=never -e 'epub$'
+ ;;
+ note|notes)
+ for __extension in "${_TEXT_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "${__extension}$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}" \
+ | grep --color=never -v \
+ -e '\.bookmark\.' \
+ -e '\-bookmark\.'
+ ;;
+ folder|folders|directory|directories)
+ local __line=
+ while read -r __line
+ do
+ if [[ -d "${_path}/${__line}" ]]
+ then
+ printf "%s\\n" "${__line}"
+ fi
+ done
+ ;;
+ archive)
+ for __extension in "${_ARCHIVE_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "${__extension}$" "-e" "${__extension}.enc$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ audio|music)
+ for __extension in "${_AUDIO_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "${__extension}$" "-e" "${__extension}.enc$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ document|documents|doc|docs)
+ for __extension in "${_DOCUMENT_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$" "-e" "\.${__extension}\.enc$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ encrypted)
+ for __extension in "${_ENCRYPTED_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ image|images|picture|pictures)
+ for __extension in "${_IMAGE_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$" "-e" "\.${__extension}\.enc$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ text|txt)
+ for __extension in "${_TEXT_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ todo|todos)
+ for __extension in "${_TODO_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ video|videos)
+ for __extension in "${_VIDEO_FILE_EXTENSIONS[@]}"
+ do
+ _grep_patterns+=("-e" "\.${__extension}$" "-e" "\.${__extension}\.enc$")
+ done
+
+ grep --color=never "${_grep_patterns[@]}"
+ ;;
+ *)
+ grep --color=never -e "\.${_type}$"
+ ;;
+ esac
+ else
+ cat
+ fi
+ }
+}
+
+# _open_in_gui_app()
+#
+# Usage:
+# _open_in_gui_app |
+#
+# Description:
+# Open or in the system GUI web browser or application for the
+# given file type.
+_open_in_gui_app() {
+ local _target="${1:-}"
+
+ [[ -n "${_target:-}" ]] || return 1
+
+ if _command_exists "xdg-open"
+ then
+ xdg-open "${_target}"
+ elif _command_exists "open"
+ then
+ open "${_target}"
+ elif _command_exists "wslview"
+ then
+ wslview "${_target}"
+ else
+ return 1
+ fi
+}
+
+# _pager()
+#
+# Usage:
+# _pager
+#
+# Description:
+# Display standard input in the preferred pager, `less`, `bat`, or `cat`.
+_pager() {
+ local _less_options=()
+ _less_options=(-R --CLEAR-SCREEN --prompt="$(_less_prompt)")
+
+ if [[ -n "${PAGER:-}" ]]
+ then
+ local _pager_command=()
+ IFS=" " read -r -a _pager_command <<< "${PAGER:-}"
+
+ if [[ "${PAGER:-}" =~ less ]]
+ then
+ "${_pager_command[@]:-less}" "${_less_options[@]:-}"
+ else
+ "${_pager_command[@]:-cat}"
+ fi
+ elif _command_exists "less"
+ then
+ less "${_less_options[@]:-}"
+ elif _command_exists "bat"
+ then
+ bat --style numbers,grid
+ else
+ cat
+ fi
+}
+
+# _pandoc()
+#
+# Usage:
+# _pandoc [--standard-input | ] []
+#
+# Description:
+# Interact with pandoc, using iconv when available.
+_pandoc() {
+ if ! _command_exists "pandoc"
+ then
+ _warn printf "%spandoc%s not found.\\n" "${_BT}" "${_BT}"
+
+ return 1
+ fi
+
+ local _pandoc_arguments=()
+ local _path=
+ local _read_from_standard_input=0
+
+ case "${1:-}" in
+ --standard-input)
+ _read_from_standard_input=1
+ ;;
+ *)
+ _path="${1:-}"
+ ;;
+ esac
+
+ shift
+
+ [[ -n "${1:-}" ]] && _pandoc_arguments=("${@}")
+
+ if _command_exists "iconv" && {
+ ((_read_from_standard_input)) || _file_is_text "${_path:-}"
+ }
+ then
+ {
+ if ((_read_from_standard_input))
+ then
+ iconv -c -t utf-8
+ else
+ iconv -c -t utf-8 "${_path}"
+ fi
+ } | {
+ if [[ -n "${_pandoc_arguments[*]:-}" ]]
+ then
+ pandoc "${_pandoc_arguments[@]:-}" --wrap=preserve
+ else
+ pandoc --wrap=preserve
+ fi
+ } | {
+ iconv -f utf-8
+ }
+ else
+ if ! ((_read_from_standard_input))
+ then
+ _pandoc_arguments=("${_path}" "${_pandoc_arguments[@]:-}")
+ fi
+
+ if [[ -n "${_pandoc_arguments[*]:-}" ]]
+ then
+ pandoc "${_pandoc_arguments[@]:-}"
+ else
+ pandoc
+ fi
+ fi
+}
+
+# _piped_input()
+#
+# Usage:
+# _piped_input
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If piped input is present.
+# 1 (error, false) If the current input is interactive (eg, a shell).
+_piped_input() {
+ [[ ! -t 0 ]]
+}
+
+# _print_line()
+#
+# Usage:
+# _print_line [--muted] [--no-wrap] [--visible-length []
+#
+# Description:
+# Print a line of dashes the length of .
+#
+# More Information:
+# https://wiki.bash-hackers.org/commands/builtin/printf
+_print_line() {
+ local _extra_length=0
+ local _muted=0
+ local _text=
+ local _visible_length=0
+ local _wrap_line=1
+
+ while ((${#}))
+ do
+ case "${1:-}" in
+ --muted*) _muted=1 ;;
+ --no*w*) _wrap_line=0 ;;
+ --vis*) _visible_length=1 ;;
+ *)
+ case "${_text:-}" in
+ '') _text="${1:-}" ;;
+ *) _extra_length="${1:-}" ;;
+ esac
+ ;;
+ esac
+
+ shift
+ done
+
+ local _text_length=
+
+ if ((_visible_length))
+ then
+ _text_length="$(
+ _get_visible_length "${_text:-}" "${_extra_length:-0}"
+ )"
+ else
+ _text_length="${#_text}"
+ fi
+
+ local _full_width_line=0
+
+ if ! ((_wrap_line)) && {
+ [[ -z "${_text_length:-}" ]] ||
+ [[ "${_text_length:-}" -gt "${_COLUMNS}" ]]
+ }
+ then
+ _full_width_line=1
+ fi
+
+ local _line=
+
+ if ((_full_width_line))
+ then
+ printf -v _line "%*s" "${_COLUMNS}" ""
+ else
+ printf -v _line "%*s" "${_text_length}" ""
+ fi
+
+ if ((_muted))
+ then
+ _color_muted "${_line// /-}"
+ else
+ _color_secondary "${_line// /-}"
+ fi | if ((_full_width_line))
+ then
+ tr -d '\n'
+ else
+ cat
+ fi
+}
+
+# _print_padding()
+#
+# Usage:
+# _print_padding []
+#
+# Description:
+# Print a string of spaces that can be used as left padding to
+# center-align . is an optional boolean that can
+# be used to print no padding, returning an empty string.
+_print_padding() {
+ local _padding_centered="${3:-1}"
+ local _padding_columns="${2:-}"
+ local _padding_line="${1:-}"
+
+ local _padding_line_length="${#_padding_line}"
+
+ local _padding_amount=$(( (_padding_columns-_padding_line_length) / 2 ))
+
+ if ((_padding_centered)) && ((_padding_amount))
+ then
+ printf "%-${_padding_amount}s" " "
+ fi
+}
+
+# _print_welcome()
+#
+# Usage:
+# _print_welcome
+#
+# Description:
+# Print the welcome message.
+_print_welcome() {
+ local _padding=
+ _padding="$(_print_padding " / / | | | | |_) |" "${_COLUMNS}")"
+
+ _print_line "$(printf "%-${_COLUMNS}s" '.')"
+
+ cat <
+#
+# Description:
+# Print the identifier (non-notebook) portion of a selector.
+_selector_get_identifier() {
+ local _selector="${1:-}"
+
+ if [[ "${_selector:-}" =~ : ]] &&
+ [[ ! "${_selector:-}" =~ /.*: ]]
+ then
+ printf "%s\\n" "${_selector#*:}" # strip notebook name
+ else
+ printf "%s\\n" "${_selector:-}"
+ fi
+}
+
+# _selector_resolve_folders()
+#
+# Usage:
+# _selector_resolve_folders [] [--build]
+#
+# Options:
+# --build Build a new path that includes segments that don't exist yet.
+#
+# Description:
+# Resolve a selector path, which can be a mix of folder names and ids.
+_selector_resolve_folders() {
+ local _build_path=0
+ local _folder_relative_path=
+ local _notebook_path=
+ local _selector=
+
+ local __arg=
+ for __arg in "${@:-}"
+ do
+ case "${__arg}" in
+ --build)
+ _build_path=1
+ ;;
+ *)
+ if [[ -z "${_selector:-}" ]]
+ then
+ _selector="${__arg}"
+ else
+ _notebook_path="${__arg}"
+ fi
+ ;;
+ esac
+ done
+
+ local _selector_path="${_selector#*:}"
+
+ if [[ "${_selector}" =~ : ]]
+ then
+ local _selector_notebook_path=
+ _selector_notebook_path="$(
+ _notebooks show "${_selector%%:*}" --path 2>/dev/null || :
+ )"
+
+ if [[ -n "${_selector_notebook_path:-}" ]]
+ then
+ _notebook_path="${_selector_notebook_path}"
+ fi
+ fi
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ _notebook_path="$(_notebooks current --path)"
+ fi
+
+ if [[ "${_selector_path}" =~ ^/ ]]
+ then
+ _selector_path="${_selector_path#"${_notebook_path}"/}"
+ fi
+
+ if [[ ! "${_selector_path}" =~ / ]]
+ then
+ if ((_build_path))
+ then
+ _build_path=0
+ _selector_path="${_selector_path}/"
+ else
+ return 0
+ fi
+ fi
+
+ while [[ "${_selector_path}" =~ / ]]
+ do
+ local _folder_name=
+ local _parent_folder_path=
+ local _selector_segment="${_selector_path%%\/*}"
+
+ if [[ -z "${_folder_relative_path:-}" ]]
+ then
+ _parent_folder_path="${_notebook_path}"
+ else
+ _parent_folder_path="${_notebook_path}/${_folder_relative_path}"
+ fi
+
+ if [[ -n "${_selector_segment:-}" ]]
+ then
+ if [[ "${_selector_segment}" =~ ^[0-9]+$ ]]
+ then # folder name might be an id
+ _folder_name="$(
+ _index get_basename "${_selector_segment:?}" "${_parent_folder_path:?}" ||
+ printf "%s\\n" "${_selector_segment:?}"
+ )"
+ fi
+
+ if [[ -z "${_folder_name:-}" ]]
+ then
+ _folder_name="${_selector_segment}"
+ fi
+
+ if [[ -d "${_parent_folder_path:?}/${_folder_name:?}" ]] ||
+ ((_build_path))
+ then
+ if [[ -z "${_folder_relative_path:-}" ]]
+ then
+ _folder_relative_path="${_folder_name}"
+ else
+ _folder_relative_path+="/${_folder_name}"
+ fi
+
+ _selector_path="${_selector_path#*/}"
+
+ continue
+ fi
+ fi
+
+ return 1
+ done
+
+ printf "%s\\n" "${_folder_relative_path:-}"
+}
+
+# _selector_resolve_path()
+#
+# Usage:
+# _selector_resolve_path [] [--full | --relative]
+# [--skip-titles]
+#
+# Options:
+# --full Print the full path.
+#
+# Description:
+# Determine relative path of the file that is identified by the given ,
+# , , or and is in the current notebook or a
+# notebook specified with a colon prefix.
+#
+# Examples:
+# 1
+# example.md
+# title
+# relative/path/to/example.md
+# /path/to/example.md
+# notebook:1
+# notebook:example.md
+# notebook:title
+# notebook:relative/path/to/example.md
+# notebook:/path/to/example.md
+_selector_resolve_path() {
+ local _identifier=
+ local _notebook_path=
+ local _print_full_path=0
+ local _relative_path=
+ local _selector=
+ local _skip_titles=0
+
+ local __arg=
+ for __arg in "${@:-}"
+ do
+ case "${__arg:-}" in
+ --full)
+ _print_full_path=1
+ ;;
+ --skip-titles)
+ _skip_titles=1
+ ;;
+ [^-]*)
+ if [[ -z "${_selector:-}" ]]
+ then
+ _selector="${__arg%/}" # strip trailing /
+ else
+ _notebook_path="${__arg:-}"
+ fi
+ ;;
+ esac
+ done
+
+ if [[ -n "${_selector}" ]]
+ then
+ if [[ -z "${_notebook_path:-}" ]] &&
+ [[ "${_selector}" =~ (:|^/) ]]
+ then
+ _notebook_path="$(
+ _notebooks show "${_selector}" --path 2>/dev/null || :
+ )"
+ fi
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ _notebook_path="$(
+ _notebooks current --path 2>/dev/null || :
+ )"
+ fi
+
+ _identifier="$(_selector_get_identifier "${_selector}")"
+ _identifier="${_identifier%/}" # strip trailing /
+
+ if [[ -e "${_identifier}" ]] &&
+ [[ "${_identifier}" =~ ^${_notebook_path} ]]
+ then # is a full path in the current notebook
+ _relative_path="${_identifier/${_notebook_path}}"
+ _relative_path="${_relative_path#\/}"
+ elif [[ "${_identifier}" =~ ^/ ]] &&
+ [[ -e "${_identifier:-}" ]]
+ then # is a full path in a different notebook
+ _notebook_path="$(
+ _notebooks show "${_identifier}" --path 2>/dev/null
+ )"
+
+ _relative_path="${_identifier/"${_notebook_path}"\//}"
+ elif [[ -e "${_notebook_path}/${_identifier}" ]] &&
+ [[ ! "${_identifier}" =~ ^[0-9]+$ ]]
+ then # is a relative path with a non-numeric filename
+ _relative_path="${_identifier}"
+ else
+ local _folder_path="${_notebook_path}"
+ local _folder_relative_path=
+ _folder_relative_path="$(
+ _selector_resolve_folders "${_selector}" "${_notebook_path}"
+ )" || return 0
+
+ if [[ -n "${_folder_relative_path:-}" ]]
+ then
+ _folder_path="${_notebook_path}/${_folder_relative_path}"
+ fi
+
+ _identifier="${_identifier##*/}"
+
+ if [[ -e "${_folder_path}/${_identifier}" ]] &&
+ [[ ! "${_identifier}" =~ ^[0-9]+$ ]]
+ then # is relative path with a non-numeric filename
+ _relative_path="${_folder_relative_path}/${_identifier}"
+ elif [[ "${_identifier}" =~ ^[0-9]+$ ]]
+ then # is an id
+ local _basename=
+ _basename="$(_index get_basename "${_identifier}" "${_folder_path}")"
+
+ if [[ -n "${_basename:-}" ]]
+ then
+ if [[ -n "${_folder_relative_path:-}" ]]
+ then
+ _relative_path="${_folder_relative_path}/${_basename}"
+ else
+ _relative_path="${_basename}"
+ fi
+ elif [[ -e "${_notebook_path}/${_identifier}" ]]
+ then # is a relative path with numeric filename
+ _relative_path="${_identifier}"
+ fi
+ elif ! ((_skip_titles))
+ then # might be a title
+ local _maybe_title="${_identifier##*\/}"
+
+ _relative_path="$(
+ {
+ _list_files "${_folder_path}"
+ } | {
+ local __filename=
+ while read -r __filename || [[ -n "${__filename:-}" ]]
+ do
+ local _title=
+ _title="$(
+ _get_content "${_folder_path}/${__filename}" --title
+ )"
+
+ if [[ -n "${_title}" ]] &&
+ [[ "${_title}" == "${_maybe_title}" ]]
+ then
+ local _full_path="${_folder_path}/${__filename}"
+
+ printf "%s\\n" "${_full_path/"${_notebook_path}"\//}"
+
+ break
+ fi
+ done
+ }
+ )"
+ fi
+ fi
+ fi
+
+ if [[ -n "${_identifier:-}" ]] && [[ -z "${_relative_path:-}" ]]
+ then
+ return 0
+ elif ((_print_full_path))
+ then
+ printf "%s\\n" "$(_join "/" "${_notebook_path}" "${_relative_path:-}")"
+ else
+ printf "%s\\n" "${_relative_path:-}"
+ fi
+}
+
+# _spinner()
+#
+# Usage:
+# _spinner
+#
+# Description:
+# Display an ascii spinner while is running.
+#
+# Example Usage:
+# ```
+# _spinner_example() {
+# printf "Working..."
+# (sleep 1) &
+# _spinner $!
+# printf "Done!\\n"
+# }
+# (_spinner_example)
+# ```
+#
+# More Information:
+# http://fitnr.com/showing-a-bash-spinner.html
+_spinner() {
+ local _delay=0.1
+ local _pid="${1:-}"
+ local _spin_string="|/-\\"
+
+ if [[ -z "${_pid:-}" ]]
+ then
+ _exit_1 printf "Usage: _spinner \\n"
+ fi
+
+ while ps a | awk '{print $1}' | grep -q "${_pid}"
+ do
+ local _temp="${_spin_string#?}"
+
+ if ((_COLOR_ENABLED))
+ then
+ printf " %s%s[%s%c%s]%s" \
+ "${_TPUT_SGR0}" \
+ "${_TPUT_SETAF_8}" \
+ "${_TPUT_COLOR_PRIMARY}" \
+ "${_spin_string}" \
+ "${_TPUT_SETAF_8}" \
+ "${_TPUT_SGR0}"
+
+ printf "\b\b\b\b\b\b\b\b"
+ fi
+
+ sleep ${_delay}
+
+ _spin_string="${_temp}${_spin_string%"${_temp}"}"
+ done
+
+ if ((_COLOR_ENABLED))
+ then
+ printf " \b\b\b\b\b\b\b\b"
+ fi
+
+ return 0
+}
+
+# _string_is_email()
+#
+# Usage:
+# _string_is_email
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If the string is recognized as an email address.
+# 1 (error, false) if not.
+_string_is_email() {
+ [[ "${1:-}" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]+$ ]]
+}
+
+# _string_is_url()
+#
+# Usage:
+# _string_is_url
+#
+# Exit / Error / Return Status:
+# 0 (success, true) If the string is a recognized URL.
+# 1 (error, false) if not.
+_string_is_url() {
+ [[ -z "${1:-}" ]] && return 1
+
+ local _url_match_patterns=()
+
+ local __url_match_pattern=
+ for __url_match_pattern in "${_URL_PATTERNS[@]}"
+ do
+ _url_match_patterns+=(-e "^${__url_match_pattern}$")
+ done
+
+ printf "%s\\n" "${1:-}" | grep -E -q --color=never "${_url_match_patterns[@]:-}"
+}
+
+# _wrap()
+#
+# Usage:
+# _wrap [on | off]
+#
+# Description:
+# Turn on line wrapping / automatic margins.
+_wrap() {
+ if [[ "${1:-}" == "off" ]]
+ then
+ # NOTE: `tput` vs escape sequence:
+ # https://unix.stackexchange.com/a/558771
+ # https://unix.stackexchange.com/a/515938
+ # https://superuser.com/a/189068
+
+ # tput rmam
+ printf '\033[?7l'
+ elif [[ "${1:-}" == "on" ]]
+ then
+ # tput smam
+ printf '\033[?7h'
+ fi
+}
+
+###############################################################################
+# describe
+###############################################################################
+
+# describe()
+#
+# Usage:
+# describe
+# describe --get
+#
+# Options:
+# --get Print the description for if one has been set.
+#
+# Examples:
+# ```
+# describe "list" <. The
+# text can be passed as the second argument or as standard input.
+#
+# To make the text available to other functions, `describe()`
+# assigns the text to a variable with the format `$____DESCRIBE_`.
+#
+# When the `--get` option is used, the description for is printed, if
+# one has been set.
+describe() {
+ set +e
+ [[ -z "${1:-}" ]] &&
+ _exit_1 printf "describe(): required.\\n"
+
+ if [[ "${1}" == "--get" ]]
+ then # get ------------------------------------------------------------------
+ [[ -z "${2:-}" ]] &&
+ _exit_1 printf "describe(): required.\\n"
+
+ local _name="${2:-}"
+ local _describe_var="____DESCRIBE_${_name//-/_}"
+
+ if [[ ! "${_name}" =~ ^\- ]] &&
+ [[ "${_name}" =~ ^[A-Za-z0-9_-]+$ ]] &&
+ [[ -n "${!_describe_var:-}" ]]
+ then
+ printf "%s\\n" "${!_describe_var}"
+ elif _command_exists "_describe_${_name:-}"
+ then
+ "_describe_${_name:-}"
+ else
+ printf "No additional information for \`%s\`\\n" "${_name}"
+ fi
+ else # set ------------------------------------------------------------------
+ if [[ -n "${2:-}" ]]
+ then # argument is present
+ read -r -d '' "____DESCRIBE_${1//-/_}" <:][/]
+
+$(_color_primary "Description"):
+ Print the number of items in the first level of the current notebook,
+ , or the folder at .
+HEREDOC
+}
+_count() {
+ local _list_files_options=()
+ local _selector=
+ local _skip_unmatched_selector=0
+
+ while ((${#}))
+ do
+ case "${1:-}" in
+ --skip-unmatched-selector)
+ _skip_unmatched_selector=1
+ ;;
+ --type)
+ _list_files_options+=("${1}")
+ _list_files_options+=(_option_get_value "${1}" "${2:-}")
+
+ shift
+ ;;
+ -*|/*)
+ _list_files_options+=("${1}")
+ ;;
+ *)
+ _selector="${1}"
+ ;;
+ esac
+
+ shift
+ done
+
+ if [[ -n "${_selector:-}" ]]
+ then
+ local _selector_path=
+ _selector_path="$(_selector_resolve_path "${_selector}" --full)"
+
+ if [[ -z "${_selector_path:-}" ]]
+ then
+ if ! ((_skip_unmatched_selector))
+ then
+ _warn printf "Not found: %s\\n" "$(_color_primary "${_selector:-}")"
+
+ return 1
+ fi
+ else
+ _list_files_options+=("${_selector_path:-}")
+ fi
+ fi
+
+ _list_files "${_list_files_options[@]:-}" | wc -l | tr -d ' '
+}
+
+# git ยท ################################################################# ยท git
+
+_describe_git() {
+ cat <] | dirty]
+ ${_ME} git ...
+
+$(_color_primary "Subcommands"):
+ checkpoint Create a new git commit in the current notebook and sync with
+ the remote if \`${_ME} set auto_sync\` is enabled.
+ dirty 0 (success, true) if there are uncommitted changes in the
+ current notebook. 1 (error, false) if the notebook is clean.
+
+$(_color_primary "Description"):
+ Run \`git\` commands within the current notebook directory.
+
+$(_color_primary "Read More"):
+ ${_README_URL}#-git-sync
+ ${_README_URL}#-revision-history
+
+$(_color_primary "See Also"):
+ ${_ME} help history
+ ${_ME} help remote
+ ${_ME} help run
+ ${_ME} help status
+ ${_ME} help sync
+
+$(_color_primary "Examples"):
+ ${_ME} git status
+ ${_ME} git diff
+ ${_ME} git log
+ ${_ME} example:git status
+HEREDOC
+}
+_git() {
+ # _git_autosyncable()
+ #
+ # Usage:
+ # _git_autosyncable []
+ #
+ # Exit / Error / Return Status:
+ # 0 (success, true) If autosync should trigger.
+ # 1 (error, false) If autosync should not trigger.
+ _git_autosyncable() {
+ ((_GIT_ENABLED)) || return 1
+
+ local _last_fetch_timestamp=
+ local _maybe_fetch_timestamp=
+ local _notebook_path="${1:-}"
+
+ if [[ -z "${_notebook_path}" ]]
+ then
+ _notebook_path="$(_notebooks current --path)"
+ fi
+
+ if ! ((NB_AUTO_SYNC))
+ then # autosync not enabled.
+ return 1
+ fi
+
+ if ! git -C "${_notebook_path}" config --get remote.origin.url &>/dev/null
+ then # there is no remote configured.
+ return 1
+ fi
+
+ if [[ ! -e "${_notebook_path}/.git/FETCH_HEAD" ]]
+ then # no previous fetches, but has an origin and autosync is enabled.
+ return 0
+ fi
+
+ if _maybe_fetch_timestamp="$(
+ stat -c %Y "${_notebook_path}/.git/FETCH_HEAD" 2>/dev/null
+ )"
+ then # GNU
+ _last_fetch_timestamp="${_maybe_fetch_timestamp}"
+ elif _maybe_fetch_timestamp="$(
+ gstat -c %Y "${_notebook_path}/.git/FETCH_HEAD" 2>/dev/null
+ )"
+ then # GNU prefixed
+ _last_fetch_timestamp="${_maybe_fetch_timestamp}"
+ else
+ _last_fetch_timestamp="$(
+ stat -f '%m' "${_notebook_path}/.git/FETCH_HEAD"
+ )"
+ fi
+
+ local _current_timestamp=
+ _current_timestamp="$(date +%s)"
+
+ local _diff=
+ _diff=$((_current_timestamp-_last_fetch_timestamp))
+
+ [[ "${_diff}" -gt 60 ]]
+ }
+
+ # _git_checkpoint()
+ #
+ # Usage:
+ # _git_checkpoint
+ #
+ # Description:
+ # Commit all files in with the provided .
+ _git_checkpoint() {
+ ((_GIT_ENABLED)) || return 0
+
+ # Usage: _git_checkpoint_commit
+ _git_checkpoint_commit() {
+ local _message="${2:-}"
+ local _notebook_path="${1:-}"
+
+ if [[ -z "${_notebook_path:-}" ]] || [[ -z "${_message:-}" ]]
+ then
+ _exit_1 printf \
+ "Usage: _git_checkpoint_commit "
+ fi
+
+ git -C "${_notebook_path}" add --all &&
+ git -C "${_notebook_path}" commit -a -m "${_message}"
+ }
+
+ local _message=
+ local _notebook_path=
+ local _show_spinner=0
+
+ local __arg=
+ for __arg in "${@:-}"
+ do
+ case "${__arg}" in
+ --spinner)
+ _show_spinner=1
+ ;;
+ *)
+ if [[ -z "${_notebook_path}" ]] &&
+ [[ "${__arg}" =~ ^/ ]] &&
+ [[ -d "${__arg}" ]]
+ then
+ _notebook_path="${__arg}"
+ elif [[ -z "${_message}" ]]
+ then
+ _message="${__arg}"
+ fi
+ ;;
+ esac
+ done
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ _notebook_path="$(_notebooks current --path)"
+ fi
+
+ if [[ -z "${_message}" ]]
+ then
+ _message="[${_ME}] Commit"
+ fi
+
+ if ((NB_AUTO_SYNC))
+ then
+ (
+ {
+ # only sync when the index is dirty
+ if _git dirty "${_notebook_path}"
+ then
+ _git_checkpoint_commit "${_notebook_path}" "${_message}" &>/dev/null
+ fi && GIT_TERMINAL_PROMPT=0 _git sync "${_notebook_path}" &>/dev/null
+ } || :
+ ) &
+ else
+ (
+ _git_checkpoint_commit "${_notebook_path}" "${_message}" &>/dev/null ||
+ return 0
+ ) &
+ fi
+
+ if ((_show_spinner))
+ then
+ _spinner ${!}
+ fi
+
+ wait ${!}
+ return ${?}
+ }
+
+ # _git_dirty()
+ #
+ # Usage:
+ # _git_dirty []
+ #
+ # Exit / Error / Return Status:
+ # 0 (success, true) If there are uncommitted changes in
+ # or the current notebook.
+ # 1 (error, false) If or the current notebook is clean.
+ _git_dirty() {
+ ((_GIT_ENABLED)) || return 1
+
+ local _target_path="${1:-}"
+
+ if [[ -z "${_target_path:-}" ]]
+ then
+ _target_path="$(_notebooks current --path)"
+ fi
+
+ [[ -n "$(git -C "${_target_path}" status --porcelain)" ]]
+ }
+
+ # _git_out_of_sync()
+ #
+ # Usage:
+ # _git_out_of_sync []
+ #
+ # Exit / Error / Return Status:
+ # 0 (success, true) If or the current notebook is out of
+ # sync with the remote.
+ # 1 (error, false) If or the current notebook is synced
+ # with the remote.
+ _git_out_of_sync() {
+ ((_GIT_ENABLED)) || return 1
+
+ local _notebook_path="${1:-}"
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ _notebook_path="$(_notebooks current --path)"
+ fi
+
+ git -C "${_notebook_path}" status | grep -q 'diverged'
+ }
+
+ # _git_required()
+ #
+ # Usage:
+ # _git_required
+ #
+ # Description:
+ # Exit with `_exit_1` if `git` isn't found.
+ _git_required() {
+ ((_GIT_ENABLED)) || return 0
+
+ if ! _command_exists "git"
+ then
+ _exit_1 cat <]
+ #
+ # Description:
+ # Sync or the current notebook with the remote.
+ _git_sync() {
+ local _notebook_path="${1:-}"
+ local _prompt="${GIT_TERMINAL_PROMPT:-1}"
+
+ if _contains "--skip-prompt" "${1:-}" "${2:-}"
+ then
+ _prompt=0
+ fi
+
+ if [[ -z "${_notebook_path:-}" ]]
+ then
+ _notebook_path="$(_notebooks current --path)"
+ fi
+
+ local _remote_url=
+
+ if ! _remote_url="$(git -C "${_notebook_path}" remote get-url origin 2>/dev/null)" ||
+ [[ -z "${_remote_url:-}" ]]
+ then
+ return 0
+ fi
+
+ local _git_branch=
+ _git_branch="$(git -C "${_notebook_path}" rev-parse --abbrev-ref HEAD 2>/dev/null)"
+
+ local _branch_query_succeeded=0
+
+ local _remote_branches=
+ _remote_branches=($(
+ git -C "${_notebook_path}" ls-remote --heads "${_remote_url}" 2>/dev/null \
+ | LC_ALL=C sed "s/.*\///g"
+ )) && _branch_query_succeeded=1
+
+ if ! GIT_TERMINAL_PROMPT=0 git -C "${_notebook_path}" fetch origin "${_git_branch}" &>/dev/null
+ then
+ if _contains "${_git_branch}" "${_remote_branches[@]:-}" ||
+ ! ((_branch_query_succeeded))
+ then
+ if ((_prompt))
+ then # fetch again, displaying git prompt and/or errors after newline
+ printf "\\n"
+
+ if ! GIT_TERMINAL_PROMPT=1 git -C "${_notebook_path}" fetch origin "${_git_branch}"
+ then
+ exit 1
+ fi
+ else
+ return 1
+ fi
+ fi
+ fi
+
+ if ((_prompt))
+ then
+ if ! _contains "${_git_branch}" "${_remote_branches[@]:-}" ||
+ ! git -C "${_notebook_path}" show-branch \
+ "remotes/origin/${_git_branch}" > /dev/null 2>&1 ||
+ {
+ local _local_branch_root_hash=
+ _local_branch_root_hash="$(
+ git -C "${_notebook_path}" rev-list --max-parents=0 \
+ "${_git_branch}"
+ )"
+
+ local _remote_branch_root_hash=
+ _remote_branch_root_hash="$(
+ git -C "${_notebook_path}" rev-list --max-parents=0 \
+ "remotes/origin/${_git_branch}"
+ )"
+
+ [[ "${_local_branch_root_hash:-}" != "${_remote_branch_root_hash:-}" ]]
+ }
+ then
+ printf "\\n"
+
+ _remote set "${_remote_url}" "${_git_branch}" --skip-preamble
+
+ _git_branch="$(git -C "${_notebook_path}" rev-parse --abbrev-ref HEAD)"
+ fi
+ fi
+
+ if git -C "${_notebook_path}" show-branch \
+ "remotes/origin/${_git_branch}" \
+ > /dev/null 2>&1 &&
+ ! git -C "${_notebook_path}" rebase "origin/${_git_branch}" &>/dev/null
+ then
+ local _merge_error=1
+
+ local _conflicted_files=
+ _conflicted_files=($(
+ GIT_PAGER='' git -C "${_notebook_path}" diff --name-only --diff-filter=U
+ ))
+
+ local _conflicted_binary_files=()
+ local _conflicted_text_files=()
+
+ if [[ -n "${_conflicted_files[*]:-}" ]]
+ then
+ local __maybe_index_relative_path=
+ for __maybe_index_relative_path in "${_conflicted_files[@]}"
+ do
+ if [[ "${__maybe_index_relative_path}" =~ (^.index$|/.index$) ]]
+ then
+ # Remove git conflict markers to keep both.
+ # More info: https://stackoverflow.com/a/55187779
+ _sed_i \
+ -e '/^<<<<<<>>>>>>/d' \
+ -e '/=======/d' \
+ "${_notebook_path}/${__maybe_index_relative_path}"
+
+ git -C "${_notebook_path}" add "${__maybe_index_relative_path}"
+ fi
+ done
+
+ local __relative_path=
+ for __relative_path in "${_conflicted_files[@]}"
+ do
+ [[ "${__relative_path}" =~ (^.index$|/.index$) ]] && continue
+
+ if _file_is_text "${__relative_path}"
+ then
+ _conflicted_text_files+=("${__relative_path}")
+
+ git -C "${_notebook_path}" add \
+ "${__relative_path}"
+ else
+ git -C "${_notebook_path}" checkout \
+ "origin/${_git_branch}" -- "${__relative_path}"
+
+ local _conflicted_file=
+ _conflicted_file="$(
+ _notebooks show "${_notebook_path}" \
+ --filename "${__relative_path%%.*}--conflicted-copy.${__relative_path#*.}"
+ )"
+
+ _conflicted_binary_files+=("${_conflicted_file}")
+
+ git -C "${_notebook_path}" mv \
+ "${__relative_path}" "${_conflicted_file}"
+
+ git -C "${_notebook_path}" checkout \
+ "${_git_branch}" -- "${__relative_path}"
+
+ git -C "${_notebook_path}" add "${_conflicted_file}"
+ git -C "${_notebook_path}" add "${__relative_path}"
+ fi
+ done
+
+ GIT_EDITOR=true git -C "${_notebook_path}" rebase \
+ --continue &>/dev/null &&
+ _merge_error=0
+ fi
+
+ if ((_merge_error))
+ then
+ git -C "${_notebook_path}" rebase --abort
+
+ _warn printf \
+ "Merge conflict. Use \`%s git\` to merge manually.\\n" \
+ "${_ME}"
+
+ return 1
+ else
+ local _notebook_name=
+ _notebook_name="$(_notebooks show "${_notebook_path}" --name)"
+
+ if ((${#_conflicted_text_files[@]}))
+ then
+ printf "\\n"
+
+ _warn cat </dev/null || {
+ _warn printf "Unable to push to remote.\\n"
+
+ return 1
+ }
+ }
+
+ case "${1:-}" in
+ *autosync*)
+ _git_autosyncable "${2:-}"
+ ;;
+ checkpoint)
+ shift
+
+ _git_checkpoint "${@:-}"
+ ;;
+ dirty)
+ _git_dirty "${2:-}"
+ ;;
+ out*of*sync|o|oos|out)
+ _git_out_of_sync "${2:-}"
+ ;;
+ required|setup)
+ _git_required
+ ;;
+ sync)
+ _git_sync "${2:-}" "${3:-}"
+ ;;
+ *)
+ local _notebook_path=
+ _notebook_path="$(_notebooks current --path)"
+
+ if [[ -n "${1:-}" ]]
+ then
+ git -C "${_notebook_path}" "${@}"
+ else
+ git -C "${_notebook_path}"
+ fi
+ ;;
+ esac
+}
+
+# help ยท ############################################################### ยท help
+
+export _README_URL="https://github.com/xwmx/nb"
+
+_describe_help() {
+ cat <] [-p | --print]
+ ${_ME} help [-c | --colors] | [-r | --readme] | [-s | --short] [-p | --print]
+
+$(_color_primary "Options"):
+ -c, --colors View information about color themes and color settings.
+ -p, --print Print to standard output / terminal.
+ -r, --readme View the \`${_ME}\` README file.
+ -s, --short Print shorter help without subcommand descriptions.
+
+$(_color_primary "Description"):
+ Print the program help information. When a subcommand name is passed, print
+ the help information for the subcommand.
+
+$(_color_primary "Examples"):
+ ${_ME} help
+ ${_ME} help add
+ ${_ME} help import
+ ${_ME} h notebooks
+ ${_ME} h e
+
+$(_color_primary "Shortcut Alias"):
+ ${_ME} h
+HEREDOC
+}
+_help() {
+ local _arguments=()
+ local _color_help=0
+ local _readme=0
+ local _shell=0
+ local _short=0
+ local _use_pager=1
+
+ local __arg=
+ for __arg in "${@:-}"
+ do
+ case "${__arg}" in
+ +) _help "add" && return 0 ;;
+ -) _help "delete" && return 0 ;;
+ --pager)
+ _use_pager=1
+ ;;
+ -p|--print|--dump|--skip*pager|--no*pager)
+ _use_pager=0
+ ;;
+ -r|*readme*|*README*)
+ _readme=1
+ ;;
+ --shell)
+ _shell=1
+ ;;
+ -s|--short)
+ _short=1
+ ;;
+ -c|*color*|*colour*|*theme*)
+ _color_help=1
+ ;;
+ *)
+ _arguments+=("${__arg}")
+ ;;
+ esac
+ done
+
+ if ! ((_COLOR_ENABLED))
+ then
+ _TPUT_COLOR_PRIMARY=""
+ _TPUT_SETAF_8=""
+ _TPUT_SGR0=""
+ fi
+
+ if ((_readme))
+ then
+ _download_from \
+ "${_REPO_RAW_URL}/README.md" \
+ | if _command_exists "pandoc" &&
+ _web_browser --check
+ then
+ _pandoc \
+ --standard-input \
+ --from markdown \
+ --to html \
+ | _web_browser
+
+ return 0
+ else
+ _highlight_syntax_if_available \
+ | if ((_use_pager))
+ then
+ _pager
+ else
+ cat
+ fi
+ fi
+ else
+ if ((_color_help))
+ then
+ # NOTE: this screen mirrors the content from README.md#-color-themes:
+ # https://github.com/xwmx/nb#-color-themes
+ _color_primary " __ _ _"
+ _color_primary " \ \ _ __ | |__ ___ ___ | | ___ _ __ ___"
+ _color_primary " \ \ | '_ \| '_ \ _ / __/ _ \| |/ _ \| '__/ __|"
+ _color_primary " / / | | | | |_) | (_) | (_| (_) | | (_) | | \__ \\"
+ _color_primary " /_/ |_| |_|_.__/ \___\___/|_|\___/|_| |___/"
+ printf "\\n"
+ _color_secondary \
+ " ---------------------------------------------------"
+ printf "\\n"
+ cat <") $(_color_primary "${_ME} plugins install https://github.com/xwmx/nb/blob/master/plugins/turquoise.${_ME}-theme")
+ $(_color_secondary "Plugin installed:")
+ $(_color_secondary "/home/example/.${_ME}/.plugins/turquoise.${_ME}-theme")
+
+Once a theme is installed, use it with \`${_ME} set color_theme\`:
+
+ $(_color_muted ">") $(_color_primary "${_ME} set color_theme turquoise")
+ $(_color_secondary "NB_COLOR_THEME set to turquoise")
+
+The primary and secondary colors can also be overridden individually,
+making color themes easily customizable:
+
+ $(_color_secondary "# open the settings prompt for the primary color")
+ $(_color_primary "${_ME} set color_primary")
+
+ $(_color_secondary "# open the settings prompt for the secondary color")
+ $(_color_primary "${_ME} set color_secondary")
+
+To view a table of available colors and numbers, run:
+
+ $(_color_primary "${_ME} settings colors")
+
+To set the syntax highlighting color theme, use:
+
+ $(_color_primary "${_ME} set syntax_theme")
+
+HEREDOC
+ elif [[ -z "${_arguments[*]:-}" ]] || ((_short))
+ then
+ _color_primary " __ _"
+ _color_primary " \ \ _ __ | |__"
+ _color_primary " \ \ | '_ \| '_ \\"
+ _color_primary " / / | | | | |_) |"
+ _color_primary " /_/ |_| |_|_.__/"
+
+ printf \
+ "\\n %s\\n" \
+ "$(_color_secondary "------------------")"
+
+ cat < View help information for .
+ ${_ME} help --colors View information about color settings.
+ ${_ME} help --readme View the \`${_ME}\` README file.
+
+$(_color_primary "Usage"):
+ ${_ME}
+ ${_ME} [...] [ | | | | ]
+ ${_ME} [] [...]
+ ${_ME} add [:][/][] []
+ [-b | --browse] [-c | --content ] [--edit]
+ [-e | --encrypt] [-f | --filename ]
+ [--folder ] [--tags ,...]
+ [-t | --title ] [--type ]
+ ${_ME} add bookmark [...]
+ ${_ME} add folder []
+ ${_ME} add todo [...]
+ ${_ME} archive []
+ ${_ME} bookmark [...]
+ ${_ME} bookmark [:][/]
+ [-c | --comment ] [--edit] [-e | --encrypt]
+ [-f | --filename ] [--no-request]
+ [-q | --quote ] [--save-source]
+ [-r ( | ) | --related ( | )]...
+ [-t ,... | --tags ,...] [--title ]
+ ${_ME} bookmark [list [...]]
+ ${_ME} bookmark (edit | delete | open | peek | url)
+ ([:][/][ | | ])
+ ${_ME} bookmark search
+ ${_ME} browse [:][/][ | | ]
+ [-g | --gui] [-n | --notebooks] [-p | --print] [-q | --query ]
+ [-s | --serve] [-t | --tag | --tags ,...]
+ ${_ME} browse add [:][/][]
+ [-c | --content ] [--tags ,...]
+ [-t | --title ]
+ ${_ME} browse delete ([:][/][ | | ])
+ ${_ME} browse edit ([:][/][ | | ])
+ ${_ME} completions (check | install [-d | --download] | uninstall)
+ ${_ME} copy ([:][/][ | | ])
+ [[:][/]]
+ ${_ME} count [:][/]
+ ${_ME} delete ([:][/][ | | ])...
+ [-f | --force]
+ ${_ME} do ([:][/][ | | ])
+ []
+ ${_ME} edit ([:][/][ | | ])
+ [-c | --content ] [--edit]
+ [-e | --editor ] [--overwrite] [--prepend]
+ ${_ME} export ([