From: Cameron Ball Date: Fri, 22 May 2015 08:12:42 +0000 (+0800) Subject: Somewhat working menu with JSON config file X-Git-Url: http://cameron1729.xyz/?a=commitdiff_plain;h=c83642f20cd27749f0b39175367cf5e38234f0df;p=PreCom.git Somewhat working menu with JSON config file --- diff --git a/JSON.sh b/JSON.sh new file mode 100755 index 0000000..1dcb521 --- /dev/null +++ b/JSON.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash + +throw () { + echo "$*" >&2 + exit 1 +} + +BRIEF=0 +LEAFONLY=0 +PRUNE=0 +NORMALIZE_SOLIDUS=0 + +usage() { + echo + echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]" + echo + echo "-p - Prune empty. Exclude fields with empty values." + echo "-l - Leaf only. Only show leaf nodes, which stops data duplication." + echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options." + echo "-s - Remove escaping of the solidus symbol (stright slash)." + echo "-h - This help text." + echo +} + +parse_options() { + set -- "$@" + local ARGN=$# + while [ "$ARGN" -ne 0 ] + do + case $1 in + -h) usage + exit 0 + ;; + -b) BRIEF=1 + LEAFONLY=1 + PRUNE=1 + ;; + -l) LEAFONLY=1 + ;; + -p) PRUNE=1 + ;; + -s) NORMALIZE_SOLIDUS=1 + ;; + ?*) echo "ERROR: Unknown option." + usage + exit 0 + ;; + esac + shift 1 + ARGN=$((ARGN-1)) + done +} + +awk_egrep () { + local pattern_string=$1 + gawk '{ + while ($0) { + start=match($0, pattern); + token=substr($0, start, RLENGTH); + print token; + $0=substr($0, start+RLENGTH); + } + }' pattern="$pattern_string" +} + +tokenize () { + local GREP + local ESCAPE + local CHAR + + if echo "test string" | egrep -ao --color=never "test" &>/dev/null + then + GREP='egrep -ao --color=never' + else + GREP='egrep -ao' + fi + + if echo "test string" | egrep -o "test" &>/dev/null + then + ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' + CHAR='[^[:cntrl:]"\\]' + else + GREP=awk_egrep + ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' + CHAR='[^[:cntrl:]"\\\\]' + fi + + local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" + local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' + local KEYWORD='null|false|true' + local SPACE='[[:space:]]+' + + $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" +} + +parse_array () { + local index=0 + local ary='' + read -r token + case "$token" in + ']') ;; + *) + while : + do + parse_value "$1" "$index" + index=$((index+1)) + ary="$ary""$value" + read -r token + case "$token" in + ']') break ;; + ',') ary="$ary," ;; + *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;; + esac + read -r token + done + ;; + esac + [ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value= + : +} + +parse_object () { + local key + local obj='' + read -r token + case "$token" in + '}') ;; + *) + while : + do + case "$token" in + '"'*'"') key="$token" ;; + *) throw "EXPECTED string GOT ${token:-EOF}" ;; + esac + read -r token + case "$token" in + ':') ;; + *) throw "EXPECTED : GOT ${token:-EOF}" ;; + esac + read -r token + parse_value "$1" "$key" + obj="$obj$key:$value" + read -r token + case "$token" in + '}') break ;; + ',') obj="$obj," ;; + *) throw "EXPECTED , or } GOT ${token:-EOF}" ;; + esac + read -r token + done + ;; + esac + [ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value= + : +} + +parse_value () { + local jpath="${1:+$1.}$2" isleaf=0 isempty=0 print=0 + case "$token" in + '{') parse_object "$jpath" ;; + '[') parse_array "$jpath" ;; + # At this point, the only valid single-character tokens are digits. + ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;; + *) value=$token + # if asked, replace solidus ("\/") in json strings with normalized value: "/" + [ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=${value//\\\//\/} + isleaf=1 + [ "$value" = '""' ] && isempty=1 + ;; + esac + [ "$value" = '' ] && return + [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1 + [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1 + [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1 + [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \ + [ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1 + [ "$print" -eq 1 ] && printf "%s\t%s\n" "${jpath//\"/}" "$value" + : +} + +parse () { + read -r token + parse_value + read -r token + case "$token" in + '') ;; + *) throw "EXPECTED EOF GOT $token" ;; + esac +} + +if ([ "$0" = "$BASH_SOURCE" ] || ! [ -n "$BASH_SOURCE" ]); +then + parse_options "$@" + tokenize | parse +fi diff --git a/menu.json b/menu.json new file mode 100644 index 0000000..1b4e23f --- /dev/null +++ b/menu.json @@ -0,0 +1,26 @@ +{ + "MainMenu": { + "type": "menu", + "description": "PreCom Options", + "items": { + "Services": { + "type": "menu", + "description": "Start/Stop services", + "items": { + "ITG": { + "type": "service", + "description": "Start/Stop the ITG service", + "command": "/home/cameron/OpenITG/itg", + "user": "itg" + }, + "gEdit": { + "type": "service", + "description": "Start/Stop the gEdit service", + "command": "gedit", + "user": "itg" + } + } + } + } + } +} diff --git a/menu.sh b/menu.sh new file mode 100755 index 0000000..62850fb --- /dev/null +++ b/menu.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +#Pass something like MainMenu.type and it'll +#give the value +#this should be the full path to the item +#this function is not intended for use in +#the main logic of the menu +function get_key() +{ + value_re="[[:space:]]+\"(.+)\"$" + while read -r line; do + [[ $line == "$1"* ]] && [[ $line =~ $value_re ]] && echo ${BASH_REMATCH[1]} && break + done <<< "$menu_json" +} + +#simply puts item between each element +#in the path. So Menu.Services.ITG +#become Menu.items.Services.items.ITG +function short_path_to_full_path() +{ + echo "${1//./.items.}" +} + +#Functions below here are for convenience. +#They allow extraction of information about +#menus without having to constantly specifiy +#Menu.items.Thing.items.MenuIWant +#Instead you can just do Menu.Thing.MenuIWant + +#Use a dot delim path to the item +#returns the type of the item +#e.g., +# menu +# service +function get_item_type() +{ + full_path=$(short_path_to_full_path $1) + get_key "$full_path.type" +} + +function get_item_description() +{ + full_path=$(short_path_to_full_path $1) + get_key "$full_path.description" +} + +#Returns true if line is a child +#of the dot-delim menu passed in +#(line, dot-delim) +#note: child means _direct_ child +#this will exclude grandchildren etc +function is_child_of() +{ + full_path=$(short_path_to_full_path $2) + items_in_path=$(grep -o "items" <<< "$full_path" | wc -l) + items_in_line=$(grep -o "items" <<< "$1" | wc -l) + + if [[ "$1" == "$full_path"* ]] && [[ $((items_in_path + 1)) == "$items_in_line" ]]; then + return 0 + else + return 1 + fi + +} + +#Use a dot delimited path to the menu +#e.g., +# MainMenu.Services +# MainMenu.Services.Streaming +function render_menu() +{ + options=() + while read -r line; do + if is_child_of "$line" "$1"; then + name_re="items.([a-zA-Z]+).description" + desc_re=".description[[:space:]]\"(.+)\"" + [[ $line =~ $name_re ]] && name=${BASH_REMATCH[1]} && [[ $line =~ $desc_re ]] && desc=${BASH_REMATCH[1]} && options+=("$name" "$desc") + fi + done <<< "$menu_json" + + if [[ $1 == "MainMenu" ]]; then + options+=("Quit" "Exit the menu masterqueef") + else + options+=("Back" "Go back") + fi + + dialog --clear --backtitle "DivinElegy PreCom" --title "${1//./>}" --menu "$(get_item_description $1)" 15 50 4 "${options[@]}" 2>"${INPUT}" +} + +function render_item() +{ + type=$(get_item_type $1) + case $type in + menu) render_menu $1;; + esac +} + +#################### + +INPUT=/tmp/menu.sh.$$ +OUTPUT=/tmp/output.sh.$$ + +trap "rm $OUTPUT' rm $INPUT; exit" SIGHUP SIGINT SIGTERM + +menu_json="$(./JSON.sh -l < menu.json)" +current_item="MainMenu" + +while true; do + render_item $current_item + selection=$(<"${INPUT}") + + case $selection in + Quit) break;; + Back) current_item="${current_item%.*}";; + *) current_item="$current_item.$selection";; + esac +done + +#clear + +[ -f $OUTPUT ] && rm $OUTPUT +[ -f $INPUT ] && rm $INPUT diff --git a/simlink.sh b/simlink.sh old mode 100755 new mode 100644