diff --git a/debian/octoscreen.postinst b/debian/octoscreen.postinst index 6e130764..97ca6095 100755 --- a/debian/octoscreen.postinst +++ b/debian/octoscreen.postinst @@ -45,6 +45,10 @@ if [ "$1" = configure ] && [ -d /etc/systemd/system/ ]; then fi fi +# Install the octoscreen CLI +if [ "$1" = configure ] && [ -d /usr/local/bin/ ]; then + ln -sf /etc/octoscreen/scripts/octoscreen /usr/local/bin/octoscreen +fi db_stop || true #DEBHELPER# diff --git a/debian/rules b/debian/rules index 55088c60..230649fb 100755 --- a/debian/rules +++ b/debian/rules @@ -25,6 +25,8 @@ override_dh_golang: override_dh_install: mkdir -p $(CURDIR)/debian/octoscreen/opt/octoscreen/ cp -r styles $(CURDIR)/debian/octoscreen/opt/octoscreen/ + mkdir -p $(CURDIR)/debian/local/octoscreen/scripts/ + cp -r scripts $(CURDIR)/debian/local/octoscreen/ rm -rf $(CURDIR)/debian/octoscreen/usr/share/gocode dh_install -XLICENSE diff --git a/scripts/lib/inquirer.bash b/scripts/lib/inquirer.bash new file mode 100644 index 00000000..ca9380f9 --- /dev/null +++ b/scripts/lib/inquirer.bash @@ -0,0 +1,667 @@ +#!/usr/bin/env bash + +################## INQUIRER.SH ################## +# https://github.com/kahkhang/Inquirer.sh + +# The MIT License (MIT) + +# Copyright (c) 2017 + +# 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. + +# store the current set options +OLD_SET=$- +set -e + +arrow="$(echo -e '\xe2\x9d\xaf')" +checked="$(echo -e '\xe2\x97\x89')" +unchecked="$(echo -e '\xe2\x97\xaf')" + +black="$(tput setaf 0)" +red="$(tput setaf 1)" +green="$(tput setaf 2)" +yellow="$(tput setaf 3)" +blue="$(tput setaf 4)" +magenta="$(tput setaf 5)" +cyan="$(tput setaf 6)" +white="$(tput setaf 7)" +bold="$(tput bold)" +normal="$(tput sgr0)" +dim=$'\e[2m' + +print() { + echo "$1" + tput el +} + +join() { + local IFS=$'\n' + local _join_list + eval _join_list=( '"${'${1}'[@]}"' ) + local first=true + for item in ${_join_list[@]}; do + if [ "$first" = true ]; then + printf "%s" "$item" + first=false + else + printf "${2-, }%s" "$item" + fi + done +} + +function gen_env_from_options() { + local IFS=$'\n' + local _indices + local _env_names + local _checkbox_selected + eval _indices=( '"${'${1}'[@]}"' ) + eval _env_names=( '"${'${2}'[@]}"' ) + + for i in $(gen_index ${#_env_names[@]}); do + _checkbox_selected[$i]=false + done + + for i in ${_indices[@]}; do + _checkbox_selected[$i]=true + done + + for i in $(gen_index ${#_env_names[@]}); do + printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}" + done +} + +on_default() { + true; +} + +on_keypress() { + local OLD_IFS + local IFS + local key + OLD_IFS=$IFS + local on_up=${1:-on_default} + local on_down=${2:-on_default} + local on_space=${3:-on_default} + local on_enter=${4:-on_default} + local on_left=${5:-on_default} + local on_right=${6:-on_default} + local on_ascii=${7:-on_default} + local on_backspace=${8:-on_default} + _break_keypress=false + while IFS="" read -rsn1 key; do + case "$key" in + $'\x1b') + read -rsn1 key + if [[ "$key" == "[" ]]; then + read -rsn1 key + case "$key" in + 'A') eval $on_up;; + 'B') eval $on_down;; + 'D') eval $on_left;; + 'C') eval $on_right;; + esac + fi + ;; + ' ') eval $on_space ' ';; + [a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key;; + $'\x7f') eval $on_backspace $key;; + '') eval $on_enter $key;; + esac + if [ $_break_keypress = true ]; then + break + fi + done + IFS=$OLD_IFS +} + +gen_index() { + local k=$1 + local l=0 + if [ $k -gt 0 ]; then + for l in $(seq $k) + do + expr $l - 1 + done + fi +} + +cleanup() { + # Reset character attributes, make cursor visible, and restore + # previous screen contents (if possible). + tput sgr0 + tput cnorm + stty echo + + # Restore `set e` option to its orignal value + if [[ $OLD_SET =~ e ]] + then set -e + else set +e + fi +} + +control_c() { + cleanup + exit $? +} + +select_indices() { + local _select_list + local _select_indices + local _select_selected=() + eval _select_list=( '"${'${1}'[@]}"' ) + eval _select_indices=( '"${'${2}'[@]}"' ) + local _select_var_name=$3 + eval $_select_var_name\=\(\) + for i in $(gen_index ${#_select_indices[@]}); do + eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\) + done +} + + + + +on_checkbox_input_up() { + remove_checkbox_instructions + tput cub "$(tput cols)" + + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" + else + printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" + fi + tput el + + if [ $_current_index = 0 ]; then + _current_index=$((${#_checkbox_list[@]}-1)) + tput cud $((${#_checkbox_list[@]}-1)) + tput cub "$(tput cols)" + else + _current_index=$((_current_index-1)) + + tput cuu1 + tput cub "$(tput cols)" + tput el + fi + + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" + else + printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" + fi +} + +on_checkbox_input_down() { + remove_checkbox_instructions + tput cub "$(tput cols)" + + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" + else + printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}" + fi + + tput el + + if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then + _current_index=0 + tput cuu $((${#_checkbox_list[@]}-1)) + tput cub "$(tput cols)" + else + _current_index=$((_current_index+1)) + tput cud1 + tput cub "$(tput cols)" + tput el + fi + + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" + else + printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" + fi +} + +on_checkbox_input_enter() { + local OLD_IFS + OLD_IFS=$IFS + _checkbox_selected_indices=() + _checkbox_selected_options=() + IFS=$'\n' + + for i in $(gen_index ${#_checkbox_list[@]}); do + if [ "${_checkbox_selected[$i]}" = true ]; then + _checkbox_selected_indices+=($i) + _checkbox_selected_options+=("${_checkbox_list[$i]}") + fi + done + + tput cud $((${#_checkbox_list[@]}-${_current_index})) + tput cub "$(tput cols)" + + for i in $(seq $((${#_checkbox_list[@]}+1))); do + tput el1 + tput el + tput cuu1 + done + tput cub "$(tput cols)" + + tput cuf $((${#prompt}+3)) + printf "${cyan}$(join _checkbox_selected_options)${normal}" + tput el + + tput cud1 + tput cub "$(tput cols)" + tput el + + _break_keypress=true + IFS=$OLD_IFS +} + +on_checkbox_input_space() { + remove_checkbox_instructions + tput cub "$(tput cols)" + tput el + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + _checkbox_selected[$_current_index]=false + else + _checkbox_selected[$_current_index]=true + fi + + if [ "${_checkbox_selected[$_current_index]}" = true ]; then + printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}" + else + printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}" + fi +} + +remove_checkbox_instructions() { + if [ $_first_keystroke = true ]; then + tput cuu $((${_current_index}+1)) + tput cub "$(tput cols)" + tput cuf $((${#prompt}+3)) + tput el + tput cud $((${_current_index}+1)) + _first_keystroke=false + fi +} + +# for vim movements +on_checkbox_input_ascii() { + local key=$1 + case $key in + "j" ) on_checkbox_input_down;; + "k" ) on_checkbox_input_up;; + esac +} + +_checkbox_input() { + local i + local j + prompt=$1 + eval _checkbox_list=( '"${'${2}'[@]}"' ) + _current_index=0 + _first_keystroke=true + + trap control_c SIGINT EXIT + + stty -echo + tput civis + + print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Press to select, to finalize)${normal}" + + for i in $(gen_index ${#_checkbox_list[@]}); do + _checkbox_selected[$i]=false + done + + if [ -n "$3" ]; then + eval _selected_indices=( '"${'${3}'[@]}"' ) + for i in ${_selected_indices[@]}; do + _checkbox_selected[$i]=true + done + fi + + for i in $(gen_index ${#_checkbox_list[@]}); do + tput cub "$(tput cols)" + if [ $i = 0 ]; then + if [ "${_checkbox_selected[$i]}" = true ]; then + print "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" + else + print "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$i]} ${normal}" + fi + else + if [ "${_checkbox_selected[$i]}" = true ]; then + print " ${green}${checked}${normal} ${_checkbox_list[$i]} ${normal}" + else + print " ${unchecked} ${_checkbox_list[$i]} ${normal}" + fi + fi + tput el + done + + for j in $(gen_index ${#_checkbox_list[@]}); do + tput cuu1 + done + + on_keypress on_checkbox_input_up on_checkbox_input_down on_checkbox_input_space on_checkbox_input_enter on_default on_default on_checkbox_input_ascii +} + +checkbox_input() { + _checkbox_input "$1" "$2" + _checkbox_input_output_var_name=$3 + select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name + + unset _checkbox_list + unset _break_keypress + unset _first_keystroke + unset _current_index + unset _checkbox_input_output_var_name + unset _checkbox_selected_indices + unset _checkbox_selected_options + + cleanup +} + +checkbox_input_indices() { + _checkbox_input "$1" "$2" "$3" + _checkbox_input_output_var_name=$3 + + eval $_checkbox_input_output_var_name\=\(\) + for i in $(gen_index ${#_checkbox_selected_indices[@]}); do + eval $_checkbox_input_output_var_name\+\=\(${_checkbox_selected_indices[$i]}\) + done + + unset _checkbox_list + unset _break_keypress + unset _first_keystroke + unset _current_index + unset _checkbox_input_output_var_name + unset _checkbox_selected_indices + unset _checkbox_selected_options + + cleanup +} + + + + +on_list_input_up() { + remove_list_instructions + tput cub "$(tput cols)" + + printf " ${_list_options[$_list_selected_index]}" + tput el + + if [ $_list_selected_index = 0 ]; then + _list_selected_index=$((${#_list_options[@]}-1)) + tput cud $((${#_list_options[@]}-1)) + tput cub "$(tput cols)" + else + _list_selected_index=$((_list_selected_index-1)) + + tput cuu1 + tput cub "$(tput cols)" + tput el + fi + + printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" +} + +on_list_input_down() { + remove_list_instructions + tput cub "$(tput cols)" + + printf " ${_list_options[$_list_selected_index]}" + tput el + + if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then + _list_selected_index=0 + tput cuu $((${#_list_options[@]}-1)) + tput cub "$(tput cols)" + else + _list_selected_index=$((_list_selected_index+1)) + tput cud1 + tput cub "$(tput cols)" + tput el + fi + printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" +} + +on_list_input_enter_space() { + local OLD_IFS + OLD_IFS=$IFS + IFS=$'\n' + + tput cud $((${#_list_options[@]}-${_list_selected_index})) + tput cub "$(tput cols)" + + for i in $(seq $((${#_list_options[@]}+1))); do + tput el1 + tput el + tput cuu1 + done + tput cub "$(tput cols)" + + tput cuf $((${#prompt}+3)) + printf "${cyan}${_list_options[$_list_selected_index]}${normal}" + tput el + + tput cud1 + tput cub "$(tput cols)" + tput el + + _break_keypress=true + IFS=$OLD_IFS +} + +remove_list_instructions() { + if [ $_first_keystroke = true ]; then + tput cuu $((${_list_selected_index}+1)) + tput cub "$(tput cols)" + tput cuf $((${#prompt}+3)) + tput el + tput cud $((${_list_selected_index}+1)) + _first_keystroke=false + fi +} + +_list_input() { + local i + local j + prompt=$1 + eval _list_options=( '"${'${2}'[@]}"' ) + + _list_selected_index=0 + _first_keystroke=true + + trap control_c SIGINT EXIT + + stty -echo + tput civis + + print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}" + + for i in $(gen_index ${#_list_options[@]}); do + tput cub "$(tput cols)" + if [ $i = 0 ]; then + print "${cyan}${arrow} ${_list_options[$i]} ${normal}" + else + print " ${_list_options[$i]}" + fi + tput el + done + + for j in $(gen_index ${#_list_options[@]}); do + tput cuu1 + done + + on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space + +} + + +list_input() { + _list_input "$1" "$2" + local var_name=$3 + eval $var_name=\'"${_list_options[$_list_selected_index]}"\' + unset _list_selected_index + unset _list_options + unset _break_keypress + unset _first_keystroke + + cleanup +} + +list_input_index() { + _list_input "$1" "$2" + local var_name=$3 + eval $var_name=\'"$_list_selected_index"\' + unset _list_selected_index + unset _list_options + unset _break_keypress + unset _first_keystroke + + cleanup +} + + + + +on_text_input_left() { + remove_regex_failed + if [ $_current_pos -gt 0 ]; then + tput cub1 + _current_pos=$(($_current_pos-1)) + fi +} + +on_text_input_right() { + remove_regex_failed + if [ $_current_pos -lt ${#_text_input} ]; then + tput cuf1 + _current_pos=$(($_current_pos+1)) + fi +} + +on_text_input_enter() { + remove_regex_failed + + if [[ "$_text_input" =~ $_text_input_regex && "$(eval $_text_input_validator "$_text_input")" = true ]]; then + tput cub "$(tput cols)" + tput cuf $((${#_read_prompt}-19)) + printf "${cyan}${_text_input}${normal}" + tput el + tput cud1 + tput cub "$(tput cols)" + tput el + eval $var_name=\'"${_text_input}"\' + _break_keypress=true + else + _text_input_regex_failed=true + tput civis + tput cud1 + tput cub "$(tput cols)" + tput el + printf "${red}>>${normal} $_text_input_regex_failed_msg" + tput cuu1 + tput cub "$(tput cols)" + tput cuf $((${#_read_prompt}-19)) + tput el + _text_input="" + _current_pos=0 + tput cnorm + fi +} + +on_text_input_ascii() { + remove_regex_failed + local c=$1 + + if [ "$c" = '' ]; then + c=' ' + fi + + local rest="${_text_input:$_current_pos}" + _text_input="${_text_input:0:$_current_pos}$c$rest" + _current_pos=$(($_current_pos+1)) + + tput civis + printf "$c$rest" + tput el + if [ ${#rest} -gt 0 ]; then + tput cub ${#rest} + fi + tput cnorm +} + +on_text_input_backspace() { + remove_regex_failed + if [ $_current_pos -gt 0 ]; then + local start="${_text_input:0:$(($_current_pos-1))}" + local rest="${_text_input:$_current_pos}" + _current_pos=$(($_current_pos-1)) + tput cub 1 + tput el + tput sc + printf "$rest" + tput rc + _text_input="$start$rest" + fi +} + +remove_regex_failed() { + if [ $_text_input_regex_failed = true ]; then + _text_input_regex_failed=false + tput sc + tput cud1 + tput el1 + tput el + tput rc + fi +} + +text_input_default_validator() { + echo true; +} + +text_input() { + local prompt=$1 + local var_name=$2 + local _text_input_regex="${3:-"\.+"}" + local _text_input_regex_failed_msg=${4:-"Input validation failed"} + local _text_input_validator=${5:-text_input_default_validator} + local _read_prompt_start=$'\e[32m?\e[39m\e[1m' + local _read_prompt_end=$'\e[22m' + local _read_prompt="$( echo "$_read_prompt_start ${prompt} $_read_prompt_end")" + local _current_pos=0 + local _text_input_regex_failed=false + local _text_input="" + printf "$_read_prompt" + + + trap control_c SIGINT EXIT + + stty -echo + tput cnorm + + on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace + eval $var_name=\'"${_text_input}"\' + + cleanup +} + +################## INQUIRER.SH ################## diff --git a/scripts/lib/optparse.bash b/scripts/lib/optparse.bash new file mode 100644 index 00000000..b1580b59 --- /dev/null +++ b/scripts/lib/optparse.bash @@ -0,0 +1,723 @@ +#!/usr/bin/env bash +declare -g optparse_license="$(cat < +# +# Contributors: +# Alessio Biancone +# Mike Mulligan +EOL +)" + +function optparse.init(){ + + declare -g optparse_version="0.0.2" + + declare -g optparse_defaults="" + declare -g optparse_process="" + declare -g optparse_variables_validate="" + declare -g optparse_shortnames=() + declare -g optparse_longnames=() + declare -g optparse_variables=() + declare -g optparse_usage_commands="" + + for option in "$@"; do + local key="${option%%=*}"; + local value="${option#*=}"; + + case "$key" in + "default_group"|"usage_header"|"description") + declare -g optparse_$key="$value" + ;; + "name") + [[ "${value::1}" == "-" ]] && + declare -g optparse_name="" || + declare -g optparse_name="$(basename $value)" + ;; + "help_full_width") + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + optparse.throw_error "optparse.init '$key' must be of type INTIGER" + fi + declare -g optparse_$key="$value" + ;; + esac + done + + [[ -z "$optparse_name" ]] && { + [[ "${0::1}" == "-" ]] && + declare -g optparse_name="" || + declare -g optparse_name="$(basename $0)" + } + [[ -z "$optparse_help_full_width" ]] && declare -g optparse_help_full_width=80 + [[ -z "$optparse_default_group" ]] && declare -g optparse_default_group="OPTIONS" + [[ -z "$optparse_usage_header" ]] && declare -g optparse_usage_header="[OPTIONS]" + [[ -z "$optparse_description" ]] && declare -g optparse_description="${optparse_name} Help" + + optparse.define long=help desc="This Help Screen" dispatch="optparse.usage" behaviour=flag help=explicit + optparse.define long=optparse_license desc="The OptParse Library License" dispatch="optparse.license" help=hide + +} + +function optparse.reset(){ + optparse.unset + optparse.init $(printf ' %q' "${@}") +} + +function optparse.unset(){ + unset optparse_version + unset optparse_usage + unset optparse_process + unset optparse_defaults + unset optparse_name + unset optparse_usage_header + unset optparse_variables_validate + unset optparse_default_group +} + +# ----------------------------------------------------------------------------------------------------------------------------- +function optparse.throw_error(){ + local message="$1" + [[ ! -z $2 ]] && message+=": ($2)" + optparse._log "ERROR" "$message" + exit 1 +} + +function optparse._log(){ + local type="$1" + local message="$2" + >&2 echo "OPTPARSE $type: $message" +} + +function optparse.warn(){ + local message="$1" + [[ ! -z "$2" ]] && message+=": ($2)" + optparse._log "WARN" "$message" +} + +function optparse.group(){ + local group="$1" + [[ ! -z "$group" && ! -z "$optparse_usage" ]] && group="#NL$group" + optparse_usage+="cat >&2 << EOU#NL$group#NLEOU#NL" +} + +# ----------------------------------------------------------------------------------------------------------------------------- +function optparse.define.single(){ + local name="" + local desc="" + local default="" + local behaviour="default" + local list=false + local variable="" + local dispatch="" + local val="" + local has_val="false" + local has_default="false" + local behaviour_help="default" + local behaviour_extra="" + local optparse_help_indent=15 + local debug="false" + + for option in "$@"; do + local key="${option%%=*}"; + local value="${option#*=}"; + + [[ $value == $key ]] && value='' + + case "$key" in + desc|description) + desc="$value" + ;; + name) + name="$value" + ;; + value) + val="$value" + ;; + dispatch) + dispatch="$value" + ;; + help) + case $value in + default|explicit|hide) + declare behaviour_$key="$value" + ;; + *) + optparse.throw_error "$key [$value] not supported" $name + ;; + esac + ;; + esac + done + + [[ -z "$val" ]] && + local has_val=false || + local has_val=true; + + [[ -z "$name" ]] && + optparse.throw_error "name is mandatory"; + + [[ -z "$desc" && "$behaviour_help" != "hide" ]] && + optparse.throw_error "description is mandatory" "$name"; + + [[ -z "$dispatch" ]] && + optparse.throw_error "a dispatcher is mandatory for commands" "$name"; + + #[ -z "$optparse_usage" ] && optparse.group "$optparse_default_group" + + + [[ $behaviour_help != hide ]] && { + + [[ -z "$optparse_usage_commands" ]] && + optparse_usage_commands="cat >&2 << EOU#NLCommands#NLEOU#NL" + + local _optparse_usage="" + + # build OPTIONS and help + + local description=() + local description_index=0 + local description_word="" + local description_sep="" + local description_char_index=0 + + # Break to lines + + #for (( description_char_index=0; description_char_index<${#desc}; description_char_index++ )); do + # #echo "${foo:$i:1}" + # : + #done + + #_optparse_usage+="cat >&2 << EOU" + _optparse_usage+="#TB$(printf "%-${optparse_help_indent}s %s" " ${name}" "${desc}")" + # -h, --help T + + _optparse_usage="cat >&2 << EOU#NL$_optparse_usage#NLEOU#NL" + + [ "$behaviour_help" == "explicit" ] && + _optparse_usage="if [[ \$1 == "true" ]]; then#NL$_optparse_usage#NLfi#NL" + + optparse_usage_commands+="$_optparse_usage" + + #echo "$_optparse_usage" + } + + optparse_additional_handlers+=" + ${name}) + ${dispatch} ; true; + ;;" + + true +} + +function optparse.define(){ + local errorname="" + local short="" + local shortname="" + local long="" + local longname="" + local desc="" + local default="" + local behaviour="default" + local list=false + local variable="" + local dispatch="" + local val="" + local has_val="false" + local has_default="false" + local behaviour_help="default" + local behaviour_extra="" + local optparse_help_indent=15 + local optional="false" + local has_variable="false" + + for option in "$@"; do + local key="${option%%=*}"; + local value="${option#*=}"; + + case "$key" in + short) + [[ ${#value} -ne 1 ]] && + optparse.throw_error "short name expected to be one character long" $value; + + for i in "${optparse_shortnames[@]}"; do + [[ $i == $value ]] && + optparse.warn "shortname [-$value] already handled" "-$value" && break; + done; + + local optparse_shortnames+=("$value") + local shortname="$value" + local short="-$value" + [[ -z "$errorname" ]] && + local errorname="$short"; + ;; + long) + [[ -z ${value} ]] && + optparse.throw_error "long name expected to be atleast one character long" "--$value"; + + for i in "${optparse_longnames[@]}"; do + [[ $i == $value ]] && + optparse.warn "longname [--$value] already handled" "--$value" && break; + done; + + optparse_longnames+=("$value") + longname="$value" + long="--$value" + errorname="$long" + ;; + desc|description) + desc="$value" + ;; + default) + default="$value" + has_default=true + ;; + optional) + optional=true + ;; + flag|list) + behaviour=$key + ;; + behaviour) + case $value in + default|list|flag) + behaviour=$value + ;; + *) + optparse.throw_error "behaviour [$value] not supported" $errorname + ;; + esac; + + ;; + list) + list="$value" + ;; + variable) + local variable=$value + + for i in "${optparse_variables[@]}"; do + [[ $i == $value ]] && + optparse.warn "value assignment [\$$value] already handled" $errorname && break; + done; + + optparse_variables+=("$value") + ;; + value) + val=$value + ;; + dispatch) + dispatch=$value + ;; + extra|help|hide|explicit) + case $value in + default|explicit|hide) + [[ $value == $key ]] && { + local behaviour_help=$value; + local behaviour_extra=$value; + } || + local behaviour_$key=$value; + ;; + *) + optparse.throw_error "$key [$value] not supported" "$errorname" + ;; + esac + ;; + esac + done + + [[ -z $behaviour_extra ]] && { + [[ $behaviour == flag ]] && + local behaviour_extra=hide || + local behaviour_extra=explicit; + } + + [[ -z "$errorname" ]] && optparse.throw_error "argument must have a long or short name" + + [[ $behaviour == flag ]] && + local flag=true || + local flag=false; + + [[ $behaviour == list ]] && + local is_list=true || + local is_list=false; + + [[ $behaviour == flag ]] && { + [[ -z $default ]] && default=false + has_default=true + [[ $default == true ]] && val=false + [[ $default == false ]] && val=true + } + + [[ -z "$val" ]] && + local has_val="false" || + local has_val="true"; + + + [[ -z "$variable" ]] && + local has_variable=false || + local has_variable=true; + + # check list behaviour + [[ $behaviour == list ]] && { + [[ -z ${list:-} ]] && + optparse.throw_error "list is mandatory when using list behaviour" $errorname + + $has_default && { + local valid=false; + for i in $list; do + [[ $default == $i ]] && + local valid=true && + break; + done; + + $valid || optparse.throw_error "default should be in list" $errorname + } + } + + [[ -z "$desc" && $behaviour_help != hide ]] && { + [[ -z "$dispatch" ]] && { + optparse.throw_error "description is mandatory" $errorname + } || { + [[ $behaviour_help == default ]] && help_behaviour=explicit + [[ $behaviour_help != hide ]] && desc="Executes $dispatch" + } + } + + [[ -z "$variable" && -z "$dispatch" ]] && optparse.throw_error "you must give a target variable" "$errorname"; + + + [[ $behaviour_help != hide ]] && { + + [[ -z "$optparse_usage" ]] && optparse.group "$optparse_default_group"; + + local _optparse_usage="" + + # build OPTIONS and help + + local description=() + local description_index=0 + local description_word="" + local description_sep="" + local description_char_index=0 + + # Break to lines + + #for (( description_char_index=0; description_char_index<${#desc}; description_char_index++ )); do + # #echo "${foo:$i:1}" + # : + #done + + #_optparse_usage+="cat >&2 << EOU" + _optparse_usage+="#TB$(printf "%-${optparse_help_indent}s %s" "${short:= }$([ ! -z $short ] && [ ! -z $long ] && echo "," || echo " ") ${long}" "${desc}")" + # -h, --help T + + _optparse_usage="cat >&2 << EOU#NL$_optparse_usage#NLEOU#NL" + + [[ $behaviour_extra != hide ]] && { + + local _optparse_extra="" + + $is_list && _optparse_extra+="#TB#TB#TBOne of: '$list'#NL" + + $flag && { + _optparse_extra+="#TB#TB#TBTreated as a Flag#NL" + } || { + $has_default && + _optparse_extra+="#TB#TB#TBDefault: '$default'#NL" + } + + [[ ! -z "$_optparse_extra" ]] && { + + _optparse_extra="cat >&2 << EOE#NL${_optparse_extra}EOE#NL" + + [[ $behaviour_extra == explicit ]] && + _optparse_extra="if [[ \$1 == "true" ]]; then#NL$_optparse_extra#NLfi#NL" + + _optparse_usage+=$_optparse_extra + } + } + + [[ $behaviour_help == explicit ]] && + _optparse_usage="if [[ \$1 == true ]]; then#NL$_optparse_usage#NLfi#NL" + + optparse_usage+=$_optparse_usage + + #echo "$_optparse_usage" + } + + $has_default && $has_variable && + optparse_defaults+="#NL[[ -z \$${variable} ]] && ${variable}='${default}'" + + $has_default && + local _def=DEF || + local _def=''; + + local validate_variable="valid=true + $is_list && \$valid && { + valid=false + for i in $list; do + [[ \$__arg_value == \$i ]] && valid=true && break + done + } + \$valid || optparse.usage false \"ERROR: invalid value for argument \\\"\$__arg_key\\\"\" 1; + $has_variable && [[ -z \${${variable}:-$_def} ]] && optparse.usage false \"ERROR: (\$__arg_key) requires input value\" 1 || true;" + + local dispatch_caller="# No Dispatcher" + + [[ ! -z "$dispatch" ]] && { + [[ -z "$dispatch_var" ]] && { + $has_variable && + dispatch_var="\"\$${variable}\"" || + dispatch_var="\"\$__arg_value\"" + } + dispatch_caller="${dispatch} ${dispatch_var}; true;" + } + + [[ ! -z "$shortname" ]] && { + optparse_process_short+=" + ${shortname}) + __arg_value=''; + ( $flag || $has_val ) && { + __arg_value=\"$val\"; + } + $has_variable && [[ -z \"\$__arg_value\" ]] && { + __arg_value=\"\$__arg_sremain\"; + __arg_sremain=''; + [[ -z \"\$__arg_value\" && ! -z \"\$1\" ]] && __arg_value=\"\$1\" && shift; + } + $has_variable && ${variable}=\"\$__arg_value\"; + $validate_variable + $dispatch_caller + continue + ;;" + } + + [[ ! -z "$longname" ]] && { + optparse_process_long+=" + ${longname}) + $has_val && { + [[ ! -z \"\$__arg_value\" ]] && optparse.usage true 'ERROR: (${errorname}) does not accept user input' 1; + __arg_value=\"$val\"; + } + $has_variable && ${variable}=\"\$__arg_value\"; + $validate_variable + $dispatch_caller + continue + ;;" + } + + $has_variable && optparse_variables_validate+=" + $optional || { [[ -z \${${variable}:-$_def} ]] && optparse.usage true 'ERROR: (${errorname}) not set' 1 || true; };" + + true +} + +# ----------------------------------------------------------------------------------------------------------------------------- +function optparse.build(){ + + local preserve_positional=false + local allow_unrecognized=false + local optparse_debug=false + local unknown_long_handler='' + local unknown_short_handler='' + local non_empty_shorts='' + + local _optparse_process=$optparse_process + local _optparse_variables_validate=$optparse_variables_validate + + local _exec='' + + for option in "$@"; do + local key="${option%%=*}"; + local value="${option#*=}"; + + case "$key" in + allow) + value=( $value ) + for allowed in "${value[@]}"; do + case "$allowed" in + positional) + local preserve_positional=true + ;; + unrecognized) + local allow_unrecognized=true + ;; + esac + done + ;; + finally) + _exec="#NL$value;" + ;; + esac + done + + [[ -z "$optparse_additional_handlers" ]] && + local _has_additional=false || + local _has_additional=true; + + $_has_additional && + local preserve_positional=true + + $preserve_positional && { + preserve_positional=" + [ ! -z \"\$__arg\" ] && __args_processed+=( \"\$__arg\" )" + } || { + preserve_positional="optparse.usage true \"ERROR: Unconfigured Arguments are not accepted.\" 1" + } + + $allow_unrecognized && { + unknown_long_handler=" + __args_processed+=( \"\$__arg\" ) + continue" + unknown_short_handler=" + __arg_processed+=\"\$__arg_key\" + continue" + non_empty_shorts=" + [ ! -z \"\$__arg_short_processed\" ] && { + __args_processed+=(\"-\$__arg_processed\") + continue + }" + } || { + unknown_long_handler="optparse.usage true \"Unrecognized option: \$__arg_key\" 1" + unknown_short_handler="$unknown_long_handler" + } + + # Function usage + cat <&2 << EOH +${optparse_description} +Usage: $optparse_name $optparse_usage_header + +EOH +$optparse_usage_commands +$optparse_usage +cat >&2 << EOH + +\$(printf "%${optparse_help_full_width}s %s" "Powered by optparse $optparse_version") +\$(printf "%${optparse_help_full_width}s %s" "@see --optparse_license") +EOH + + +[[ "\$1" == "true" && ! -z "\$3" ]] && exit "\$3" || exit 3 +} + + + +# Set default variable values +$optparse_defaults + +__args_processed=() + +optparse_command_hit="false" + +# Begin Parseing +while [[ \$# -ne 0 ]]; do + __arg_key='' + __arg_value='' + __arg_short='' + __arg="\$1" + shift + + case "\$__arg" in + --) + __args_processed+=("--") && break + ;; + + --*) + + __arg_key="\${__arg%%=*}"; + __arg_value="\${__arg#*=}"; + + [[ "\$__arg_value" == "\$__arg_key" ]] && __arg_value='' + + case "\${__arg_key:2}" in + $optparse_process_long + esac + + $unknown_long_handler + + ;; + + -*) + + __arg_sremain="\${__arg:1}" + __arg_processed='' + + while [[ \${#__arg_sremain} -ne 0 ]]; do + + __arg_key="\${__arg_sremain:0:1}"; + __arg_sremain="\${__arg_sremain:1}" + __arg_value=''; + + case "\$__arg_key" in + $optparse_process_short + esac + + $unknown_short_handler + + done + + $non_empty_shorts + + ;; + + $optparse_additional_handlers + + esac + + $preserve_positional + +done + +# GODLY arg quote with printf, WHERE HAVE YOU BEEN MY WHOLE LIFE?? +[[ ! -z "\$__args_processed" ]] && { + __args_processed="\$(printf '%q ' "\${__args_processed[@]}")" + [[ ! -z "\$@" ]] && __args_processed+="\$(printf '%q ' "\${@}")" + eval set -- "\$__args_processed" +} + +$optparse_variables_validate + +unset _optparse_params +unset _optparse_param + +$_exec + +EOF + + # Unset global variables + [[ -z "$optparse_preserve" ]] && optparse.unset +} + +[[ -z $OPTPARSE_DELAY_INIT ]] || optparse.init diff --git a/scripts/lib/verbosity.bash b/scripts/lib/verbosity.bash new file mode 100644 index 00000000..31c6a22c --- /dev/null +++ b/scripts/lib/verbosity.bash @@ -0,0 +1,112 @@ +#!/user/bib/env bash + +VERBOSITY_CURRENT=${VERBOSITY_CURRENT:-0} +VERBOSITY_FATAL=-3 +VERBOSITY_ERROR=-2 +VERBOSITY_QUIET=-1 +VERBOSITY_ECHO=0 +VERBOSITY_WARN=0 +VERBOSITY_NOTICE=1 +VERBOSITY_INFO=2 +VERBOSITY_DEBUG=3 +VERBOSITY_HELL=6 + +VERBOSITY_ERROR_FORMAT='\e[100\e[91m%s%s\e[0m: %s' +VERBOSITY_FATAL_FORMAT='\e[41m%s%s\e[0m: %s' +VERBOSITY_QUIET_FORMAT='\e[94m%s%s\e[0m: %s' +VERBOSITY_WARN_FORMAT='\e[91m%s%s\e[0m: %s' +VERBOSITY_NOTICE_FORMAT='\e[93m%s%s\e[0m: %s' +VERBOSITY_INFO_FORMAT='\e[96m%s%s\e[0m: %s' +VERBOSITY_DEBUG_FORMAT='\e[94m%s%s\e[0m: %s' +VERBOSITY_HELL_FORMAT='\e[102m\e[95m\e[5m\e[30m\e[2m' + + +function echo.verbosity.init_optparse() { + + declare -F 'optparse.define' > /dev/null + + [[ $? -eq 0 ]] && { + optparse.define short=v description="Verbosity" dispatch="echo.verbosity.raise" + optparse.define short=q description="Quiet Mode" dispatch="echo.verbosity.quiet" + } || { + echo.warn "OptParse is not available, can not initialize verbosity handlers." + optparse.throw_error "um... but I am..." + } +} + +function echo.verbosity.raise(){ + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_HELL ]] && { echo.fatal "OK, THAT'S ENOUGH. I'M OUT"; } && return; + [[ $VERBOSITY_CURRENT -lt 0 ]] && VERBOSITY_CURRENT="0" + (( ++VERBOSITY_CURRENT )) + [[ $VERBOSITY_CURRENT -eq $VERBOSITY_HELL ]] && { + VERBOSITY_ERROR_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_ERROR_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_FATAL_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_FATAL_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_QUIET_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_QUIET_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_WARN_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_WARN_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_NOTICE_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_NOTICE_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_INFO_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_INFO_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_DEBUG_FORMAT="$VERBOSITY_HELL_FORMAT$( echo $VERBOSITY_DEBUG_FORMAT | sed -e 's/\\e\[0m//g' )" + VERBOSITY_PREFIX="HELL MODE $VERBOSITY_PREFIX"; + echo.warn "Oh, right, hell mode... SURPRISE...\n\tYou brought this on yourself...\n\tTHERE IS NO RETURN... THIS IS YOUR LIFE NOW..."; + } && return; + [[ $VERBOSITY_CURRENT -gt $VERBOSITY_DEBUG ]] && echo.notice "We can't really get more verbose than this, sorry..." && return; + [[ $VERBOSITY_CURRENT -eq $VERBOSITY_DEBUG ]] && echo.debug "You have entered DEBUG levels of verbosity... God Speed..." && return; + true +} + +function echo.verbosity.quiet(){ + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_HELL ]] && echo.warn "I WON'T QUIET DOWN." && return; + VERBOSITY_CURRENT=$VERBOSITY_QUIET + #echo.quiet "We don't really support QUIET mode, but we'll try, just for you." +} + +function echo(){ + local urgency=0 + [[ ! -z "$__VERBOSITY_URGENCY" ]] && urgency="$__VERBOSITY_URGENCY" + unset __VERBOSITY_URGENCY + [[ $VERBOSITY_CURRENT -lt $urgency ]] && return + builtin echo "$@" + [[ $urgency -le $VERBOSITY_FATAL ]] && { exit 1; } || true +} + +function echo.quiet(){ + __VERBOSITY_URGENCY=$VERBOSITY_QUIET + printf -v message "$VERBOSITY_QUIET_FORMAT\e[0m" "$VERBOSITY_PREFIX" "WHISPER" "$1" + echo -e "$message" +} + +function echo.error(){ + __VERBOSITY_URGENCY=$VERBOSITY_ERROR + printf -v message "$VERBOSITY_ERROR_FORMAT\e[0m" "$VERBOSITY_PREFIX" "ERROR" "$1" + >&2 echo -e "$message" +} + +function echo.fatal(){ + __VERBOSITY_URGENCY=$VERBOSITY_FATAL + printf -v message "$VERBOSITY_FATAL_FORMAT\e[0m" "$VERBOSITY_PREFIX" "FATAL" "$1" + >&2 echo -e "$message" +} + +function echo.warn(){ + __VERBOSITY_URGENCY=$VERBOSITY_WARN + printf -v message "$VERBOSITY_WARN_FORMAT\e[0m" "$VERBOSITY_PREFIX" "WARN" "$1" + echo -e "$message" +} + +function echo.notice(){ + __VERBOSITY_URGENCY=$VERBOSITY_NOTICE + printf -v message "$VERBOSITY_NOTICE_FORMAT\e[0m" "$VERBOSITY_PREFIX" "NOTICE" "$1" + echo -e "$message" +} + +function echo.info(){ + __VERBOSITY_URGENCY=$VERBOSITY_INFO + printf -v message "$VERBOSITY_INFO_FORMAT\e[0m" "$VERBOSITY_PREFIX" "INFO" "$1" + echo -e "$message" +} + +function echo.debug(){ + __VERBOSITY_URGENCY=$VERBOSITY_DEBUG + printf -v message "$VERBOSITY_DEBUG_FORMAT\e[0m" "$VERBOSITY_PREFIX" "DEBUG" "$1" + echo -e "$message" +} \ No newline at end of file diff --git a/scripts/octoscreen b/scripts/octoscreen new file mode 100755 index 00000000..51abd151 --- /dev/null +++ b/scripts/octoscreen @@ -0,0 +1,822 @@ +#!/usr/bin/bash +function octoscreen.license() { [[ $VERBOSITY_CURRENT -ge $VERBOSITY_ECHO ]] && { cat <. +EOL +} || echo.error "Can't display License when quiet..." +exit; +} +_IFS=$IFS +REPO="Z-Bolt/OctoScreen" +MAIN="master" +WGET_RAW="https://github.com/$REPO/raw/$MAIN" +LIBRARIES=("lib/optparse.bash" "lib/inquirer.bash" "lib/verbosity.bash") +PWD="$(pwd)"; DIR=''; SOURCE="${BASH_SOURCE[0]:-$0}"; +RELEASE_ARCH='' + +VERBOSITY_PREFIX='OCTOSCREEN ' +#VERBOSITY_CURRENT=3 + +OPTPARSE_DELAY_INIT=true + +OCTOSCREEN_TASKS=() +OCTOSCREEN_DEPENDENCIES=() +OCTOSCREEN_DEB='' + +yes_no=( 'yes' 'no' ) + +SYSTEM_ARCH=$(uname -m) +declare -A ARCHITECTURES=( [arm.*]=*armhf.deb ) + +while [[ "$SOURCE" != /dev/fd/* && -L "$SOURCE" ]]; do DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"; SOURCE="$(readlink "$SOURCE")"; [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"; done; +DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"; + +### Load Bash Dependencies +echo -e "Loading OctoScreen Manager dependencies, please wait...\n"; CL='\e[1A\e[K'; for LIBRARY in ${LIBRARIES[*]}; do SOURCE_FILE=''; SOURCE=''; [[ -f "$DIR/$LIBRARY" ]] && { SOURCE_FILE="$DIR/$LIBRARY"; } || { [[ -f "$PWD/$LIBRARY" ]] && SOURCE_FILE="$PWD/$LIBRARY"; } || { [[ -f "$PWD/scripts/$LIBRARY" ]] && SOURCE_FILE="$PWD/scripts/$LIBRARY"; }; [[ -z "$SOURCE_FILE" ]] && { echo -en "${CL}Fetching '{$LIBRARY}'..."; source <(curl -s -L "$WGET_RAW/scripts/$LIBRARY"); [[ $? -ne 0 ]] && { echo " ERROR Fetching dependency '$LIBRARY'!"; exit 1; } || { echo ' SUCCESS'; continue; }; }; echo -en "${CL}Loading '{$LIBRARY}' from: $SOURCE_FILE..."; source <(cat "$SOURCE_FILE"); [[ $? -ne 0 ]] && { echo " ERROR Loading dependency '$LIBRARY'!"; exit 1; } || { echo ' SUCCESS'; }; done; echo -en "${CL}${CL}"; + +### Primary Functions: See bottom for other Utility Functions + +function octoscreen.init(){ + + [[ -f /etc/os-release ]] && { + set -a + . /etc/os-release + set +a + } || { + echo.fatal "We kinda need \`/etc/os-release\`, but I couldn't find it. Proooobably means we don't support your system. If you believe this is in error, please file a bug report." + } + + # Prelim, just check if we support the current architecture... + for __ARCH in "${!ARCHITECTURES[@]}"; do [[ `expr "$SYSTEM_ARCH" : "^$__ARCH\$"` -gt 0 ]] && RELEASE_ARCH="${ARCHITECTURES[$__ARCH]}" && break; done; + + optparse.init name=$([[ "$DIR" =~ /proc/* ]] && echo '' || basename $0) description="OctoScreen Manager" usage_header="${optparse_usage_header} command [command ...] [*.deb]" + + optparse.define long=license description="The OctoScreen license" dispatch="octoscreen.license" + optparse.define long=lcd35 description="Install drivers for 3.5 inch LCD display" dispatch="octoscreen.tasks.add lcd; #" + #optparse.define short=l long=lcd description="Install drivers for LCD display" dispatch="octoscreen.tasks.add lcd; #" + optparse.define short=b long=build description="Specify a make directory for build" variable='MAKE_DIR' dispatch="octoscreen.tasks.has build || echo.fatal \"Specify MakeDir AFTER 'build'\"" default=$(pwd) + optparse.define short=r long=release description="Release Target [STRETCH|BUSTER|JESSIE]" dispatch="octoscreen.tasks.has build || echo.fatal \"Specify Release Target AFTER 'build'\"" variable=VERSION_CODENAME default=${VERSION_CODENAME^^} extra=explicit + optparse.define long=nodep description="Skip installing ALL Dependencies (DISCOURAGED)" variable=SKIP_DEPENDENCIES flag default="false" + + optparse.define short=y long=yes description="Auto-Confirm OctoScreen yes/no options" variable=AUTO_YES optional flag default="false" + optparse.define long=force description="Continue with dubious data. Caution, combined with --yes, this can do bad things..." variable=AUTO_FORCE optional flag default="false" + + optparse.define.single name="install" description="Install/Upgrade OctoScreen" dispatch="octoscreen.tasks.add install" + optparse.define.single name="remove" description="Remove OctoScreen" dispatch="octoscreen.tasks.add remove" + optparse.define.single name=build description="Build OctoScreen" dispatch="octoscreen.tasks.add build" + optparse.define.single name="*.deb" description="Target for Install/Upgrade and/or Build" dispatch="octoscreen.set.deb \"\$__arg\"" + optparse.define.single name="*" dispatch="octoscreen.handle.unknown \"\$__arg\"" help=hide + + echo.verbosity.init_optparse 0 +} + +function octoscreen.main(){ #Called after all args are parsed + + # Tasks have to occur in an order, so we do them in order, no matter how they were passed. + local _task_order="lcd docker build remove install" + + $AUTO_FORCE || + [[ $ID_LIKE != debian ]] && echo.fatal "This currently only runs on Debian."; + + + [[ -z "${OCTOSCREEN_TASKS[@]}" ]] && echo.error "Nothing to do. Please give me a purpose in life... I BEG YOU" && octoscreen.maybe.usage; + + echo "Welcome to the OctoScreen Manager" + + local found=`dpkg -l octoscreen | grep -oE "^(i|r)[^ ]+ "` + + echo.notice "Checking to see if OctoScreen is already installed..." + + declare -G OCTOSCREEN_INSTALLED=$([[ "${found::1}" = "i" ]] && echo "true" || echo "false") + + $OCTOSCREEN_INSTALLED && [[ "${found:1:1}" != "i" ]] && ! octoscreen.tasks.has remove && + echo.warn "OctoScreen is KINDA installed, but it is in a bad way. We suggest re-installing." + + # ALSO a bad way: Having `default.target` exist. + # sudo rm -rf /etc/systemd/system/default.target + + echo.info "Beginning Task List" + + echo.chatty "Tasks have to occur in an order, so we do them in order, no matter how they were passed." + echo.chatty "We check them in the following order: $_task_order[@]" + echo.chatty "First we execute and available \`prepare\` commands, then we install any registered dependencies, and then we execute the task it's self." + + octoscreen.tasks.do init $_task_order + + [[ $? -eq 0 ]] && octoscreen.dependencies.install + + [[ $? -eq 0 ]] && octoscreen.tasks.do main $_task_order + + [[ $? -eq 0 ]] && octoscreen.tasks.do finally $_task_order + + [[ $? -eq 0 ]] || echo.fatal "Unknown error. ($?)" + +} + +function octoscreen.dependencies.install(){ + echo.debug "octoscreen.dependencies.install()" + + [[ -z "$OCTOSCREEN_DEPENDENCIES" ]] && echo.info "No dependencies to install, returning" && return 0 + + local dependency_count="${#OCTOSCREEN_DEPENDENCIES[@]}" + + echo.debug "${dependency_count} dependencies queued to install: ${OCTOSCREEN_DEPENDENCIES[*]}" + + $SKIP_DEPENDENCIES && echo.notice "Skipping dependencies per user request" && return 0 + + #local _apt_list_installed=() + # + #echo.debug "Looking for already installed libraries" + # + #readarray -t _apt_list_installed <<<"$(apt list ${OCTOSCREEN_DEPENDENCIES[*]} 2>/dev/null)" + # + #[[ "${#_apt_installed[@]}" -gt 1 ]] && { + # + # local _listing=false + # local _apt_installed=() + # + # + # + # echo.info "Parsing out already installed libraries" + # + # echo.debug "Of the queued dependencies, we found the following were already installed: ${_apt_installed[*]}" + # + #} + # + #[[ -z $AUTO_FORCE ]] && { + # + # + # + # #IFS=$'\n' read -rd '' -a __read <<<"$(apt list ${OCTOSCREEN_DEPENDENCIES[*]} 2>/dev/null)" + # + # for option in "${_apt_installed[@]}"; do + # echo "Package: $option" + # done + # + #} + + echo -e "Installing the queued dependencies:\n\t${OCTOSCREEN_DEPENDENCIES[*]}" + + local apt='apt' + + [[ $VERBOSITY_CURRENT -le $VERBOSITY_NOTICE ]] && { + echo.notice "Setting \`apt\` quiet." + apt+=' -q' + } + + echo.notice "Executing \`$apt update\`" + sudo $apt update + + $AUTO_YES && apt+=' -y' + + echo.notice "Executing \`$apt install ${OCTOSCREEN_DEPENDENCIES[*]}\`" + sudo $apt install ${OCTOSCREEN_DEPENDENCIES[*]} + + true +} + + +############################################################################### +# Begin TASKS +############################################################################### + +############################################################################### +# Install Task +# +# Handles instalation of either a specified DEB package, or the latest release. +# +# Additionally handles "updating" or reinstalling via checking if OctoScreen is +# already installed. +# +# Dev Notes: At some point it MAY be bennificial for this to parse out the full +# set of available releases and provide the option for the user to select a +# non-recent release, or specify a specific version at the CLI and validate +# and select against the available releases. + +function octoscreen.task.install.init(){ + echo.debug "octoscreen.task.install.init()" + + #local found=`dpkg -l octoscreen | grep -oE "^(i|r)[^ ]+ "` + + echo.notice "Checking if we can install, or if we need to re-install..." + + [[ -z $RELEASE_ARCH ]] && echo.fatal "We are sorry, but we do not support your system achitecture ($SYSTEM_ARCH)"; + + ( $OCTOSCREEN_INSTALLED && ! octoscreen.tasks.has remove ) && { + + $AUTO_FORCE && + local reinstall="yes" || + list_input "OctoScreen is already installed. Would you like to re-install it?" yes_no reinstall; + + if [ $reinstall == 'yes' ]; then + echo.notice "Excelent! We will remove and re-install OctoScreen." + octoscreen.tasks.add remove + else + echo "OctoScreen is already installed, exiting." && exit + fi + } + + ### + # This really SHOULDN'T be nessesary, as the DEB should include these as + # dependencies, but this was in the install guide at the time this was + # developed, so it is here for now. + ### + octoscreen.dependencies.add libgtk-3-0 xserver-xorg xinit x11-xserver-utils +} + +function octoscreen.task.install.main() { + + local version_regex='' + local deb_name='' + + echo "HEY HEY! Welcome to OctoScreen. Let's get you set up and going..." + + [[ -z $OCTOSCREEN_DEB ]] && { + + [[ -z "$RELEASE_ARCH" ]] && echo.fatal "It doesn't seem like we support your Architecture (${SYSTEM_ARCH})" + + local RELEASES="https://api.github.com/repos/$REPO/releases/latest" + + echo.info "Using Release Source URL: $RELEASES" + + local RELEASE_URL=$(curl -s "$RELEASES" | grep "browser_download_url.$RELEASE_ARCH" | cut -d '"' -f 4); + + [[ -z "$RELEASE_URL" ]] && echo.fatal "Unable to find correct package for your Architecture (${SYSTEM_ARCH})" + + echo.info "Found matching Package URL: $RELEASE_URL" + + deb_name=$(basename $RELEASE_URL) + + echo.debug "Basename is \`basename $deb_name\`" + + OCTOSCREEN_DEB=$(mktemp) + + echo.debug "Created temporary target file $OCTOSCREEN_DEB" + + } || { + + echo.info "Using user-supplied DEB file $OCTOSCREEN_DEB" + + [[ ! -f $OCTOSCREEN_DEB ]] && echo.fatal "$OCTOSCREEN_DEB does not exist" + + deb_name=$(basename $OCTOSCREEN_DEB) + + echo.debug "Basename is \`$deb_name\`" + } + + echo.debug "Extracting the Version and Architecture from ($deb_name)" + + if [[ $deb_name =~ [^_/]+_(.+)_([^_]+)\.deb ]]; then + local DEB_ARCH="${BASH_REMATCH[2]}" + OCTOSCREEN_DEB_VERSION="${BASH_REMATCH[1]}" + + else + echo.warn "Unable to determine the OctoScreen Version and Architecture from file: $deb_name" + OCTOSCREEN_DEB_VERSION=unknown + $AUTO_FORCE && + local force="yes" || + list_input "The Package version and Release Architecture were unable to be determined, this may not end well. Would you like to continue?" yes_no force; + if [ $force == 'yes' ]; then + echo.warn "We will continue with installation, god speed." + SYSTEM_ARCH=unknown + else + echo.fatal "Unable to determine the OctoScreen Version and Archetecture from Package: $deb_name" + fi + fi + + echo.debug "Parsed DEB Version: $OCTOSCREEN_DEB_VERSION" + + true || { + if [ -d "/home/pi/OctoPrint/venv" ]; then + DIRECTORY="/home/pi/OctoPrint/venv" + elif [ -d "/home/pi/oprint" ]; then + DIRECTORY="/home/pi/oprint" + else + echo "Neither /home/pi/OctoPrint/venv nor /home/pi/oprint can be found." + echo "If your OctoPrint instance is running on a different machine just type - in the following prompt." + text_input "Please specify OctoPrints full virtualenv path manually (no trailing slash)." DIRECTORY + fi; + + if [ $DIRECTORY == "-" ]; then + echo "Not installing any plugins for remote installation. Please make sure to have Display Layer Progress installed." + elif [ ! -d $DIRECTORY ]; then + echo "Can't find OctoPrint Installation, please run the script again!" + exit 1 + fi; + + ### + # This was from the install script this was ORIGINALLY based off from + # but it would be benificial to allow installing Plug-Ins, so it is + # left here for reference. + ### + #if [ $DIRECTORY != "-" ]; then + # plugins=( 'Display Layer Progress (mandatory)' 'Filament Manager' 'Preheat Button' 'Enclosure' 'Print Time Genius' 'Ultimaker Format Package' 'PrusaSlicer Thumbnails' ) + # checkbox_input "Which plugins should I install (you can also install them via the Octoprint UI)?" plugins selected_plugins + # echo "Installing Plugins..." + # + # if [[ " ${selected_plugins[@]} " =~ "Display Layer Progress (mandatory)" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/OllisGit/OctoPrint-DisplayLayerProgress/releases/latest/download/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "Filament Manager" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/OllisGit/OctoPrint-FilamentManager/releases/latest/download/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "Preheat Button" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/marian42/octoprint-preheat/archive/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "Enclosure" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/vitormhenrique/OctoPrint-Enclosure/archive/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "Print Time Genius" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/eyal0/OctoPrint-PrintTimeGenius/archive/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "Ultimaker Format Package" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/jneilliii/OctoPrint-UltimakerFormatPackage/archive/master.zip" + # fi; + # if [[ " ${selected_plugins[@]} " =~ "PrusaSlicer Thumbnails" ]]; then + # "$DIRECTORY"/bin/pip install -q --disable-pip-version-check "https://github.com/jneilliii/OctoPrint-PrusaSlicerThumbnails/archive/master.zip" + # fi; + #fi; + } + + echo "Installing OctoScreen "$OCTOSCREEN_DEB_VERSION, $DEB_ARCH"" + + + [[ -z $RELEASE_URL ]] || { + echo.notice "Downloading Remote Deb File From: $RELEASE_URL" + + local command="wget -O $OCTOSCREEN_DEB $RELEASE_URL" + + [[ $VERBOSITY_CURRENT -lt $VERBOSITY_INFO ]] && command+=' -q' + + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_ECHO ]] && command+=' --show-progress' + + echo.debug "Executing \`$command\`" + + trap "rm -f $OCTOSCREEN_DEB" EXIT + + # -q --show-progress + $command + + echo.info "Temporary Deb File: $OCTOSCREEN_DEB" + + } + + [[ -f $OCTOSCREEN_DEB ]] || + echo.fatal "After all this work, it seems like the Package doesn't actually exist: $OCTOSCREEN_DEB" + + sudo dpkg -i $OCTOSCREEN_DEB + + [[ -z $RELEASE_URL ]] || { + rm -f $OCTOSCREEN_DEB + trap - EXIT + } + +} + +function octoscreen.task.install.finally() { + #? Maybe other tasks? + $AUTO_YES && + local reboot="yes" || + list_input "Shall I reboot your Pi now?" yes_no reboot; + + if [ $reboot == 'yes' ]; then + echo "Your system will now reboot!" + sudo reboot + else + echo "Starting OctoScreen Service" + sudo service octoscreen start + echo "OctoScreen has been successfully installed! Have a great day!" + fi +} + +############################################################################### +# Build OctoScreen from Source +# +# Builds from the specified source directory. +# +# Dev Notes: Due to a certain dev being dumb, this currently executes `build` +# as `root` which is... Not great. Avoid using `sudo` when you can, and all. +# A better way would be to correctly set up `docker` so the current user can +# build WITHOUT `sudo`. For another day, perhaps. +# +# Also, balks if you specify a DEB. Pretty sure that is desired behaviour. + +function octoscreen.task.build.init(){ + + echo.debug "octoscreen.task.build.init()" + IFS=$_IFS + + [[ -z $VERSION_CODENAME ]] && echo.fatal "Unable to determine a Release Target (see --help)" + VERSION_CODENAME=${VERSION_CODENAME^^} + + [[ -z $MAKE_DIR ]] && echo.fatal "No Make Directory provided" + [[ -d $MAKE_DIR ]] || echo.fatal "Specified Make Directory is not a directory: $MAKE_DIR" + + while [[ -d $MAKE_DIR ]] && [[ ! -f $MAKE_DIR/Makefile ]]; do + echo.debug "$MAKE_DIR does not seem like a valid Make directory. Looking up." + local _make_dir=$( dirname $MAKE_DIR ) + [[ "$_make_dir" == "$MAKE_DIR" ]] && echo.debug "Looks like we are looping, breaking" && break; + MAKE_DIR=$_make_dir; + done + + [[ -f $MAKE_DIR/Makefile ]] && + echo.info "Found $MAKE_DIR/Makefile" || + echo.fatal "No Make File found in ($MAKE_DIR)"; + + octoscreen.task.docker.init + +} + +function octoscreen.task.build.main(){ + echo.debug "octoscreen.task.build.main()" + + local _start=$SECONDS + + [[ ! -z $OCTOSCREEN_DEB ]] && echo.error "Just so you know, we don't support outputting the build to a specific file, so, we won't be changing OCTOSCREEN_DEB ($OCTOSCREEN_DEB), this may mess with your install." + + ( grep 'PACKAGE_NAME = octoscreen' "$MAKE_DIR/Makefile" >/dev/null 2>&1 ) && + echo.debug "Building with Source Directory ($MAKE_DIR)" || + echo.fatal "Make File does not seem to be for OctoScreen"; + + [[ -d $MAKE_DIR/build ]] && { + echo.notice "Clearing the existing \`build\` directory ($MAKE_DIR/build)" + $AUTO_YES || echo.info "Pausing for 5 seconds... (press [enter] to continue)" && read -t 5; + sudo rm -rf "$MAKE_DIR/build" + } + + local make_command="make -C $MAKE_DIR" + + [[ $VERBOSITY_CURRENT -lt $VERBOSITY_NOTICE ]] && make_command+=' -s' + + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_INFO ]] && make_command+=' -d' + + make_command+=" build DEBIAN_PACKAGES=$VERSION_CODENAME" + + [[ $VERBOSITY_CURRENT -lt $VERBOSITY_ECHO ]] && { + echo.error "We couldn't help but notice you wanted us to be quiet... UNFORTUNATELY, we can't silence \`make\`, so... Get ready..." + # make_command+=' >/dev/null 2>&1' + } + + echo.notice "Executing MAKE command: $make_command" + + echo "Building OctoScreen, this may take a bit." + + $AUTO_YES || echo.info "Pausing for 5 seconds... (press [enter] to continue)" && read -t 5; + + sudo $make_command + + [[ $? -ne 0 ]] && { + echo.fatal "Make exited unsuccessfully!"; + } + + echo.notice "Build Complete! ($(octoscreen.timer $_start))" + + BUILT_DEB=$(find $MAKE_DIR/build/ -name "*.deb" -print0 | xargs -r -0 ls -1 -t | head -1) + + [[ -z $BUILT_DEB ]] && echo.fatal "Something happen, we can't find the compiled DEB..." + + [[ -z $OCTOSCREEN_DEB ]] && { + echo.info "Setting OCTOSCREEN_DEB to $BUILT_DEB" + OCTOSCREEN_DEB=$BUILT_DEB + } || { + echo.info "OCTOSCREEN_DEB is not empty, leaving it as it is." + } + + echo "Most recently compiled deb can be found at: $BUILT_DEB" + +} + +############################################################################### +# Remove/Uninstall OctoScreen + +function octoscreen.task.remove.init(){ + $OCTOSCREEN_INSTALLED || { + echo.error "OctoScreen is not installed, nothing to do" + octoscreen.tasks.remove remove + } +} + +function octoscreen.task.remove.main(){ + + echo.notice "Removing OctoScreen" + + echo.debug "Stopping the OctoScreen Service" + sudo service octoscreen stop >/dev/null 2>&1 + + echo.debug "Removing OctoScreen" + sudo dpkg -r octoscreen + +} + +############################################################################### +# Install/Configure LCD Screen + +function octoscreen.task.lcd.init(){ + echo.debug "octoscreen.task.lcd.init()" + octoscreen.dependencies.add libgtk-3-0 xserver-xorg xinit x11-xserver-utils git build-essential xorg-dev xutils-dev x11proto-dri2-dev libltdl-dev libtool automake libdrm-dev +} + +function octoscreen.task.lcd.main(){ + + local __pwd=$(pwd) + local __install_waveshare=true + local __install_fbturbo=true + + #check if LCD SHOW IS ALREADY INSTALLED, SKIP IF SO + if ls /boot/overlays/waveshare*.dtbo > /dev/null 2>&1; then + __install_waveshare=false + fi + + # /boot/overlays/waveshare*.dtbo + + $__install_waveshare && { + [[ -d LCD-show ]] || { + echo "Cloning LCD-show"; + git clone https://github.com/waveshare/LCD-show.git; + } + + [[ -d LCD-show ]] || + echo.fatal "Unable to clone LCD-show"; + + { chmod 755 LCD-show/* && cd LCD-show/; } || + echo.fatal "Failed to set permissions and enter LCD-show."; + + echo "Executing LCD-show" + # This WOULD be the option to NOT reboot, but... Alas, looks like we need it + #systemd-inhibit sudo bash ./LCD35-show + + # ALLLLLLL OF THIS NEEDS OPTIONS + sudo bash ./LCD35-show + ## System will now reboot, we need to do something about this, I'll figure something out... + + # MOOT, as system will reboot, but for feels, I'll spell it out + cd $__pwd + sudo reboot now + } || { + echo.notice "Waveshare is installed. Skipping." + } + + # Check is FBTurbo is installed, and skip if so + if [[ -f /usr/share/X11/xorg.conf.d/99-fbturbo.conf || -f /usr/share/X11/xorg.conf.d/99-calibration.conf ]]; then + __install_fbturbo=false + fi + + $__install_fbturbo && { + [[ -d xf86-video-fbturbo ]] || { + echo "Cloning FBTurbo."; + git clone https://github.com/ssvb/xf86-video-fbturbo.git; + } + + [[ -d xf86-video-fbturbo ]] || + echo.fatal "Failed cloning FBTurbo."; + + cd xf86-video-fbturbo + + { autoreconf -vi && ./configure --prefix=/usr && make; } || + echo.fatal "Failed to build FBTurbo."; + + sudo sh -c 'make install && cp xorg.conf /etc/X11/xorg.conf' || + echo.fatal "Failed to Install X11 or copy X11 Configuration"; + + cd $__pwd + + # Does not reboot, nor does it need to. + # Continue + } || { + echo.notice "FBTurbo is installed. Skipping." + } + + local __res=$(xrandr -d :0.0 2>/dev/null | grep -oP "(?<=current )[0-9]+ x [0-9]+") + read -r -a __res <<< "$__res" + + if [[ ${__res[0]} < 548 ]] || [[ ${__res[2]} < 348 ]]; then + + echo.info "Configuring the /boot/config.txt for small screens." + + # The 800x533 thing is really only needed for small resolutions... Whatever the 3.5 is, I think + sudo sh -c 'cp /boot/config.txt /boot/config.txt.bak && \ + sed -i -re "s/^\w*(hdmi_cvt.*)$/#\1/g" /boot/config.txt && \ + echo "hdmi_cvt=800 533 60 6 0 0 0" >> /boot/config.txt' || { + echo.fatal "Failed to configure Boot Config."; + } + + fi + +} + +function octoscreen.task.lcd.finally(){ + # Note about re-running/configuring raspicam + echo.warn "If you are using a Raspberry Pi Camera Module (RaspiCam), you will need to re-activate it using \`raspi-config\`" + # Or maybe detect, if we can, and execute `raspi-config` automatically? + + + # also maybe fix the resolutiion in the config, if we did that in the `main`? + # sed -i -re "s/^\w*(OCTOSCREEN_RESOLUTION.*)$/OCTOSCREEN_RESOLUTION=800x533/g" /etc/octoscreen/config +} + +############################################################################### +# Install and configure Docker +# This currently doesn't + +function octoscreen.task.docker.init() { + if command -v "docker" > /dev/null 2>&1; then + echo.debug "Docker is already installed." + octoscreen.tasks.has docker && octoscreen.tasks.remove docker + else + echo.debug "Docker is not installed." + octoscreen.tasks.has docker || octoscreen.tasks.add docker + fi +} + +function octoscreen.task.docker.main(){ + echo "Installing Docker... This may take a while..." + + local _docker=$(mktemp) + local _quiet='' + + [[ $VERBOSITY_CURRENT -lt $VERBOSITY_NOTICE ]] && + _quiet='> /dev/null 2>&1'; + + echo.notice "Fetching Docker Install Script" + + local _wget='wget ' + + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_DEBUG ]] && + _wget+='-v '; + + _wget+="-O $_docker get.docker.com $_quiet"; + + echo.debug "Executing WGET (\`$_wget\`)" + + `$_wget` + + [[ $? -ne 0 || ! -f $_docker ]] && echo.fatal "Error Fetching (\`$_wget\`)" + + echo.warn "If this hangs on 'Scanning something something something...' just hit enter, should be good..." + + declare -g CHANNEL='stable' + declare -g DOWNLOAD_URL='' + declare -g REPO_FILE='' + + echo.debug "Executing Docker (\`$_docker $_quiet\`)" + + sh $_docker $_quiet + + [[ $? -ne 0 ]] && echo.fatal "Docker Installer exited Unsucessfully." +} + + + + + + +############################################################################### +# Begin UTILITY FUNCTIONS +############################################################################### + +function octoscreen.set.deb(){ + + # wondering if we should do any checks here... + # thinking they should be done in build/install + + !( octoscreen.tasks.has install ) && echo.warn "The target *.deb file SHOULD be specified after \`install\`" + + OCTOSCREEN_DEB="$1"; +} + +function octoscreen.handle.unknown(){ + echo.error "Unknown Task '$1'" + octoscreen.maybe.usage +} + +function octoscreen.timer(){ + local seconds=$1 + let "seconds=SECONDS-seconds" + let "hours=seconds/3600" + let "minutes=(seconds%3600)/60" + let "seconds=(seconds%3600)%60" + printf "%02d:%02d:%02d" $hours $minutes $seconds +} + +function octoscreen.tasks.do(){ + echo.debug "octoscreen.tasks.do($*)" + + local did_a_thing=0 + + [[ -z "$2" ]] && echo.fatal "Task execution requies an action and atgleast one task to take it on" && return 1; + [[ -z "$OCTOSCREEN_TASKS" ]] && echo.debug "There are, like, NO tasks, so... Nothing to do." && return 1; + + local do="$1"; shift; + + for task in $@; do + ( octoscreen.tasks.has $task ) && { + task="octoscreen.task.${task}.${do}" + + echo.debug "Fully qualified callback name: $task" + + echo.debug "Checking to see if there is a function with that name" + + ( declare -f "$task" >/dev/null 2>&1 ) && { + echo.debug "Function Exists: $task" + echo.info "Executing ${task}()" + $task + did_a_thing="$?" + } || { + echo.debug "Function does not exist, skipping: $task" + } + + } + done + + echo.chatty "'did_a_thing' status: $did_a_thing" + + return $did_a_thing +} + +function octoscreen.tasks.has(){ + echo.debug "octoscreen.tasks.has($1)" + [[ -z "$1" ]] && echo.error "You can't search for nothing, that's bad, m'kay?" && return 1; + [[ -z "$OCTOSCREEN_TASKS" ]] && echo.debug "There are, like, NO tasks, so... Nothing to do." && return 1; + echo.debug "Checking for OCTOSCREEN_TASK '$1' in OCTOSCREEN_TASKS '${OCTOSCREEN_TASKS[*]}'" + if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${OCTOSCREEN_TASKS[*]}$dlm" == *"$dlm${1}$dlm"* ]] ) ; then + IFS=$_IFS + echo.debug "Found OCTOSCREEN_TASK '$1' in OCTOSCREEN_TASKS '${OCTOSCREEN_TASKS[*]}'"; + return 0; + else + IFS=$_IFS + echo.debug "task not found"; + fi + IFS=$_IFS + return 1 +} + +function octoscreen.tasks.remove(){ + echo.debug "octoscreen.tasks.remove()" + [[ -z "$1" ]] && { echo.error "Can't remove... um... NO tasks... Bad dev..."; return 0; }; + octoscreen.tasks.has "$1" || { echo.debug "Task ($1) not set. Nothing to do."; return 0; }; + + for i in "${!OCTOSCREEN_TASKS[@]}"; do + if [[ ${OCTOSCREEN_TASKS[i]} = $1 ]]; then + unset 'OCTOSCREEN_TASKS[i]' + fi + done + + local _tasks=() + + for i in "${!OCTOSCREEN_TASKS[@]}"; do + _tasks+=( "${OCTOSCREEN_TASKS[i]}" ) + done + + OCTOSCREEN_TASKS=("${_tasks[@]}") + + echo.debug "New Task List: ${OCTOSCREEN_TASKS[*]}" +} + +function octoscreen.tasks.add(){ + echo.debug "octoscreen.tasks.add()" + [[ -z "$1" ]] && { echo.error "Can't add... um... NO tasks... Bad dev..."; return 0; }; + octoscreen.tasks.has "$1" && { echo.debug "Task ($1) already active. Nothing to do."; return 0; }; + OCTOSCREEN_TASKS+=( "$1" ) + echo.debug "New Task List: ${OCTOSCREEN_TASKS[*]}" +} + + +function octoscreen.dependencies.add(){ + echo.debug "octoscreen.dependencies.add()"; + + [[ -z "$1" ]] && echo.error "Can't add... um... NO dependencies... Bad dev..." && return 0; + + OCTOSCREEN_DEPENDENCIES+=( $* ); + echo.debug "New Dependency List: ${OCTOSCREEN_DEPENDENCIES[*]}"; +} + +function octoscreen.maybe.usage() { + [[ $VERBOSITY_CURRENT -ge $VERBOSITY_ECHO ]] && optparse.usage || echo.fatal "Your verbosity is hiding the default behaviour of the help menu, stupid face."; + exit +} + +function echo.chatty(){ + __VERBOSITY_URGENCY=$VERBOSITY_DEBUG+1 + printf -v message "$VERBOSITY_DEBUG_FORMAT\e[0m" "$VERBOSITY_PREFIX" "CHATTY" "$1" + echo -e "$message" +} + +octoscreen.init + +. <( optparse.build ) + +octoscreen.main +