Blog-Thread: Creating a Shell Wrapper and Runtime Modifier (SWARM)

SWARM = Shell Wrapper and Runtime Modifier

But as a matter of fact, it's more like a Text User Interface framework for scripts.
Which was the original title (TUI) of the first split-up iteration from Script-Tools.

Script-Tools is still the 'end/final-project', SWARM is just the way to get there.
And for that project - Script Tools - I'd like to ask to cooperate with others, once SWARM is stable that is.

I've had it (Script-Tools) working with things like: st tweak grub 2 theme or st dev ssh con or even st set lm gdm rndbg .
As you see by the command syntax, it has several sub section, all of which are directories containing directories and files which can be called and arguments/options passed to.

This (functionality of SWARM) allows one to 'sort' one's script into directories and still be able to call them directly.
I belive this is something alot of us want.

Further, I love the console, it's very powerfull, but... also boring in it's appearance.
There's alot of unused space as everything is left oriented, SWARM offers you to output text left, center or right oriented.
And even better yet, it allows for 2 different 'liners' called 'header' and 'title' which bring some (theme'able) colors and designs to the regular terminal.

SWARM also offers 'handlers' for browsers and editors (and more), for both, CLI and GUI applications alike.
So you, as a script author/writer dont have to worry which text editor an enduser has installed.
Further it simplifies repeative tasks like yes/no question and status information wether or not a task has been successfully executed (yesno "question", status $? "text").

So, in an all short saying:
SWARM allows you to easy write application-like scripts.

A comment from a GNU guy i've sent TUI/SWARM to review, sorry lost the email and dont recall the name:
"A very new approach to the console!"

For general feedback, please refer this specific thread:
Feedback wanted for upcoming script language
But of course if we can keep it short, you're welcome to post here too, but I dont want hughe discussions in this thread.

If this sounds interesting to you so far, and if you want to know what kind of challenges I'll be facing, stay tuned :stuck_out_tongue:
But a warning, I've completed all tasks already, what I need to do now is to restructure and rewrite/adjust the code for the new projectname SWARM (runtime only, no install required anymore).

What I want to do in/with this thread:

  • Write my 'todays goal'
  • Write how it went, because it's been 5 years when I last 'viewed' my code.
  • This is to push myself, because redoing what you've already done is.. annoying...

--- Post updated at 19:31 ---

Yesterday I finaly completed the basic loading procedure.
Had an issue with traps, due to blindly copy-paste code without considering from where I copied (a file generating script, thus some variables were escaped, which obviously didnt work in an executed/sourced file).

Traps aside, the Theme procedure works now, at least the Theme-Array contained the required data.

And I learned something new.
If you want to fill a variable/array from a function for the main script, use declare -G varname[=value]

Also some other interesting point, the main reasons I initial had decided to go for an installation was that not all Distro's support "COLUMNS" or "LINES", thus when setting those variables per call, they wouldnt update in a runtime only apporach, but when beeing single files each call will have the updated values, so the aligment of the output is according to the terminal windows dimensions (if called in a GUI).
Now, I do have some more experiences, so it wasnt a problem to simply make it a subshell (is that the proper word?) which will reset those 2 values and export them to the mainscript.

Heck, I've even gave it an option to set the intervall.
Writing this reminds me of something, this value should be 'set'able' from the outside so a script can define how fast the values COLUMS and LINES are beeing updated.
Maybe even a check wether we're in GUI or not.. as for a console (TTY) only, those values wont change, so the intervall could be disabled alltogether...

(this is precicly why I wanted a blog, writing about something helps to find new things, and share new discoveries/learned things)

So much for yesterday.
Today I want to get swarm.print.border done (all swarm.* functions are for internal use only, bash'ism), maybe even the basic printl function.
But boy.... thats 500 lines of code (1 function) to review and adjust, wish me luck :stuck_out_tongue:

4 Likes

Getting somewhere, alot faster and much more elegant (IMO, code wise) than with TUI.

The border, 'is there' - somewhere... the basic text orientation is slowly falling into place...

[sea@manjaro-desktop SWARM]$ time . ./runtime 
........
TODO : swarm.sanity.env
.............
 ----------- TEST AREA -----------
TODO case
# | SWARM 0.1                                                                                  2020.03.12 : 00:33

TODO case
# |                                                   my title                                               | # 

# |                                                                                                               | #
# | left                                               middle                                               right | #
 ----------- TEST AREA -----------

real	0m0.090s
user	0m0.125s
sys	0m0.018s

Incredible, the very first time, it took me WEEKS to get 'here'...
With TUI, it (still) took me DAYS.
Now, so far, it's been a task of just a couple of HOURS! :smiley: :cool:

Though, colors are still missing and the orientations still need to be 'ironed out', but it's getting somewhere :smiley:
And I still have some hours today :wink:

EDIT:
Oh yeah and did I mention that I love traps?
Just pressing CTRL+C in a terminal to stop all subprocess I started is just... awesomely comfortable! :smiley:

[sea@manjaro-desktop SWARM]$ ^Ctodo cleanup
bash: unset: `swarm.*': Ist kein gültiger Bezeichner.

[1]   Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )
[2]   Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )
[3]   Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )
[4]   Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )
[5]-  Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )
[6]+  Getötet                ( while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero; do
    export WIDTH="$(eval $TPUT cols)"; export LINES="$(eval $TPUT lines)"; $doLogExt && init.log "Updating geometrics for SWARM PID: ${PPID:-$PID}"; sleep ${SWARM_INTERVALL_GEOMETRY:-15};
done )

Just saying if anyone thinks of 'dirty-coding' :wink:
-> Yes' I'm aware of that unset issue, I'll take care of that later, that part is not urgent for the mood.
-> Nor is that swarm.sanity.env, as thats for when the very basic core is ready and shall be tested on different OS'

EDIT 2:
lol... do you see the mistake I just seen?
Quite an obvious one... width instead of columns... :doh:

Told you, just a couple of hours. :smiley:
Colors are due tomorrow, with a screenshot as soon it works!

[sea@manjaro-desktop SWARM]$ time . ./runtime 
........
TODO : swarm.sanity.env
.............
 ----------- TEST AREA -----------
# | SWARM 0.1                                                                                  2020.03.12 : 00:57 | #
# |                                                   my title                                                    | #
# |                                                                                                               | #
# | left                                               middle                                               right | #
real	0m0.099s
user	0m0.114s
sys	0m0.035s

Yay, good time to say good night

1 Like

Colors are working now! :slight_smile:
Gave it a shot this afternoon for a few minutes (first image), but since the sun was shining, I decided to not sit in front of the computer :stuck_out_tongue:

So I then continued like 1 hr and 5 minutes ago....

And a a few adjustments later, voila, the result is screenshot:

Allthough I had code written already for that, I could only re-use 2 lines of code, the other 140 lines (total, incl yesterday) were/are freshly written.

I'm so happy, now that the core visuals are done (already!!!), I can focus on the cool part....
Convenience functions, like: ask, choose, edit, progress, download and all the other small things :smiley:

You have NO IDEA how much I MISS my TUI!!
But I force myself to continue SWARM until I can replace all my TUI scripts.

Console/terminal feels sooo 1985 again...

Soon, very soon.. I hope :wink:

EDIT:
And just saying.. colors and borders are THEME'able! :wink:

EDIT2:
But for the roadmap ahead, I'll do these tomorrow:

  • ask "Question?" ; the (yn) will be supported by language file & code!
  • choose $list or "${ARRAY[@]}" ; the select wrapper
  • and if, which probably will be the case, I have enough time, status $? "left text" "center text"

As this would complete what I consider the absolute minimal core requirements of SWARM.

Well, ask "Is this cool?" works now.

 ----------- TEST AREA -----------
# | SWARM 0.3                                                                                  2020.03.13 : 03:38 | #
# |                                                   my title                                                    | #
# | left                                               middle                                               right | #
# | This                                                 is                                                 piped | #
# | Is this cool? (yn)                                                                                          y | #
0
 ----------- TEST AREA -----------

real	0m1.585s
user	0m0.122s
sys	0m0.044s
[sea@manjaro-desktop prjs]$ time . SWARM/runtime 
........
TODO : swarm.sanity.env
.............n
 ----------- TEST AREA -----------
# | SWARM 0.3                                                                                  2020.03.13 : 03:39 | #
# |                                                   my title                                                    | #
# | left                                               middle                                               right | #
# | This                                                 is                                                 piped | #
# | Is this cool? (yn)                                                                                            | #
1
 ----------- TEST AREA -----------

real	0m0.131s
user	0m0.141s
sys	0m0.043s

EDIT: /* Seems I was a bit too fast on pressing 'n' that it was printed during the loading screen, thus not at it's 'regular' position.. but just check the return number :wink: */

It even has a 'fallback' mode, using '+' and '-', just in case anything with possible translations should not work.

And the best part, you dont have to press enter - at least not as long your 'read' does support this feature :smiley:
Well, the 2nd part is what I'm going to adapt now.

But first I'll write a quick 'help handler' and probably prepare a dyanamic 'man(page) handler' as well.

But if you're interest in the BASIC version, it's first iteration (without the actual handling of the different read abilities , here's the code:

	ask() { # "What question?"
	# Ask a yes/no question, returns 0/true for yes and 1/false for no
	# y/n are taken from language file and are used in code
		# Check if 'yesno' is empty, if so, fill with fallback values
		[[ -z "${SWARM_MSG_ASK_YES}${SWARM_MSG_ASK_NO}" ]] && \
			SWARM_MSG_ASK_YES="+" && \
			SWARM_MSG_ASK_NO="-"

		swarm.print.border -e
		# Print the text
		$PRINTF "$(swarm.print.goto $(( ${#SWARM_THEME_DATA[border-left]} + 2  )))$1 (${SWARM_MSG_ASK_YES}${SWARM_MSG_ASK_NO})"
		# Move the cursor
		$PRINTF "$(swarm.print.goto $(( $COLUMNS / 2 * 2 - ${#SWARM_THEME_DATA[border-left]} - 2  )))"
		read -n1 answer
		$PRINTF "$posEND\n"

		case "$answer" in
		"+"|"$SWARM_MSG_ASK_YES")
				return 0
				;;
		"-"|"$SWARM_MSG_ASK_NO")
				return 1
				;;
		*)		return 1
				;;
		esac
	}

Lets share the code as long it's simple :wink:

Currently listening: End of Line by Daft Punk, Tron Legacy OST

Oh what whall I say...
That "-> https://www.unix.com/shell-programming-and-scripting/283945-issue-writing-pipe-new-post.html <-" was really costing my nerves!
Thanks here again to Made In Germany (MIG) for his 'echo wrappers' (and the additional help)!

Luckily at least ask seemed to work 'out of the box' for the pipe tweaks today. :slight_smile:

Allthough the average load & output time was around 0.150 s (0.097-0.210), it recently became 'jumps' up to 0.300 or higher.
As a natural tweaker, I wanted to fix this, one breaks things along the way - which helped to make the load sequence more stable.

As I had prepared a subshell process if no $COLUMNS and no $LINES should be available,
to have them calculated and exported, but since my sytem has them at hand, well, I just assumed the code would work,
since there were subprocess and I had seen with set -x that the code indeed was executed in the shell.

Regardless, the exported variables were not to be found in the shell I sourced the file with the the sub process in from.
Something like this:

[sea/ .bin]$ cat testfile 
(
	declare -g my_test_a="a test"
	export my_test_b="an exported beer"
)
[sea/ .bin]$ . testfile 
[sea/ .bin]$ echo $my_test_a

[sea/ .bin]$ echo $my_test_b

Well, something like this, but with a looped sleep while grep checked the ps output for the $PPID as actual sub process.

That was quite, disencouraging.
This would have been so cool, real multi-tasking in console!
Guess I'm too much of a dreamer :stuck_out_tongue:

Next will be alot of small utility functions, and to finalize the current core commands with 'help' functionality - before adding new core (usualy slitly bigger/ more complex command functions..

1 Like

Hi.

Perhaps I missed something, but variables are created/set when from a function:

#!/usr/bin/env bash

# @(#) s1       Demonstrate use of option "-g" in "declare".

LC_ALL=C ; LANG=C ; export LC_ALL LANG
pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }
em() { pe "$*" >&2 ; }
db() { ( printf " db, ";for _i;do printf "%s" "$_i";done;printf "\n" ) >&2 ; }
db() { : ; }
C=$HOME/bin/context && [ -f $C ] && $C

(
        declare -g my_test_a="a test"
        export my_test_b="an exported beer"
)

unset my_test_a my_test_b
pl " Results, not in function:"
echo $my_test_a
echo $my_test_b

f1 ()
{
    declare -g my_test_a="a test"
    export my_test_b="an exported beer"
}

unset my_test_a my_test_b
pl " Results, in function f1:"
f1
echo $my_test_a
echo $my_test_b

exit

producing:

$ ./s1

Environment: LC_ALL = C, LANG = C
(Versions displayed with local utility "version")
OS, ker|rel, machine: Linux, 3.16.0-7-amd64, x86_64
Distribution        : Debian 8.11 (jessie) 
bash GNU bash 4.3.30

-----
 Results, not in function:



-----
 Results, in function f1:
a test
an exported beer

Seen in man bash :

and noted with shellcheck :

...
In s1 line 21:
echo $my_test_a
     ^-- SC2031: my_test_a was modified in a subshell. That change might be lost. 
...

Apologies if I missed it ... cheers, drl

( currently listening: Bartok: String Quartets, but soon Yes )

1 Like

Thank you drl!
When I execute 'as is' it works - but it isnt quite what I was aiming for..
-> Or I fail to see how to adapt.

But I'm not getting the expected results when I modify your script to my situation:

unset my_test_a my_test_b
pl " Results, in function f1:"
(
 	while true; do
		f1
	done
) &
echo $my_test_a
echo $my_test_b

Output:

[sea@manjaro-desktop SWARM]$ bash ~/drl 

-----
 Results, not in function:



-----
 Results, in function f1:


Now here's my 'case output' , also, note the new time, isnt that great?
Allthough, this is one of the 'better' runs.

Please note the 'echo runs'!

[sea@manjaro-desktop SWARM]$ time . ./runtime 
.............+++ echo runs
+++ set +x
# | SWARM 0.4-7                                                                           | 2020-03-19 / 02:02:55 | #
# |                                                    my title                                                   | #
# | left                                               middle                                               right | #
# |                                                                                                               | #
# |                                                   Piped input                                                 | #
# | Is this cool? (yn)                                                                                            | #
0
# | Is this cool? (yn)                                                                                            | #
1
# |                                                  Piped Output                                                 | #
# | this is                                            a piped                                          test code | #
# | this is                                          an argument                                        test code | #
# | this was                                         an argument                                        test code | #
# | left                                                                                                          | #

real	0m0.047s
user	0m0.043s
sys	0m0.009s
 ----------- TEST AREA -----------

real	0m0.075s
user	0m0.067s
sys	0m0.015s
[sea@manjaro-desktop SWARM]$ echo $my_test_var 

[sea@manjaro-desktop SWARM]$ +++ echo runs
+++ set +x

[sea@manjaro-desktop SWARM]$ echo $my_test_var 

[sea@manjaro-desktop SWARM]$

But the $my_test_var remains empty. (just like in the modified script)

Now here's the code in question:

#
# For easier code reusage, lets combine all math into a sub process
# Should help to speed things up a little more for the end user front ;)
# This seems useless... TODO remove or fix
#
if true
then
	unset "${!len*}" my_test_var
	swarm.geometry.update() { #
		declare -g DATE_TIME=$($DATE +'%H:%M:%S')		# Time on the other hand is

		declare -g  my_test_var="$(date +%T)"

		# Set basic values
		declare -g BORDER_LEFT="${SWARM_THEME_DATA[border-left]}"
		declare -g BORDER_RIGHT="${SWARM_THEME_DATA[border-right]}"
		declare -g clrFRONT="${SWARM_THEME_DATA[color-front]}"
		declare -g clrBACK="${SWARM_THEME_DATA[color-back]}"
		declare -g clrCL="\e[2K"

		# Check if border-right was set:
		[ -z "$BORDER_RIGHT" ] && \
				declare -g BORDER_RIGHT && \
				for((i=${#BORDER_LEFT}-1;i>=0;i--)); do BORDER_RIGHT="$BORDER_RIGHT${BORDER_LEFT:$i:1}"; done

		# Get some numbers
		declare -g lenLeft"${#BORDER_LEFT}"
		declare -g lenRight="${#BORDER_RIGHT}"
		declare -g lenClrFont=${#clrFRONT}
		declare -g lenClrBack=${#clrBACK}
		export identRight=$(( $lenRight + ${#clrCL}  ))
		export numEND=$(( $COLUMNS / 2 * 2 ))

		# If user wants extended logs, he shall have it
		! $isRO && $doLogExt && init.log "$SWARM_MSG_INIT_PID_GEOMETRY: ${PPID:-$PID}"
	}
	export -f swarm.geometry.update

	# Sub shell
	(
		while ${PS:-\ps} ${PPID:-$PID} | ${GREP:-\grep} ${PPID:-$PID} >> /dev/zero
		do
			# Ok, lets keep it simple,
			#DATE_TODAY=$($DATE +'%Y.%m.%d')	# Cmon, day is not THAT important

			swarm.geometry.update
			set -x
			echo runs >/dev/zero
			set +x
			# Using 15 secs for fallback mode
			sleep ${SWARM_INTERVALL_GEOMETRY:-15}
		done
	) & >> /dev/zero
	SWARM_PID_THEME="$SWARM_PID_THEME $!"
fi

Please acknowledge that on first try, I had the function definition inside the subshell.
Since that didnt work out the way I wanted, I moved it 'outside' - to no change.

To me, this still looks like we cannot 'save' a variable from a sub/background process, wether it would be from within a function or not.

EDIT:
Unless I would use a tempfile to write and read the data, but that is something I want to avoid.

As I have a certain way... image... of how I want to use/show help and manpages, and how I want to handle code transparancy, I'm a bit.. focused.. on how the solution should look like.
My current expansion attempts didnt work quiet as I would have hoped.

However, to get something done, I've rearanged some and deleted some other files, as their code was now merged into functions for reusage and just a slightly more 'elegant' way how SWARM works internaly. (read: Fixed my own not-using-an-existing-variable issue)
This said, parts of status work now too.

But I still need to cross-check this with a TTY-only output, as they are (supposed to be) different - as in (translated if required) text only.

We're getting somewhere :smiley:

Just tried it on a TTY and... had to do some fixes... who would have thought that some shells return -bash as their $0 instead of just bash .
After that, it worked!

Though, figued I need to get rid of the underline for the title, as that becomes a 'cyan'ish background - which kinda makes it hard to read that blue text...
And sure as hell is not inteded, I mean, even adding the underline was just a little fun - and I wasnt sure wether I wanted to keep it or not.. this helps :wink:

Just thought I'd share the first image of the TTY test.

But this 'experience' is always kind of a milestone - just to know that it -as it is- works in its basic form for/in both, X and TTY/environments.
Well, once I got rid of that cyan/underline that is... :stuck_out_tongue:

Uh finaly, I have it (printe) as I want it to behave.
A simple change - long intended, that when passing 2 strings, one is left, the other right (instead of center).

It doesnt come as easy as one might think...

	swarm.print.text() { # MODE [ LEFT CENTER RIGHT]
	# This simply puts the text
	# according to MODE
		local fix_title=0
		local fix_header_r=0
		case ${1/-} in
			t)	MODE=title
				fix_title=16	;;
			h)	MODE=header
				fix_header_r=4	;;
			*)	MODE=basic	;;
		esac
		shift

		# Check if it is too short, if so, print fallback mode and return
		[[ ${COLUMNS} -lt 25 ]] && \
				$PRINTF "%s\n" "${@}" && \
				return 1

		# Make sure values are udpated
		swarm.update.geometry

		# Get the actual string
		local evalLeft=$($PRINTF "$1")
		local evalMiddle=$($PRINTF "$2")
		local evalRight=$($PRINTF "$3")

		# Retrieve their length
		local lenEvalLeft=${#evalLeft}
		local lenEvalMiddle=${#evalMiddle}
		local lenEvalRight=${#evalRight}

		# Output
		case ${#@} in
		0)	# Nothing to print
			$PRINTF "${posLEFT}"
			;;
		1)	# Just left oriented
			$PRINTF "${posLEFT}$1"
			#return
			;;
		2)	# 2nd arg is right oriented
			$PRINTF "${posLEFT}$1"
			local posRIGHT="\33[$(( $numEND - $lenMiddle - ${lenEvalMiddle} - 1 + $fix_header_r ))G"
			$PRINTF "${posRIGHT}$2"
			#return
			;;
		3)	# Default handling
			local posMIDDLE="\33[$(( $numHALF - $(( ${#2} / 2 ))  + $fix_title ))G"
			local posRIGHT="\33[$(( $numEND - $lenRight - ${lenEvalRight} - 1 + $fix_header_r ))G"
			$PRINTF "${posLEFT}$1"
			$PRINTF "${posMIDDLE}$2"
			$PRINTF "${posRIGHT}$3"
			;;
		esac

		$PRINTF "${posEND}"
	}
	printe() { # STR1 STR2 STR3
	# Simply prints the strings as passed, it requires 3 strings to use the center
	# For piping, it expects 3 lines, each representing 1 variable
		#set -x
		local MODE="none"
		# Get args
		case "$1" in
		"-1"|"-2")	MODE="${1/-}"
					shift
					;;
		*)			MODE="normal"
					;;
		esac
		# Get arg number if not forced yet
		[[ "normal" == "$MODE" ]] && [[ "-" != "$1" ]] && [[ "--" != "$1" ]] && MODE=${#@}
		# Get pipe?
		case "$1" in
		"--"|"-")
				case "$MODE" in
					"2")
						# Read pipe, expect 2 lines
						while read LEFT
						do
							read RIGHT
							printe "$LEFT" "" "$RIGHT"
						done
						return
						;;
					"1")
						# Read pipe, expect 1 lines
						while read LEFT
						do
							printe "$LEFT"
						done
						return
						;;
					*)
						# Read pipe, expect 3 lines (default)
						while read LEFT
						do
							read CENTER; read RIGHT
							printe "$LEFT" "$CENTER" "$RIGHT"
						done
						return
						;;
					esac
				;;
		esac
		#set +x
		case "$MODE" in
			"0")
				swarm.print.border -e
				swarm.print.text -e "" "" ""
				;;
			"1")
				swarm.print.border -e
				swarm.print.text -e "$1" "" ""
				;;
			"2")
				swarm.print.border -e
				swarm.print.text -e "$1" "" "$2"
				;;
			"3")
				swarm.print.border -e
				swarm.print.text -e "$1" "$2" "$3"
				;;
		esac
		$PRINTF "$posEND\n"
	}

Now comes the really fun part... passed string splitting...
And 'select'.

Also, if you wonder WHY (the heck) I upload so often pictures of SWARM, thats because 'just before' doing that picture, all of the 'UI' was terrible messed up.
So it's kind of: Hey it works (again), and as a reminder which build number I'd have to pick from the backups.
This said, here we go :stuck_out_tongue:

And I just thought, it might be 'easier' to 'make': select = pick (instead of choose) and ask = yesno, because for regular user input, I'd make a function called input.
What do you think?

TODO:

  • Maybe even remove BOLD from title, looks a bit 'light' on TTY (hard to read : try different colors/theme first)
  • Find a non-tempfile using method printing different stages/index of an array (\ | / - \ | )
  • Fiddle my way around an 'intervall based' on UNIX seconds differences between calls from within a function without using tempfiles (alternative to the previous 'multi task' attempt)
  • select
  • edit/web/terminals
  • progress/bar
  • download
  • cfg.get & cfg.set
  • swarm configuration (general purpose *conf file handler, supports in-file-commented options for the handlers)
  • more themes (ideas or wishes, anyone? :wink: )
  • typewriter
  • swarm.bol.dir ?
  • swarm.str.distro ?
  • swarm.install (distro package manager wrapper ; install only)

And these are just whats on top of my mind.
Now, allthough I have most of this "done already" - it's for TUI which was based on individual files, rather than functions - and I'm using new syntax and methods which 'dont help' to reduce 'coding time' but should help with readability and reusability of the code.
If you compare printe , well incl swarm.print.border and swarm.print.text to it's origin tui-echo , it is ALOT more readable!

However, when it was 'all file based', it was simple, all user-commands were files, and had to have args like --help and --version , but with funcions?
Sure, I could do that.
Issue is, I want it translate-able.. by which I mean, people should be able to do that 'quickly' as part of a hobby or enhusiastic week.
Thus, I only want to focus on CORE (end-user) functions (that other script authors are supposed to be using - only - I know, wont happen).

Thanks to the syntax rules I had set within my project, I could already parse current files functions and list their description (First 2 commented lines after the function definition, which as well provides the ARGS syntax, where applicable :wink:

	swarm.eu.function.list() { # [FILE]
	# Prints a list of properly declared functions
	# Please see: ./docs/{MANUAL,SYNTAX}.md)
		local tmp_list=""
		local tmp_oldpwd="${PWD:-$($PWD_EXEC)}"

		if [[ -n "$1" ]] && [[ -f "$1" ]]
		then	# There is a specific file passed to parse
				cd "$SWARM_DIR_LIBS"
				$GREP "() { #" "$1"| \
					$GREP -v GREP | \
					$AWK -v FS='() ' '{print $1}' | \
					$SED s,'()','',g
				cd "$tmp_oldpwd"
		else	# Just parse all files in SWARM_DIR_LIBS
				raw_output() {
					(
						cd "$SWARM_DIR_LIBS"
						$GREP init.*"() {" * | $GREP -v GREP
						$GREP cfg.*"() {" * | $GREP -v GREP
						$GREP swarm.*"() {" * | $GREP -v GREP
						cd "$tmp_oldpwd"
					) | while IFS=": " read one two three
					do
						# The IFS takes care of the GREP filenames
						# and this variable-regex takes care of the function definition
						echo "${two/()}"
					done
				}
				# Show data
				raw_output | sort -u
				unset -f raw_output
		fi
	}
	swarm.eu.function.show() { # MODE [FILE] FUNCTIONNAME		## MODE= --text || --code
	# Prints either these 2 comment lines or the code
	# Please see: ./docs/{MANUAL,SYNTAX}.md)
		#
		# Vars
		#
			local tmp_oldpwd="${PWD:-$($PWD_EXEC)}"
			local GREP_OPTS=""
		#
		# Check for args
		#
			local MODE=none
			case "$1" in
				"--text")
					MODE=text
					shift
					;;
				"--code")
					MODE=code
					shift
					;;
				*)
					MODE=text
					#return 1
					;;
			esac
			[[ -f "$1" ]] && \
				local curFILE="$1" && shift || \
				local curFILE=""
		#
		# Code
		#
			# Prepare command
			case "$MODE" in
				"text")
					GREP_OPTS="-h -a2  ${1}"
					cd "$SWARM_DIR_LIBS"
					# This is just to reduce unrequired disk usage
					[[ -n "$curFILE" ]] && \
						$GREP $GREP_OPTS "$curFILE"|| \
						$GREP $GREP_OPTS *
					cd "$tmp_oldpwd"
					;;
				"code")
					type "$1"
					;;
			esac
	}

Though, the *show function still needs some tweaking on the --text handling.

It's funny, I know how cool it is when it's done, but still I'm kind of overhelmed by the annoyance of 'reinventing the wheel' (redo what's already done).
And that is despite the fact, that I get alot done, alot quicker than I had anticipated - with alot more tweaks than it used to have.

If I had done this like 20 or 25 years ago.. oh boy this would have been THE invention of the decade. (speaking of how 'swarm' (function, aka tui-browser) will behave when all is done and works properly)
Now it's just some enthusastic old reto fanboy hobby project.

Oh geez, guess I have one of my moments, as I was just preparing some post offline, lots of text to be structured before actual posting it.
Well, while I prepared that text - regarding (my hopes for) the community-project Script-Tools, I had figured how much work there's still to do.

If I could briefly describe what swarm does, I would.
And that part I could describe in short, doesnt nearly reflect its full potential!

Once I gotten that far, I'll need to do a good video (with actual editing and voice-overs) to show at least parts of it's full power.
Because just describing is WAY too abstract, even for me reading my own text - despite knowing what I'm refering to...
Yet it's all very simple and almost obvious - at least I tried to achieve this for me.

But I'm already thinking ahead of time.
Stay focused, here and now.

Oh boy, I cant wait until I have all basics covered.
Yeah that help thing....
Since no install, no need to adjust the manpages I'd say, dont you?
So just basic --help coverage for the intended 'user'/author functions usage, sounds about right'ish, right? :wink:

Previously
Still writing and ironing out the default core display functions.
This includes reducing code that was written (at least) twice and could be 'combined' to a single function.

But mostly - as for optimizing code - I try to find a proper way around the the injection protection and 'bold' or colored strings that one might want to use/display.
At the very least so that the function title prints bold text.

The injection protection is not based on my skillset, however, I can read a case and some basic regex so I saw/knew that the console code for 'bold' causes an 'issue', which it did.

Next on my wish todolist is pick and cfg.set .
And I also need to finish bol2str in order to start with swarm .
I need to get the (basic, at least) swarm function done before the config editor - just so I can start scripting other things properly, and do 'external end-user' testing and so I can keep the code cleaner.
Also that will help to keep motivation up.

Current question...
However, for my current task at hand, the read wrappers, yesno , pick and input were, well - still are, based on the physicaly installed read I had to actualy parse the --help output of it to get its functionality to get them work poperly (for TUI).
Now for SWARM I'd like to reduce physical disk usage (files) to a minimum (for non-log actions; setting).

This and the recent Heureka that there's a builtin read, made me hope that the bash builtin read might have the same functions across all distros - as long the propper BASH version is installed, which would simplify the 'wrapping' process.... alot...

How to provide help?
Also the 'help' functionality of SWARM is slowly evolving in my head.
Main challenge here is that I want to distinct functions (for the help usage) between 'basics' and 'advanced', while keep most of it 'dynamic' to use - yet simple for translating.
Because I dont WANT to write actual help text for functions, because to get those, you would need to source SWARM and then call the function with --help, which seems 'too much' for me.

So I'd rather write/modify the (former) manpages and write a handler to work like:

./SWARM/runtime help [topic]

So you can 'stay' in your current project but still get the information you need.

That's for now, have a nice weekend and stay healthy everyone!

My 'recent' speed increase commit, like 6 actual commits back, broke the actual creating of the user config file, which I didnt realize back then because I had forgotton to delete said file.
Next is the creating of the/a RAMDISK for RO systems, figured I dont need to copy SWARM over there, I just need to 'map' the semi-required files there.

With this said, I had lost track of the RO systems handling, allthough prepared....
Also I've copied to code to a laptop to work on that or a bit, which broke - for some weird reason - the application detection using which , allthough which is installed as well...
So, in a way, I'm back to square one again.

But that was to be expected after several years not using gnu+linux.
Gives me a chance to simplify and unify the 'init' procedure some more, hopefully :wink:

Made some basic utilites for cross-plattform usage, as well as for unified and simplified usage.
Reused and wraped RudiC's echo_ , thanks again :slight_smile:

#
# Some tools
#
	swarm.util.isRoot() { #
	# Returns TRUE if user ID is 0
	# Returns FALSE otherwise
		if [[ ${UID:-${EUID:-0}} -eq 0 ]]
		then	$ECHO true
			return 0
		else	$ECHO false
			return 1
		fi
	}
	swarm.util.isGUI() { #
	# Returns TRUE if XDG_CURRENT_DESKTOP, DESKTOP_SESSION or XAUTHORITY is set
	# Returns FALSE otherwise
		if [[ -n "${XDG_CURRENT_DESKTOP:-${DESKTOP_SESSION:-$XAUTHORITY}}" ]]
		then	$ECHO true
			return 0
		else	$ECHO false
			return 1
		fi
	}
	swarm.util.mkdir() { # /path/to/make
	# Creates full path structures
	# 
		${isRO:-false} && return 1
		# Remove any possible args from ARGS
		while [[ "-" == "${1:0:1}" ]] ; do shift ; done
		swarm.protect "$FUNCNAME" "${@}" && return 1
		# Vars
		local dir_done=""
		local dir_todo=$($PRINTF "${1:1}" | $SED s,"/","\n",g)
		# Start pipe
		$PRINTF '%s\n' "$dir_todo" | while read current
		do	# Prepare next dir
			dir_done+="/$current"
			# Create if not exist
			[[ -d "$dir_done" ]] || $MKDIR "$dir_done"
		done
		return 0
	}
	swarm.util.echo() { # [-e|-n|-en|-ne] STRING
	# Simple echo's, by RudiC @ unix.com
	#
		echo_(){
		  ( IFS=" "; $PRINTF "%s\n" "$*" )
		}
		# Portable echo -n
		echo_n() {
		  ( IFS=" "; $PRINTF "%s" "$*" )
		}
		# Portable echo -e
		echo_e() {
		  ( IFS=" "; $PRINTF "%b\n" "$*" )
		}
		# Portable echo -ne
		echo_ne() {
		  ( IFS=" "; $PRINTF "%b" "$*" )
		}
		
		case "${1/-}" in
		"n")	shift ; echo_n "${@}"	;;
		"e")	shift ; echo_e "${@}"	;;
		"en"|"ne")
			shift ; echo_ne "${@}"	;;
		*)		echo_ "${@}"	;;
		esac
	}
	swarm.util.isDir() { #  /path/to/dir
	# Returns true if passed string is a directory
	#
		[[ -d "$1" ]] 
		return $?
	}
#
# Short variable access to util functions
# and to keep file usage to a minimum
#
	isRoot=$(swarm.util.isRoot)
	isGUI=$(swarm.util.isGUI)
	isDir="swarm.util.isDir"
	MKDIR="swarm.util.mkdir"
	ECHO="swarm.util.echo"

But currently I'm still busy in re-thinking/doing the init procedure....
Well, unifying (is that a word??) the procedure of using functions to do different checks, keep/put all variables to RAM while put some essential ones (usage speed increase / customize options) in the 'rc' file.

The specific part of preparing configuration files & folders that are optional - because it must run on a Read-Only system as well, is quite more challenging than making it just in a "must-be-installed" way.
Also, with my... crazy... idea of implementing an optional ramdisk support (as root, for configuration and tempfiles only) didnt make the init procedure any easier.
Further, I need to 'bypass' the (so far hardcoded) use of /root because not all systems have it, thus I have to place the configfile (swarmrc) in another place, like using '/etc' if root has no $HOME.

Allthhough I havent had SWARM working for what feels like 2 weeks, I believe it's going to get alot more robust than any previous working versions.

This said, what always bothered me, was that which is not installed on systems by default, thus I had to work around it.
I used to do that by just call any expected app-name with the --help argument, figured, that it is not reliable.

So this time around, I wrote a which 'handler', if it isnt installed:

	swarm.util.which() { # COMMAND [CMD1 CMD2 ....]
	# Returns 'path/cmd' and true if COMMAND was found in PATH
	# This function is assigned to WHICH - IF no 'which' was found.
		swarm.protect "$FUNCNAME" "${@}" && exit 1
		# Get an arry
		local array=($($PRINTF "$PATH"|$SED s,':','\n',g))
		# Parse for args
		case $# in
		0)	swarm.util.usage swarm.util.which ;;
		1)	case "$1" in
			"-"|"--")
				while read cmd
				do
					$FUNCNAME "$cmd"
				done
				return 0
				;;		
			# *)	continue	;;
			esac
			;;
		*)	for arg in "${@}"
			do
				$FUNCNAME "$arg"
			done
			return 0
			;;
		esac
		# Parse for application
		for Arr in "${array[@]}"
		do
			[[ -f "$Arr/$1" ]] && \
				$PRINTF '%s\n' "$Arr/$1" && \
				return 0
		done
		return 1
	}

Further, I've written the edit function, which will open a different editor for GUI or TTY envornments.
Tough, still need to add language strings for the first time selection.

Hope I cant stay focused on cfg.set today.
Challenging part is to keep (or set) identions, force or remove quotes of the values and to force lower or upper case for the variable names.

cfg.get is working nice already :slight_smile:

[~/prjs/SWARM] 1 $ time cfg.get ~/.bashrc PS1
[\w] \$? \\$ 

real	0m0.003s
user	0m0.003s
sys	0m0.001s
[~/prjs/SWARM] 0 $ 

And pick the select emulator, but for that I'd like to make a list function first.

Once that's done I'll focus on getting the init working again...
Though, I can not test most of the functions just yet, as SWARM is not working during the 'init procedure' change.

Runtime + Cross-Platform = Headache

Heading scripting now, happy easter and stay healthy

Oh well, I could not focus on cfg.set , but I did complete pick yesterday.
And, I get SWARM loaded again, that is something.. allthough not yet properly.

[~/prjs/SWARM] 2 $ time  ./runtime 
.........SWARM 0.4-16
 
  | 2020-04-14 / 09:55:54\033[0m


\033[7mA short intro\033[0m


TODO: Visuals, then explain, configure and stuff...



one


                                                                                                            [  ✓   ]

two

                                                                                                            [  �-   ]



                                                                                                            [  �-   ]



                                                                                                            [  �-   ]

real	0m0.129s
user	0m0.122s
sys	0m0.097s

As you 'see', there are no borders, colors are missing as well and orientation only works partialy (left / right)...
Also thanks to some 'manual debug code' (echo "blabla" >&2) I did figure that I accidently invoked 1 function 3 times (caused by the re-structuring) as that function checks/prepares some environment variables.

The current most annoying part is, well the next 'challenge', to get each output on a single line again, as currently each output uses 3 lines....
Kinda makes me think of pipes beeing the cause, since those require 3 lines....

I hope that while getting the output on a single line, the other escape codes are going to get properly interpreted again as well.

Hope dies last, but having such issues 'all the time' is energy- and motivation consuming...

Some quick positive update - and a question:

The config file is written properly now (again).
For some reason (order), I had an issue to properly identify curl/wget and more/less, for the latter it used the fallback value 'cat'- while both, less and more were installed.. OMG...
Well, it all (that part) works now again as expected. :slight_smile:

What currently confuses me, that it DOES print output (text), but neither swarm.print.border nor swarm.print.text seem to actualy get called...
By which I mean, they both SHOULD call swarm.update.geometry around which I placed set -x and set +x accordingly, yet, I dont see that part....

	swarm.print.border() { # MODE
	# Prints basic border for all lines, this should only be executed if COLUMNS > 25
	# The calling function has to handle the NEWLINE required by 'printe' (-E)
		swarm.protect "$FUNCNAME" "${@}" && exit 1
		# Makes no sense to print visuals on such short lines
		[[ ${COLUMNS} -lt 25 ]] && return 1

		local MODE="" #name=""
		case "${1/-}" in
		e|p)	MODE="basic"	;;
		h)		MODE="header"	;;
		t)		MODE="title"	;;
		esac

		# Make sure values are udpated
		set -x
		swarm.update.geometry
		set +x

		# Now prepare the seperate handling
		case "$MODE" in
		"basic")
			PRINT_LEFT_IN="$c_reset"
			PRINT_LEFT_OUT="${clrCL}${c_front}${c_back}"
			PRINT_RIGHT_IN="${c_front}${c_back}"
			pos_cor_num=0
			;;
		"header")
			local filler_num=$(( $numEND - $(( ${#BORDER_RIGHT} * 2 )) + 2 ))
			local filler_str=$($PRINTF "%*s" ${filler_num})
			PRINT_LEFT_IN="$filler_str" #$c_reset"
			PRINT_LEFT_OUT="${clrCL}${c_front}${c_back}"
			PRINT_RIGHT_IN="" #${c_front}${c_back}"
			pos_cor_num=0
			;;
		"title")
			local filler_num=$(( $(( $COLUMNS / 2 * 2)) - $(( ${#BORDER_RIGHT} * 2 )) + 2 ))
			local filler_str=$($PRINTF "%*s" ${filler_num})
			PRINT_LEFT_IN="${c_invert}$filler_str" #$c_reset"
			PRINT_LEFT_OUT="${clrCL}${c_front}${c_back}"
			PRINT_RIGHT_IN="${c_reset}${c_front}${c_back}"
			pos_cor_num=0
			;;
		esac

		# Get some numbers
		local identRight=$(( $lenRight + ${#clrCL}  ))
		local posLEFT="\33[$(( ${#BORDER_LEFT} - 2 ))G"
		local posRIGHT="\33[$(( $numEND - $lenRight - $pos_cor_num ))G"

		# Well, left aligned it works. isnt too hard
		$PRINTF "\r${PRINT_LEFT_OUT}${posLEFT}${BORDER_LEFT}${PRINT_LEFT_IN}" >&2

		# The right side is much more challenging
		$PRINTF "${posRIGHT}${PRINT_RIGHT_IN}${BORDER_RIGHT}${PRINT_RIGHT_OUT}${c_reset}" >&2
		$PRINTF "${posEND}"
	}

	swarm.update.geometry() { #
	echo " ------------- " >&2
		export COLUMNS="$($TPUT cols)"
		export LINES="$($TPUT lines)"

		# Set basic values
		export BORDER_LEFT="${SWARM_THEME_DATA[border-left]}"
		BORDER_RIGHT="${SWARM_THEME_DATA[border-right]}"	# Do not export just yet
		export clrFRONT="${SWARM_THEME_DATA[color-front]}"
		export clrBACK="${SWARM_THEME_DATA[color-back]}"

		# Check if border-right was set:
		[ -z "$BORDER_RIGHT" ] && \
				for((i=${#BORDER_LEFT}-1;i>=0;i--)); do BORDER_RIGHT="$BORDER_RIGHT${BORDER_LEFT:$i:1}"; done
		export BORDER_RIGHT

		# Get some numbers
		export lenLeft="${#BORDER_LEFT}"
		export lenRight="${#BORDER_RIGHT}"
		export lenClrFont=${#clrFRONT}
		export lenClrBack=${#clrBACK}
		export identRight=$(( $lenRight + ${#clrCL}  ))

		# Pre-Calculate positions
		export numHALF=$(( $COLUMNS / 2 ))
		export numEND=$(( $numHALF * 2 ))
		export posEND="\33[${numEND}G"
		# swarm.print.border still needs extra handling for posLEFT
		export posLEFT="\33[$(( $lenLeft + 2 ))G"

		# Set colors
		export c_front=$(swarm.color.fg ${SWARM_THEME_DATA[color-front]})
		export c_back=$(swarm.color.bg ${SWARM_THEME_DATA[color-back]})

		# If user wants extended logs, he shall have it
		! $isRO && $doLogExt && init.log "$SWARM_MSG_INIT_PID_GEOMETRY: ${PPID:-$PID}"
	}
	export -f swarm.update.geometry
[~/prjs/SWARM] 0 $ time  ./runtime  
.........SWARM 0.4-17
 
  | 2020-04-14 / 12:54:56\033[0m


\033[7mWelcome to a short introduction\033[0m


SWARM is not supposed to be called like this.



Instead it is to be sourced by your script to get access to it's functions.




\033[7m\033[0m


When calling SWARM like you just did, you should provide arguments.
They are:
./runtime config

./runtime help
./runtime tarball


one


                                                                                                            [  ✓   ]

two

                                                                                                            [  �-   ]

real	0m0.079s
user	0m0.048s
sys	0m0.034s
[~/prjs/SWARM] 0 $ 

So, usualy, I would expect some ++ lines showing the working code, and the output of: echo " ------------- " >&2 from the update.geometry function itself...
But as you see, there is nothing.

Please, anyone has an idea as of: why?

Thank you in advance

After some blind tryouts, like sourcing the SWARM/runtime and THEN use type swarm.border.print , those code-debug preparations I did, it actualy got executed...
Upon the type that is...
Like, WTF!?

Anyhow, that helped me to figure out that the swarm.update.geometry probably failed to load, due to an empty $TPUT variable.
That obviously raised the error of unkown command 'cols' by the code COLUMNS=$( $TPUT cols ) .
Upon changing that to ${TPUT:-\tput} , it failed due to unkown programm \tput , while that worked as expected in the regular command prompt.

So what I did to solve this?
I've export 'ed all commandname-variables (first tested with 'TPUT=tput' only) to avoid such a behaviour in the future.
Strange/Weird...

So, now I have colors and some sort of orientation back, kinda....

EDIT:

Weird behaviour.... first 'part' was regular executed script.
Luckily... Been there, done that... it is a 'load order' issue, I just need to figure the NEW loadorder - allthough that should not have had changed... grml...
Maybe figuring the loader order issue might also solve my missing borders and those weird symbols issues...
Lets hope :slight_smile:

Yeehhaaa...
Well.. it was NOT a load order issue, but something simpler....
I still had export -f swarm.update.geometry set in the file, which seems to have been the cause of those errors.
Also, for some reason, I did had declare -AG <VAR> rather than declare -Ag <VAR> .

With those 2 issues solved, it finaly works again! :slight_smile: :smiley:

Just 7 hrs... If I was employed, I would call it a day now :stuck_out_tongue:

Yesterday late evening, I've figured that, yes have the function pick , but it wasnt complete.
So I started working on that again.

I now 'remember' why I waited so long during TUI with the select emulator/wrapper...
Allthough it is quite simple, as soon you start implementing options, things get complicated....

For example, regular grep tasks, suddenly become false positives, thus making it impossible to properly parse the users input.

I'm obviously too stupid to get it working as 'runtime'....
Start from (what feels like) square 1 on a daily basis is just toooooooo much furstrating....
Specialy if you thought you had it just fixed... and it was working... for 24 hrs...

Few hours ago, I was thinking I could ask some people to test it - once I would have 'pick' working...
Now.. nothing works... again...

SCREW IT.....
To quote iron maiden.... Wasted Years....
I'm obviously too stupid to create such an application....
I mean, i'm just a hobby linux enduser... all my linux-know-how is due to TUI/SWARM... which ... obviously isnt enough to make/complete said project..