From 891f946e86f8a0911f67983d52fccacc9f8ebbad Mon Sep 17 00:00:00 2001 From: Julio Capote Date: Wed, 13 Jul 2022 23:40:24 -0400 Subject: initial --- LICENSE | 20 +++++++++ README.md | 41 +++++++++++++++++ bin/recit | 1 + completions/recit.bash | 14 ++++++ completions/recit.zsh | 19 ++++++++ lib/loader | 12 +++++ lib/recfile_template | 19 ++++++++ libexec/recit | 41 +++++++++++++++++ libexec/recit-add-entry | 46 ++++++++++++++++++++ libexec/recit-add-project | 9 ++++ libexec/recit-commands | 42 ++++++++++++++++++ libexec/recit-completions | 14 ++++++ libexec/recit-edit | 16 +++++++ libexec/recit-edit-entry | 32 ++++++++++++++ libexec/recit-help | 104 ++++++++++++++++++++++++++++++++++++++++++++ libexec/recit-init | 94 +++++++++++++++++++++++++++++++++++++++ libexec/recit-open-projects | 8 ++++ libexec/recit-setup | 12 +++++ libexec/recit-sh-shell | 4 ++ libexec/recit-today | 13 ++++++ libexec/recit-tomorrow | 11 +++++ libexec/recit-yesterday | 11 +++++ share/recit/example | 4 ++ 23 files changed, 587 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 120000 bin/recit create mode 100644 completions/recit.bash create mode 100644 completions/recit.zsh create mode 100644 lib/loader create mode 100644 lib/recfile_template create mode 100755 libexec/recit create mode 100755 libexec/recit-add-entry create mode 100755 libexec/recit-add-project create mode 100755 libexec/recit-commands create mode 100755 libexec/recit-completions create mode 100755 libexec/recit-edit create mode 100755 libexec/recit-edit-entry create mode 100755 libexec/recit-help create mode 100755 libexec/recit-init create mode 100755 libexec/recit-open-projects create mode 100755 libexec/recit-setup create mode 100755 libexec/recit-sh-shell create mode 100755 libexec/recit-today create mode 100755 libexec/recit-tomorrow create mode 100755 libexec/recit-yesterday create mode 100644 share/recit/example diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef54b21 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2021 Sam Stephenson, Nick Quaranto + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..825c21a --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# recit + +personal productivity system backed by [recfiles](https://en.wikipedia.org/wiki/Recfiles) + +# dependencies + +* [recutils](https://www.gnu.org/software/recutils/) +* uuidgen +* any perl + +# workflows + +You can use `recit` to track things like: + +* What needs to be done today? +* What did you do yesterday? +* What did you talk about in that 1:1 a few weeks ago? +* Where did you leave off on that project you started last year? +* What did you do last year? + +# usage + +`recit setup` gets you started + +## add some entries + +`recit add-entry` opens `$EDITOR` to let you add an entry at that moment in time, which is the same +as `recit add-entry now` or `recit add-entry today`. If the notes are short, you can just +pass it as a 3rd argument, like `recit add-entry now "this is the entry"` + +You can also add entries in the future by running `recit add-entry tomorrow` or any date like `recit add-entry 2022-07-22`. + + +## view entries + +`recit today` or `recit tomorrow` (also `recit yesterday`) + +## edit entries + +you'll notice that `recit add-entry` returns a UUID, you can pass this UUID to `recit edit-entry` +to edit the entry \ No newline at end of file diff --git a/bin/recit b/bin/recit new file mode 120000 index 0000000..b4e740f --- /dev/null +++ b/bin/recit @@ -0,0 +1 @@ +../libexec/recit \ No newline at end of file diff --git a/completions/recit.bash b/completions/recit.bash new file mode 100644 index 0000000..76ab736 --- /dev/null +++ b/completions/recit.bash @@ -0,0 +1,14 @@ +_recit() { + COMPREPLY=() + local word="${COMP_WORDS[COMP_CWORD]}" + + if [ "$COMP_CWORD" -eq 1 ]; then + COMPREPLY=( $(compgen -W "$(recit commands)" -- "$word") ) + else + local command="${COMP_WORDS[1]}" + local completions="$(recit completions "$command")" + COMPREPLY=( $(compgen -W "$completions" -- "$word") ) + fi +} + +complete -F _recit recit diff --git a/completions/recit.zsh b/completions/recit.zsh new file mode 100644 index 0000000..db7a043 --- /dev/null +++ b/completions/recit.zsh @@ -0,0 +1,19 @@ +if [[ ! -o interactive ]]; then + return +fi + +compctl -K _recit recit + +_recit() { + local word words completions + read -cA words + word="${words[2]}" + + if [ "${#words}" -eq 2 ]; then + completions="$(recit commands)" + else + completions="$(recit completions "${word}")" + fi + + reply=("${(ps:\n:)completions}") +} diff --git a/lib/loader b/lib/loader new file mode 100644 index 0000000..9b01d2a --- /dev/null +++ b/lib/loader @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +function load_recit { + local recfile_location + if [[ -f "$HOME/.recit.location" ]]; then + recfile_location=$(cat "$HOME/.recit.location") + else + echo "$HOME/.recit.location not found" + exit 1 + fi + echo "$recfile_location" +} \ No newline at end of file diff --git a/lib/recfile_template b/lib/recfile_template new file mode 100644 index 0000000..674dc7b --- /dev/null +++ b/lib/recfile_template @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +function recfile_template { + echo "# recit schema version v0.0.1 +%rec: Project +%key: Id + +Id: example-project +Status: open +Notes: blah blah + +%rec: Entry +%key: Id +%type: Time date +%auto: Time +%type: ProjectRef rec Project +%sort: Time + " +} \ No newline at end of file diff --git a/libexec/recit b/libexec/recit new file mode 100755 index 0000000..8573b1f --- /dev/null +++ b/libexec/recit @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -e + +resolve_link() { + $(type -p greadlink readlink | head -1) "$1" +} + +abs_dirname() { + local cwd="$(pwd)" + local path="$1" + + while [ -n "$path" ]; do + cd "${path%/*}" + local name="${path##*/}" + path="$(resolve_link "$name" || true)" + done + + pwd + cd "$cwd" +} + +libexec_path="$(abs_dirname "$0")" +export _RECIT_ROOT="$(abs_dirname "$libexec_path")" +export PATH="${libexec_path}:$PATH" + +command="$1" +case "$command" in +"" | "-h" | "--help" ) + exec recit-help + ;; +* ) + command_path="$(command -v "recit-$command" || true)" + if [ ! -x "$command_path" ]; then + echo "recit: no such command \`$command'" >&2 + exit 1 + fi + + shift + exec "$command_path" "$@" + ;; +esac diff --git a/libexec/recit-add-entry b/libexec/recit-add-entry new file mode 100755 index 0000000..ae0be2f --- /dev/null +++ b/libexec/recit-add-entry @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Usage: recit add-entry [time|"now"] "notes" (on 2nd argument opens $EDITOR) + +set -e + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +uuid=$(uuidgen) + +notes="" +query_time="" + +if [[ -z "$1" ]]; then + query_time="now" +else + query_time="$1" +fi + +if [[ -z "$2" ]]; then + + if [[ -z ${EDITOR+x} ]]; then + echo "$EDITOR is not defined please pass a message" + exit 1 + fi + + tmpfile="$(mktemp)" + command $EDITOR "$tmpfile" + notes=$(cat "$tmpfile") + rm "$tmpfile" +else + notes=$2 +fi + +if [[ "$query_time" == "now" || "$query_time" == "today" ]]; then + fmt_date=$(date '+%Y-%m-%d %r') +elif [[ "$query_time" == "tomorrow" ]]; then + fmt_date=$(perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time + 86400)), qq(\n);") +else + fmt_date=$query_time +fi + +recins --verbose -f Id -v "$uuid" -f Time -v "$fmt_date" -f Notes -v "$notes" -t Entry "${recfile}" +echo "$uuid" \ No newline at end of file diff --git a/libexec/recit-add-project b/libexec/recit-add-project new file mode 100755 index 0000000..25627bc --- /dev/null +++ b/libexec/recit-add-project @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Usage: recit add-project "notes" + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +recins --verbose -f Id -v "$1" -t Project "${recfile}" \ No newline at end of file diff --git a/libexec/recit-commands b/libexec/recit-commands new file mode 100755 index 0000000..edc18dd --- /dev/null +++ b/libexec/recit-commands @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Usage: recit commands +# Summary: List all recit commands +# Help: This command is mostly used for autocompletion in various shells, and for `recit help`. +# Also, this command helps find commands that are named the same as potentially builtin shell commands (which, cd, etc) + +set -e + +# Provide recit completions +if [ "$1" = "--complete" ]; then + echo --sh + echo --no-sh + exit +fi + +if [ "$1" = "--sh" ]; then + sh=1 + shift +elif [ "$1" = "--no-sh" ]; then + nosh=1 + shift +fi + +shopt -s nullglob + +{ for path in ${PATH//:/$'\n'}; do + for command in "${path}/recit-"*; do + command="${command##${path}/recit-}" + if [ -n "$sh" ]; then + if [ ${command:0:3} = "sh-" ]; then + echo ${command##sh-} + fi + elif [ -n "$nosh" ]; then + if [ ${command:0:3} != "sh-" ]; then + echo ${command##sh-} + fi + else + echo ${command##sh-} + fi + done + done +} | sort | uniq diff --git a/libexec/recit-completions b/libexec/recit-completions new file mode 100755 index 0000000..2384dc3 --- /dev/null +++ b/libexec/recit-completions @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +COMMAND="$1" +if [ -z "$COMMAND" ]; then + echo "usage: recit completions COMMAND [arg1 arg2...]" >&2 + exit 1 +fi + +COMMAND_PATH="$(command -v "recit-$COMMAND")" +if grep -i "^# provide recit completions" "$COMMAND_PATH" >/dev/null; then + shift + exec "$COMMAND_PATH" --complete "$@" +fi diff --git a/libexec/recit-edit b/libexec/recit-edit new file mode 100755 index 0000000..f105bdc --- /dev/null +++ b/libexec/recit-edit @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Usage: recit edit + +set -e + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +if [[ -z ${EDITOR+x} ]]; then + echo "$EDITOR is not defined please pass a message" + exit 1 +fi + +command $EDITOR "$recfile" \ No newline at end of file diff --git a/libexec/recit-edit-entry b/libexec/recit-edit-entry new file mode 100755 index 0000000..0636bc6 --- /dev/null +++ b/libexec/recit-edit-entry @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Usage: recit edit-entry uuid + +set -e + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +uuid=$1 + +notes=$(recsel -e "Id = '$uuid'" -t Entry -P Notes "${recfile}") +time=$(recsel -e "Id = '$uuid'" -t Entry -P Time "${recfile}") + +if [[ -z $notes ]]; then + echo "record not found" + exit 1 +fi + +if [[ -z ${EDITOR+x} ]]; then + echo "$EDITOR is not defined please pass a message" + exit 1 +fi + +tmpfile="$(mktemp)" +echo "$notes" > $tmpfile +command $EDITOR "$tmpfile" +notes=$(cat "$tmpfile") +rm "$tmpfile" + +recins -e "Id = '$uuid'" -t Entry -f Id -v "$uuid" -f Notes -v "$notes" -f Time -v "$time" "${recfile}" diff --git a/libexec/recit-help b/libexec/recit-help new file mode 100755 index 0000000..1c6371b --- /dev/null +++ b/libexec/recit-help @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +set -e + +print_summaries() { + local commands=() + local summaries=() + local longest_command=0 + local command + + for command in $(recit-commands); do + local file="$(command_path "$command")" + if [ ! -h "$file" ]; then + local summary="$(summary "$file")" + if [ -n "$summary" ]; then + commands["${#commands[@]}"]="$command" + summaries["${#summaries[@]}"]="$summary" + + if [ "${#command}" -gt "$longest_command" ]; then + longest_command="${#command}" + fi + fi + fi + done + + local index + local columns="$(tput cols)" + local summary_length=$(( $columns - $longest_command - 5 )) + + for (( index=0; index < ${#commands[@]}; index++ )); do + printf " %-${longest_command}s %s\n" "${commands[$index]}" \ + "$(truncate "$summary_length" "${summaries[$index]}")" + done +} + +print_help() { + local file="$1" + local usage="$(usage "$file")" + + if [ -n "$usage" ]; then + echo "$usage" + + local summary="$(summary "$file")" + [ -n "$summary" ] && echo "Summary: $summary" + + local help="$(help "$file")" + [ -n "$help" ] && echo && echo "$help" + else + echo "Sorry, this command isn't documented yet." + fi +} + +command_path() { + command -v "recit-$command" || command -v "recit-sh-$command" || true +} + +summary() { + sed -n "s/^# Summary: \(.*\)/\1/p" "$1" +} + +usage() { + sed -n "s/^# \(Usage: .*\)/\1/p" "$1" +} + +help() { + awk '/^[^#]/{p=0} /^# Help:/{p=1} p' "$1" | sed "s/^# Help: //;s/^# //;s/^#//" +} + +truncate() { + local max_length="$1" + local string="$2" + + if [ "${#string}" -gt "$max_length" ]; then + local length=$(( $max_length - 3 )) + echo "${string:0:$length}..." + else + echo "$string" + fi +} + +# Provide recit completions +if [ "$1" = "--complete" ]; then + exec "recit-commands" + exit +fi + +command="$1" +case "$command" in +"") echo "Usage: recit [] + +Some useful recit commands are: +$(print_summaries) + +See 'recit help ' for information on a specific command." +;; +*) + file="$(command_path "$command")" + + if [ -n "$file" ]; then + print_help "$file" + else + echo "recit: no such command \`$command'" >&2 + exit 1 + fi +esac diff --git a/libexec/recit-init b/libexec/recit-init new file mode 100755 index 0000000..a5df7d1 --- /dev/null +++ b/libexec/recit-init @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -e + +print="" +if [ "$1" = "-" ]; then + print=1 + shift +fi + +shell="$1" +if [ -z "$shell" ]; then + shell="$(basename "$SHELL")" +fi + +resolve_link() { + $(type -p greadlink readlink | head -1) $1 +} + +abs_dirname() { + local cwd="$(pwd)" + local path="$1" + + while [ -n "$path" ]; do + cd "${path%/*}" + local name="${path##*/}" + path="$(resolve_link "$name" || true)" + done + + pwd + cd "$cwd" +} + +root="$(abs_dirname "$0")/.." + +if [ -z "$print" ]; then + case "$shell" in + bash ) + profile='~/.bash_profile' + ;; + zsh ) + profile='~/.zshenv' + ;; + * ) + profile='your profile' + ;; + esac + + { echo "# Load recit automatically by adding" + echo "# the following to ${profile}:" + echo + echo "eval \"\$(${_RECIT_ROOT}/bin/recit init -)\"" + echo + } >&2 + + exit 1 +fi + +echo "export PATH=\"\${PATH}:${_RECIT_ROOT}/bin\"" + +case "$shell" in +bash | zsh ) + echo "source \"$root/completions/recit.${shell}\"" + ;; +esac + +commands=(`recit commands --sh`) +IFS="|" +cat < "$HOME/.recit.db" +echo "$HOME/.recit.db" > "$HOME/.recit.location" diff --git a/libexec/recit-sh-shell b/libexec/recit-sh-shell new file mode 100755 index 0000000..10ac03c --- /dev/null +++ b/libexec/recit-sh-shell @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +echo $1 diff --git a/libexec/recit-today b/libexec/recit-today new file mode 100755 index 0000000..96cbec4 --- /dev/null +++ b/libexec/recit-today @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Usage: recit today + +set -e + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +fmt_date=$(date '+%Y-%m-%d') + +recsel -e "(Time >> '$fmt_date 00:00:00' && Time << '$fmt_date 23:59:59') || Time == '$fmt_date'" -t Entry "${recfile}" diff --git a/libexec/recit-tomorrow b/libexec/recit-tomorrow new file mode 100755 index 0000000..c5802d1 --- /dev/null +++ b/libexec/recit-tomorrow @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Usage: recit tomorrow + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +fmt_date=$(perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time + 86400)), qq(\n);") + +recsel -e "(Time >> '$fmt_date 00:00:00' && Time << '$fmt_date 23:59:59') || Time == '$fmt_date'" -t Entry "${recfile}" diff --git a/libexec/recit-yesterday b/libexec/recit-yesterday new file mode 100755 index 0000000..ad6e804 --- /dev/null +++ b/libexec/recit-yesterday @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Usage: recit yesterday + +# shellcheck source=/dev/null +source lib/loader + +recfile=$(load_recit) + +fmt_date=$(perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time - 86400)), qq(\n);") + +recsel -e "(Time >> '$fmt_date 00:00:00' && Time << '$fmt_date 23:59:59') || Time == '$fmt_date'" -t Entry "${recfile}" diff --git a/share/recit/example b/share/recit/example new file mode 100644 index 0000000..8895044 --- /dev/null +++ b/share/recit/example @@ -0,0 +1,4 @@ +here's an example file you could read +it has stuff in it +maybe you want some config values +or even some YAML? -- cgit v1.2.3