diff --git a/inc/nux.inc.sh b/inc/nux.inc.sh index ddf935b..4a0a9b7 100644 --- a/inc/nux.inc.sh +++ b/inc/nux.inc.sh @@ -2,106 +2,14 @@ readonly NUX_INC_DIR=$(dirname $(realpath ${BASH_SOURCE[0]})) readonly NUX_ENV_DIR=$(dirname $NUX_INC_DIR) +readonly NUX_CACHE_DIR="$NUX_ENV_DIR/cache" # Color definitions -if [ -t 1 ]; then - readonly nc_bold=`tput setaf 0` - readonly nc_bg_bold=`tput setab 0` - readonly nc_black=`tput setab 0` - readonly nc_bg_black=`tput setab 0` - readonly nc_cyan=`tput setaf 6` - readonly nc_bg_cyan=`tput setab 6` - readonly nc_magenta=`tput setaf 5` - readonly nc_bg_magenta=`tput setab 5` - readonly nc_red=`tput setaf 1` - readonly nc_bg_red=`tput setab 1` - readonly nc_white=`tput setaf 7` - readonly nc_bg_white=`tput setab 7` - readonly nc_green=`tput setaf 2` - readonly nc_bg_green=`tput setab 2` - readonly nc_yellow=`tput setaf 3` - readonly nc_bg_yellow=`tput setab 3` - readonly nc_blue=`tput setaf 4` - readonly nc_bg_blue=`tput setab 4` - readonly nc_end=`tput sgr0` - readonly NC_Bold=`tput bold` - readonly NC_No=`tput sgr0` # No Color - readonly NC_Black='\033[0;30m' - readonly NC_Green='\033[0;32m' - readonly NC_Red=$nc_bold$nc_red - readonly NC_BrownOrange='\033[0;33m' - readonly NC_Blue='\033[0;34m' - readonly NC_Purple='\033[0;35m' - readonly NC_Cyan='\033[0;36m' - readonly NC_LightGray='\033[0;37m' - readonly NC_DarkGray='\033[1;30m' - readonly NC_LightRed='\033[1;31m' - readonly NC_LightGreen='\033[1;32m' - readonly NC_Yellow=$nc_yellow - readonly NC_LightBlue='\033[1;34m' - readonly NC_LightPurple='\033[1;35m' - readonly NC_LightCyan='\033[1;36m' - readonly NC_White=$nc_white - - readonly NC_error=$NC_Red -fi ## #Public functions: ## -## ##Logging - -# Color for message levels -NC_LOG_color_info=$NC_LightGray -NC_LOG_color_error=$NC_LightRed -NC_LOG_color_warning=$NC_Yellow -NC_LOG_color_debug=$NC_White - -NC_LOG_current=3 - -NC_LOG_id_none=0 -NC_LOG_id_error=1 -NC_LOG_id_warning=2 -NC_LOG_id_info=3 -NC_LOG_id_debug=4 -NC_LOG_id_trace=5 - -## -## NUX Script environment provides basic logging capabilities. -## -## Currently there are 5 log levels supported (in order of detail): -## error -## warning -## info -## debug -## trace -## -## nux.log:: -## Outputs log message to *STDERR*. LOG messages are filtered out based on -## level. Use *nux.log.level* to specify which messages should be displayed. -## -## -function nux.log { - local level=$1 - local message=$2 - local color=NC_LOG_color_$level - local level_num=NC_LOG_id_$level - shift; - if [ ${!level_num} -le $NC_LOG_current ]; then - echo -e "${!color}[$level]$NC_No $*$NC_No" >&2 - fi -} - - -## nux.log.level:: -## Sets maximum level of details to be logged. -## -function nux.log.level { - local level=$1 - local level_id=NC_LOG_id_$level - NC_LOG_current=${!level_id} -} function nux.echo.error { echo "${NC_error}$* ${NC_No}"; @@ -139,41 +47,17 @@ function nux.require { } -function nux.include { +function nux.use { local incfile="$1.inc.sh" - source "$NUX_INC_DIR/$incfile" -} - -## nux.check.function:: -## -function nux.check.function { - nux.log trace "Checking if $1 is function." - declare -f "$1" &>/dev/null && return 0 - return 1 -} - -function nux.check.nuxenv.file { - path=$(realpath -Lms "$1") - [[ "$path" =~ "^$NUX_ENV_DIR" ]] -} - - -function nux.check.optional { - local function="$1"; shift; - if nux.check.function "$function" ; then - $function "$@" - fi -} - -function nux.check.exec { - local binary=$1; - test -n "$(which "$binary")" -} - -## nux.check.file.exists:: -## -function nux.check.file.exists { - test -e "$1" -o -h "$1"; + local nuxshfile="$1.nuxsh.sh" + #FIXME: Do not use same file twice. + if [ -e "$NUX_INC_DIR/$incfile" ]; then + source "$NUX_INC_DIR/$incfile"; + elif [ -e "$NUX_INC_DIR/$nuxshfile" ]; then + nux.nuxsh.use "$NUX_INC_DIR/$nuxshfile" "$NUX_CACHE_DIR/inc/$incfile"; + else + nux.fatal "$1 not available." + fi } function nux.eval { @@ -206,25 +90,8 @@ function nux.dirty.urlencode { echo -n "$1" | sed "s/ /%20/g" } -function nux.help.comment { - local source="$1" - if nux.check.file.exists "$source" ; then - grep -E "^\#\#( |$)" "$source" \ - | cut -d\# -f3- \ - | cut -d" " -f2- \ - | nux.help.shelldoc - fi -} -function nux.help.shelldoc { - sed -r \ - -e "s/^## ?(.*)/${NC_White}\1${NC_No}/gI" \ - -e "s/^# ?(.*)/${NC_Bold}\1${NC_No}/gI" \ - -e "s/^([ a-z0-9.-_]*)::/${NC_Bold}\1${NC_No}/gI" \ - -e "s/\*\*([^*]*)\*\*/${NC_Bold}\1${NC_No}/gI" \ - -e "s/\*([^*]*)\*/${NC_White}\1${NC_No}/gI" \ - -- -} + function nux.url.parse { format=${2:-"protocol:\2\nuser:\4\nhost:\5\nport:\7 \npath:\8"} @@ -232,3 +99,7 @@ function nux.url.parse { -re "s/(([^:\/]*):\/\/)?(([^@\/:]*)@)?([^:\/]+)(:([0-9]+))?(\/(.*))?/$format/g" } + +nux.use nux/log +nux.use nux/check +nux.use nux/nuxsh diff --git a/inc/nux/check.inc.sh b/inc/nux/check.inc.sh new file mode 100644 index 0000000..6432309 --- /dev/null +++ b/inc/nux/check.inc.sh @@ -0,0 +1,31 @@ +## nux.check.function:: +## +function nux.check.function { + nux.log trace "Checking if $1 is function." + declare -f "$1" &>/dev/null && return 0 + return 1 +} + +function nux.check.nuxenv.file { + path=$(realpath -Lms "$1") + [[ "$path" =~ "^$NUX_ENV_DIR" ]] +} + + +function nux.check.optional { + local function="$1"; shift; + if nux.check.function "$function" ; then + $function "$@" + fi +} + +function nux.check.exec { + local binary=$1; + test -n "$(which "$binary")" +} + +## nux.check.file.exists:: +## +function nux.check.file.exists { + test -e "$1" -o -h "$1"; +} diff --git a/inc/nux/color.nuxsh.sh b/inc/nux/color.nuxsh.sh new file mode 100644 index 0000000..9f0cf45 --- /dev/null +++ b/inc/nux/color.nuxsh.sh @@ -0,0 +1,42 @@ +if [ -t 1 ] { + readonly nc_bold=`tput setaf 0` + readonly nc_bg_bold=`tput setab 0` + readonly nc_black=`tput setab 0` + readonly nc_bg_black=`tput setab 0` + readonly nc_cyan=`tput setaf 6` + readonly nc_bg_cyan=`tput setab 6` + readonly nc_magenta=`tput setaf 5` + readonly nc_bg_magenta=`tput setab 5` + readonly nc_red=`tput setaf 1` + readonly nc_bg_red=`tput setab 1` + readonly nc_white=`tput setaf 7` + readonly nc_bg_white=`tput setab 7` + readonly nc_green=`tput setaf 2` + readonly nc_bg_green=`tput setab 2` + readonly nc_yellow=`tput setaf 3` + readonly nc_bg_yellow=`tput setab 3` + readonly nc_blue=`tput setaf 4` + readonly nc_bg_blue=`tput setab 4` + readonly nc_end=`tput sgr0` + + readonly NC_Bold=`tput bold` + readonly NC_No=`tput sgr0` # No Color + readonly NC_Black='\033[0;30m' + readonly NC_Green='\033[0;32m' + readonly NC_Red=$nc_bold$nc_red + readonly NC_BrownOrange='\033[0;33m' + readonly NC_Blue='\033[0;34m' + readonly NC_Purple='\033[0;35m' + readonly NC_Cyan='\033[0;36m' + readonly NC_LightGray='\033[0;37m' + readonly NC_DarkGray='\033[1;30m' + readonly NC_LightRed='\033[1;31m' + readonly NC_LightGreen='\033[1;32m' + readonly NC_Yellow=$nc_yellow + readonly NC_LightBlue='\033[1;34m' + readonly NC_LightPurple='\033[1;35m' + readonly NC_LightCyan='\033[1;36m' + readonly NC_White=$nc_white + + readonly NC_error=$NC_Red +} diff --git a/inc/nux/help.nuxsh.sh b/inc/nux/help.nuxsh.sh new file mode 100644 index 0000000..8bca738 --- /dev/null +++ b/inc/nux/help.nuxsh.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env nuxsh +@namespace nux.help. { + function :shelldoc { + sed -r \ + -e "s/^## ?(.*)/${NC_White}\1${NC_No}/gI" \ + -e "s/^# ?(.*)/${NC_Bold}\1${NC_No}/gI" \ + -e "s/^([ a-z0-9.-_]*)::/${NC_Bold}\1${NC_No}/gI" \ + -e "s/\*\*([^*]*)\*\*/${NC_Bold}\1${NC_No}/gI" \ + -e "s/\*([^*]*)\*/${NC_White}\1${NC_No}/gI" \ + -- + } + + function :comment source { + if nux.check.file.exists "$source" ; then + grep -E "^\#\#( |$)" "$source" \ + | cut -d\# -f3- \ + | cut -d" " -f2- \ + | nux.help.shelldoc + fi + } +} diff --git a/inc/nux/log.inc.sh b/inc/nux/log.inc.sh new file mode 100644 index 0000000..f853f4e --- /dev/null +++ b/inc/nux/log.inc.sh @@ -0,0 +1,52 @@ +## ##Logging + +# Color for message levels +NC_LOG_color_info=$NC_LightGray +NC_LOG_color_error=$NC_LightRed +NC_LOG_color_warning=$NC_Yellow +NC_LOG_color_debug=$NC_White + +NC_LOG_current=3 + +NC_LOG_id_none=0 +NC_LOG_id_error=1 +NC_LOG_id_warning=2 +NC_LOG_id_info=3 +NC_LOG_id_debug=4 +NC_LOG_id_trace=5 + +## +## NUX Script environment provides basic logging capabilities. +## +## Currently there are 5 log levels supported (in order of detail): +## error +## warning +## info +## debug +## trace +## +## nux.log:: +## Outputs log message to *STDERR*. LOG messages are filtered out based on +## level. Use *nux.log.level* to specify which messages should be displayed. +## +## +function nux.log { + local level=$1 + local message=$2 + local color=NC_LOG_color_$level + local level_num=NC_LOG_id_$level + shift; + if [ ${!level_num} -le $NC_LOG_current ]; then + echo -e "${!color}[$level]$NC_No $*$NC_No" >&2 + fi +} + + +## nux.log.level:: +## Sets maximum level of details to be logged. +## +function nux.log.level { + local level=$1 + local level_id=NC_LOG_id_$level + NC_LOG_current=${!level_id} +} diff --git a/inc/nuxr.nuxsh.sh b/inc/nuxr.nuxsh.sh new file mode 100644 index 0000000..8436199 --- /dev/null +++ b/inc/nuxr.nuxsh.sh @@ -0,0 +1,100 @@ + +nux.use nux/color +nux.use nuxr/repl + +@prefix check nux.check. +@prefix help nux.help. + +@namespace nuxr. { + function :run TASK { + if check:function task.$TASK { + nux.log debug "Running task: $TASK"; + task.$TASK "$@" # Runs task + else + echo "$NUX_SCRIPTNAME: Unrecognized task ''$TASK' not available." + echo "Try '$NUX_SCRIPTNAME help' for more information." + return -1 + } + } + + function :run.subtask SUBTASK { + if nux.check.function task.$TASK.$SUBTASK { + nux.log debug "Running subtask: $TASK"; + task.$TASK.$SUBTASK "$@" # Runs task + else + echo "$NUX_SCRIPTNAME: '$TASK' '$SUBTASK' not available." + echo "Try '$NUX_SCRIPTNAME help' for more information." + } + } + + function :main { + :run "$@" + } +} + +@namespace nuxr.task. { + function :help { + nuxr.task.help. "$@" + } +### nuxr.task.interactive:: +### Runs an interactive taskie shell with base taskie commands available. +### + function :interactive() { + nux.use nux.repl + nux.repl.start nuxr.repl.process nuxr.repl.prompt nuxr.repl.completer + } + +} + +@namespace nuxr.task.help. { + + function : { + nux.use nux/help + allArgs="$@" + if [ -z "$allArgs" ] { + echo Usage: $NC_Bold$NUX_SCRIPTNAME ${NC_No}${NC_White}\${NC_No} [\] + help:comment "$NUX_SCRIPT" + help:comment "$NUX_RUNNER" + nux.exec.optional task.help.additional + else + :topic "$@" + } + } + + ## nuxr.task.help.topic + function :topic { + first="$1" + topic="$@" + topic_dot=$(tr " " "." <<< $topic) + nux.log trace "Displaying topic for: '$topic' '$topic_dot'" + + if check:function "task.help.$topic_dot" { + shift; + task.help.$topic "$@"; + elif nux.check.function "task.help.$first" ; then + shift; + task.help.$first "$@"; + else + nuxr.task.help.comment "$NUX_SCRIPT" "$topic" \ + || nuxr.task.help.comment "$NUX_RUNNER" "$topic" \ + || echo "Help topic $1 not found. Run '$NUX_APPNAME help' to see topics." + } + } + + function :comment script task { + local task_dot=$(tr " " "." <<< "$task") + nux.log trace "Trying to figure task documentation location for $task $task_dot" + doc_start=$(grep -hn -E "## +($task)::" "$script" | cut -d: -f1) + code_start=$(grep -hn -E "((function +task.$task_dot)|(task.$task_dot *\(\))) +{" "$script" | cut -d: -f1) + nux.log trace "doc_start" $doc_start $code_start + if [ -n "$doc_start" -a -n "$code_start" ] { + sed -n "$doc_start,$code_start"p "$script" \ + | grep "^\#\#" \ + | sed -re "s/^#+ ?(.*)/\1/gi" \ + | nux.help.shelldoc + return 0 + else + return -1 + } + } +} diff --git a/inc/nuxr/repl.nuxsh.sh b/inc/nuxr/repl.nuxsh.sh new file mode 100644 index 0000000..dfd3f1d --- /dev/null +++ b/inc/nuxr/repl.nuxsh.sh @@ -0,0 +1,134 @@ +# +### +# +# +@namespace nuxr.repl. { + function :process { + backendFunc=task.$command; + if nux.check.function repl.command.$command; then + eval repl.command.$command "$arguments" + elif nux.check.function task.$command; then + eval nuxr.run "$command" "$arguments" + else + echo "$command" is not defined. + fi + } + + function :expose { + # FIXME: Figure different way of exposing direct functions without wrapping + for cmd in "$@"; do + eval "function repl.command.$cmd { $cmd \"\$@\"; }" + done + } + + function :prompt { + echo "${nc_green}$NUX_APPNAME${nc_end}> " + } + +} + +@namespace repl.command. { + ## + ## repl.command.:: + ## fallback command which does nothing if user just presses enter. + ## + function : { + echo >>/dev/null + } + + function :help { + if [ -z "$@" ] ;then + echo "Usage: help [ | ]" + echo Displays help for specified topic or command. + echo + echo "${nc_white}Available topics:$nc_end" + :search.tasks help.$current_word | cut -d"." -f2 | column + echo + echo "${nc_white}Available commands:$nc_end" + :search.tasks $current_word | grep -v "help\\." | column + else + nuxr.task.help.topic "$@" + fi + } + +} + + +@namespace nuxr.repl.completer. { + + function :search.tasks { + set | grep -G "^task\.$1.* ()" \ + | cut -d "." -f2- \ + | cut -d"(" -f1 + } + + + function :search.commands { + set | grep -E "^((repl\\.command)|(task))\\.$1.* ()" \ + | sed -re 's/^((repl\.command)|(task))\.//gi' \ + | cut -d"(" -f1 | sort | uniq + } + + function :help() { + nux.log debug "Help completer" + nux.log debug "current_pos $current_pos" + nux.log debug "current word $current_word" + if [ $current_pos -eq 2 ]; then + :search.tasks $current_word | grep -v "help." + :search.tasks help.$current_word | cut -d"." -f2 + fi + } + + function :_prefix_task() { + nux.log debug "Prefix completer. $current_pos $current_word" + if [ $current_pos -eq 2 ]; then + :search.tasks $current_word + else + nuxr.repl.completer "${line#$command }" + fi + } + + function nuxr.repl.completer { + local line=$1; + nux.log debug "Requested completion for " "'$line'" + + local words=($line) + local current_pos="${#words[@]}"; + local current_word=""; + if [ "$current_pos" -gt 0 ]; then + current_word="${words[${#words[@]}-1]}"; + if [ -n "$line" -a "$line" != "${line%% }" ] ; then + nux.log debug "Creating proposal for next word." + let current_pos=current_pos+1 + current_word="" + fi + fi + local result=""; + if [ $current_pos -le 1 ] ; then + result=$(nuxr.repl.completer.search.commands $current_word | grep -v "help\\.") + elif [ $current_pos -ge 2 ]; then + command="${words[0]}" + nux.log debug "Trying to use completer for '$command'" + case $command in + debug) ;& + trace) + result=$(nuxr.repl.completer._prefix_task) + ;; + *) + result=$(nux.exec.optional nuxr.repl.completer.$command) + ;; + esac; + fi + + if [ -n "$result" ]; then + nux.log debug "Completion found." + echo $result + else + nux.log debug "No completion found." + printf '\a' >> $(tty) + if [ $current_pos -gt 1 ]; then + echo $current_word + fi + fi + } +}