From 66d0865df0311af65c40aa1930bb36f9dd3b9fe6 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 4 May 2023 14:07:57 +0200 Subject: [PATCH] added lua + micropython + kanban --- .../board/v86/rootfs_overlay/bin/kanban | 353 ++++++++++++++++++ buildroot-v86/configs/v86_defconfig | 2 + 2 files changed, 355 insertions(+) create mode 100755 buildroot-v86/board/v86/rootfs_overlay/bin/kanban diff --git a/buildroot-v86/board/v86/rootfs_overlay/bin/kanban b/buildroot-v86/board/v86/rootfs_overlay/bin/kanban new file mode 100755 index 0000000..08ac79e --- /dev/null +++ b/buildroot-v86/board/v86/rootfs_overlay/bin/kanban @@ -0,0 +1,353 @@ +#!/usr/bin/env bash +# +# commandline asciii kanban board for minimalist productivity bash hackers (csv-based) +# +# Usage: +# +# kanban init # initialize kanban in current directory +# kanban add # add item interactive (adviced) +# kanban show [status] .... # show ascii kanban board [with status] +# kanban # edit or update item +# kanban # update status of todo id (uses $EDITOR as preferred editor) +# kanban ..... # list only todo items with this status(es) +# kanban list # list all todos (heavy) +# kanban tags # list all submitted tags +# kanban add # add item (use quoted strings for args) +# kanban stats status [tag] +# kanban stats tag +# kanban stats history +# kanban csv # edit raw csv +# +# NOTE #1: statuses can be managed in ~/.kanban/.kanban.conf +# NOTE #2: the database csv can be found in ~/.kanban/.kanban.csv +# +# Examples: +# +# kanban add TODO projectX "do foo" +# kanban TODO DOING HOLD +# kanban stats status projectX +# kanban stats tag projectX +# # notekeeping by entering a filename as description: +# echo hello > note.txt && kanban add DOING note.txt +# # store in github repo +# git clone https://../foo.git && cd foo.git && kanban init && git add .kanban +# +# Environment: +# +# X=120 kanban .... # set max line-width to 120 +# NOCOLOR=1 kanban .... # disable colors +# PLAIN=1 kanban ... # plaintext, disable utf8 chars +# +# Tips: +# +# * /.kanban or ~/.kanban dir is checked for configuration / data +# * put .kanban directories in project-dirs +# * entering an existing text-filename as description enables note-keeping +# +# Copyright (C) 2015, Leon van Kammen / Coder of Salvation +# +# 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 . +deps="tput column pr gawk" +for dep in $deps; do which $dep &>/dev/null || { echo "$dep-cmd not installed..aborting (maybe install coreutils util-linux pkgs?)"; exit 1; }; done +BOX=(┌ ─ ┐ └ ┘ │ ┤ ┴ ┬ ├ ┼) +BAR=(▁ ▂ ▃ ▄ ▅ ▆ ▆ ▇ ▇ '█' '█') +COL0="\033[0m" +COL1="\033[37;1m" +COL2="\033[91;1m" +COL3="\033[91;5m" +TMP=~/.kanban.tmp +[[ ! -n $TERM ]] && TERM=vt100 +locale | grep -q "UTF-8" && UTF8=1 +[[ -n $PLAIN ]] && unset UTF8 +[[ ! -n $X ]] && X=$(tput cols) # get size of terminal window +[[ ! -n $Y ]] && Y=$(tput lines) # +SMALLSCREEN=('SCHEDULED' 'HOLD' 'DOING') # uncomment to get simplified kanban board +[[ ! -n $XSMALL ]] && XSMALL=119 +FILE_CONF=".kanban/.kanban.conf" +FILE_CSV=".kanban/.kanban.csv" +[[ ! -n $EDITOR ]] && { + which nano &>/dev/null && EDITOR=nano + which pico &>/dev/null && EDITOR=pico +} + +# migration: move config files of old kanban versions to .kanban +moveconfig(){ + mkdir $1/.kanban + echo "[!] old config files detected..moving to .kanban-folder" + mv $1/.kanban.* $1/.kanban/. + sleep 3s + clear +} +[[ -f ~/.kanban.csv ]] && moveconfig ~ +[[ -f .kanban.csv ]] && moveconfig "$(pwd)" + + +config_example="# kanban config file +statuses=('TODO' 'HOLD' 'DOING' 'DONE' 'NOTES' 'BACKLOG') + +XSMALL=119 # show small kanban for terminalwidth < 119 chars +SMALLSCREEN=('DOING' 'TODO' 'HOLD') # define simplified kanban board statuses + +# maximum amount of todos within status (triggers warning when exceeds) +declare -A maximum_todo +maximum_todo[HOLD]=10 +maximum_todo[DOING]=5 +" + +# usage: fb put +put() { printf "\x1B["$2";"$1"f$3" "$4"; } + +strtoline(){ for((i=0;i<${#1};i++)); do printf "$2"; done; } + +draw_line(){ + for((i=0;i<$1;i++)); do printf "-"; done +} + +# usage: fb box +draw_topline(){ + w=$((X-2)); + printf ${BOX[0]}; draw_line $w; printf "${BOX[2]}\n" +} + +createconfig(){ + dir="$1" + [[ ! -n $1 ]] && { + dir=~ + [[ -f ~/$FILE_CONF ]] && { + read -p "overwrite current config? (y/n)" overwrite; + [[ ! "$overwrite" == "y" ]] && echo "aborted" && exit 1; + } + } + [[ ! -d $dir/.kanban ]] && mkdir $dir/.kanban + echo "$config_example" > $dir/$FILE_CONF + touch $dir/$FILE_CSV +} + +init(){ createconfig $(pwd); } + +tags(){ + cat $KANBANFILE | awk -F',' '{ print $2 }' | sed 's/,.*//g;s/"//g' | tail -n+2 | sort | uniq | tr '\n' ' ' +} + +get_statuses(){ + echo ${statuses[@]} +} + +add_interactive(){ + echo "enter description:" + read -p "> " description + echo "enter one of statuses: ${statuses[@]}" + read -p "> " status + echo "enter one of tags: $(tags)" + read -p "> " tag + add "$status" "$tag" "$description" +} + +add(){ + [[ ! -n $1 ]] && { add_interactive "$@"; return 0; } + [[ ! "${statuses[*]}" =~ "$1" ]] && echo "invalid status $1 (possible: ${statuses[*]})" && exit 1 + status="$1" + csvline='"'$1'","'$2'"'; shift;shift; + csvline="$csvline,\"$*\",\"${status:0:1}\",\"$(get_current_date)\"\"" + echo "${csvline:0:$((${#csvline}-1))}" >> $KANBANFILE +} + +evaluate(){ + IFS=''; cat - | sed 's/\\/\\\\\\\\/g' | while read -r line; do + [[ "$line" =~ '$' ]] && line="$(eval "echo \"$( echo "$line" | sed 's/"/\\"/g')\"")"; + echo "$line" + done +} + +stats(){ + [[ ! -n $1 ]] && exit 1 + create_index + field=$1; shift; tags="$*" + greppattern="(${tags// /\|})" + [[ "$field" == "status" ]] && field=2 + [[ "$field" == "tag" ]] && field=3 + [[ "$field" == "history" ]] && field=5 + [[ -n $2 ]] && WIDTH=$2 || WIDTH=20; + [[ -n $3 ]] && PADDING=$3 || PADDING=20; + { + if [[ -n $PADDING ]]; then + cat $TMP.index | grep -E "$greppattern" | gawk -vFS='^"|","|"$|",|,"|,' '{h[$'"$field"']++}END{for(i in h){print h[i],i|"sort -rn|head -20"}}' |gawk '!max{max=$1;}{r="";i=s='$WIDTH'*$1/max;while(i-->0)r=r"'"${BAR[5]}"'";printf "%'$PADDING's %5d %s %s",$2,$1,r,"\n";}' + else + cat $TMP.index | grep -E "$greppattern" | gawk -vFS='^"|","|"$|",|,"|,' '{h[$'"$field"']++}END{for(i in h){print h[i],i|"sort -rn|head -20"}}' |gawk '!max{max=$1;}{r="";i=s='$WIDTH'*$1/max;while(i-->0)r=r"'"${BAR[5]}"'";printf "%s %s: %5d\n",r,$2,$1;}' | tr -s " " + fi + } | grep -v 'tag\|status\|history\|-[ ]\+1' | grep -v '^[ ]\+1' # remove header rows +} + +_init(){ + trap "[[ ! -n \$NOCOLOR ]] && tput cnorm -- normal; " 0 1 5 # reset terminal colors to normal + (( $X > $XSMALL )) && unset SMALLSCREEN + [[ -n $NOCOLOR ]] && { COL1="";COL0="";COL2=""; COL3=""; } +} + +list(){ + tags="$*" + greppattern="(${tags// /\|})" + create_index + echo -e "$COL1" + cat $TMP.index | grep -E "$greppattern" | sort -k2 -t, | HEADER="id,status,tag,description,history,start,touched\n-,-,-,-,-\n" printcsv 6 | cut -c 1-$X | colorize 3 + rm $TMP.* +} + +create_index(){ + rm $TMP.index &>/dev/null + cat -n $KANBANFILE | sed 's/^[ ]\+//g;s/\t/,/g;s/"\/.*\//"/g' | evaluate >> $TMP.index +} + +columnize(){ + awkformat='{ tag=substr($2,1,4); gsub($2,tag); printf("%-5s",$1); print " #"$2" "$3" "$4" "$5 }' + [[ -n $SMALLSCREEN ]] && awkformat='{ print $1" "$3" "$4" "$5 }' + i=1; lines="$(cat)"; header="$( echo "$lines" | head -n0 )"; output=""; + rm $TMP.col.* &>/dev/null + for status in "${statuses[@]}"; do + [[ -n $SMALLSCREEN ]] && ! [[ "${SMALLSCREEN[@]}" =~ $status ]] && continue + label=$status + nlines=$(cat $TMP.index | grep "$status" | wc -l) + maxlines=0 + [[ -v "maximum_todo[$status]" ]] && maxlines=${maximum_todo[$status]} + [[ ${maximum_todo[$status]} > 0 ]] && [[ $nlines > ${maximum_todo[$status]} ]] && label="*$status*" + echo -e ".$(strtoline "$label" "~")~~.\n| $label |_______\n|" > $TMP.col.$i + cat $TMP.index | grep "$status" | sed 's/["]\?'$status'["]\?//g' | \ + printcsv 5 | awk "$awkformat" | sed 's/^/| /g;s/ / /g' | unexpand >> $TMP.col.$i + i=$((i+1)) + done + pr -m -t -w$((X-5)) -S" " $TMP.col.* | lines + rm $TMP.col.* # print and cleanup +} + +lines(){ + echo -e "$COL1" + if [[ -n $UTF8 ]]; then + cat | sed 's/| /│ /g;s/\.~/┌/g;s/~\./──┐/g;s///g;s/~/─/g;s/|\./└/g;s/|/│/g' # nice utf9 lines + else + cat | sed 's/~/-/g;s/|+/|/g;'; + fi | colorize 3 +} + +colorize(){ + cat | awk '{ + a = gensub(/#([a-zA-Z0-9_\.-]+) /,"'$COL1'&'$COL0'","g") + a = gensub(/\*([a-zA-Z0-9_\.-]+)\*/,"'$COL3'&'$COL0'","g",a) + if( NR == '$1' ){ printf "'$COL0'"; } + print a + }' +} + +align(){ + cat | awk '{ for(i=3;i<=NF;i++){ $2=$2" "$i } printf "%-5s %s\n", $1,$2 ; }' +} + +show(){ + [[ -n $1 ]] && { cd $1; echo "[kanban] showing nested kanban '$1'"; $0 show; exit 0; } + [[ ! -f "$KANBANFILE" ]] && touch "$KANBANFILE" + create_index + if [[ -n $1 ]]; then + statuses=(); for status in $*; do statuses+=($status); done + fi + { + echo "$1" + if [[ -n $1 ]]; then cat $TMP.index | grep "$1"; else cat $TMP.index; fi + } | columnize + [[ -n $SMALLSCREEN ]] && echo -e " ${COL1}(${COL0} small terminal detected, hiding certain columns ${COL1})" + echo "" +} + +get_current_date(){ date "+%Y-%m-%d@%H:%M"; } + +update_item_status(){ + item="$( cat $KANBANFILE | awk "{ if (NR==$1) print \$0 }" )" + [[ ${#item} == 0 ]] && echo "item $1 not found" && exit 1 + if [[ -n "$2" ]]; then # status change + status="$(echo "$item" | awk -F',' '{ print $1 }' | sed 's/"//g' )" + flags="$(echo "$item" | awk -F',' '{ print $4 }' | sed 's/"//g' )" + dates="$(echo "$item" | awk -F',' '{ print $5 }' | sed 's/"//g' )" + newflags="$flags${2:0:1}" + newdates="$dates $(get_current_date)" + #[[ "$status" =~ "\$(" ]] && { update_item $1; return 0; } + [[ "$2" =~ "DONE" ]] && date="$(get_current_date)" + newitem="$item" + newitem="${newitem/$status/$2}" + newitem="${newitem/$flags/$newflags}" + newitem="${newitem/$dates/$newdates}" + sed -i "s|$item|$newitem|g" $KANBANFILE + echo "$status -> $2" + fi +} + +update_item(){ + item="$( cat $KANBANFILE | awk "{ if (NR==$1) print \$0 }" )" + [[ ${#item} == 0 ]] && echo "item $1 not found" && exit 1 + status="$(echo "$item" | awk -F',' '{ print $1 }')" + file="$(echo "$item" | awk -F',' '{ print $3 }')" + file="${file//\"/}" + [[ -d $file/.kanban ]] && { show $file; return; } + [[ -f $file ]] && ${EDITOR} "$file" + echo '# +# STATUSES ARE: '${statuses[*]}' +# +'"$item" > $TMP.update + ${EDITOR} $TMP.update + sed -i "s|$item|$(cat $TMP.update | tail -n1)|g" $KANBANFILE + echo "updated item $1" +} + +printcsv(){ + csv="$HEADER$(cat)" + [[ ! -n $1 ]] && max=999999 || max=$1 + [[ ! -n $2 ]] && min=1 || min=$1 + echo -e "$csv" | sed 's/,"",/," ",/g' | gawk -vFS='^"|","|"$|",|,"|,' \ + '{out=""; for(i='$min';i