Community discussions

MikroTik App
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

$INQUIRE - prompt user for input using arrays +$CHOICES +$QKEYS

Tue Jul 11, 2023 12:51 am

There is a node.js/JavaScript project called "inquirer.js" that takes some JSON with some "questions"/"prompts", and returns the calling JavaScript an JS object with all the answers. It's pretty handy for getting user input from the terminal. See https://github.com/SBoudrias/Inquirer.js.

So my $INQUIRE function is poor-man port of Inquirer.JS for RouterOS script. The RSC $INQUIRE function code uses the nifty "inline function" syntax e.g. the "op type" (>[]) to emulate JavaScripts callbacks (see viewtopic.php?t=170591&hilit=op#p1008177)

To use the $INQUIRE function, you can create an "array-of-arrays" with the questions. Specifically the outer array is just a list of questions, with each element being an associative array with some value $INQUIRE uses to build/validate/return the prompts.

For example, if you define $myquestions like so...

example user prompts... code

:global myquestions { 
    {   text="What is your name?"; 
        defval="";
        validate="str" 
        min=0;
        max=16;
        key="name"
    };
    {   text="What is your favorite number?"; 
        defval="42"; 
        validate="num";
        min=0;
        max=100;
        key="favnum"
    };
    {   text="Pick a random IPv4 address..."; 
        defval="$[:rndnum from=1 to=254].$[:rndnum from=0 to=255].$[:rndnum from=0 to=255].$[:rndnum from=0 to=255]"; 
        validate="ip"
        key="rndip"
    }
}

Assuming the $INQUIRE function and $myquestions are loaded, it works like this...

Image



$INQUIRE can also take "callback function" to pretty print response and skip array output with "as-value"...
$INQUIRE $myquestions (>[:put "$($1->"name")'s favorite number is $($1->"favnum")"]) as-value

Or using the same $myquestions from above, it can be stored as an array for later use:
:global myanswers [$INQUIRE $myquestions as-value]
:put ($myanswers->"name")

More complex example... Here we ask the user to confirm (or change) the RouterOS "system identity". As shown below, the questions themselves can be provided directly on the function.
This one shows the use of an "action=" that get called after each question (if defined), with $0 being the "answer" given. Also the "validator=" is an "inline function" here, so any "custom validator" can be used, if "true" is returns it mean input was okay, otherwise a string error can be returned (which is displayed to the user & user is re-prompted the same question until input is valid).
$INQUIRE ({
    {   text="Router name:"; 
        defval=(>[:return "$[/system/identity/get name]"]); 
        validate=(>[:if ([:tostr $0] ~ "^[a-zA-Z0-9_\\-]*\$" ) do={:return true} else={:return "invalid name"}]);
        action=(>[/system/identity/set name=$0]);
        key="sysid"
    }}) (>[:put "New system name is: $($1->"sysid")"]) as-value
It's important to understand that action=, defval= and validate= are "inline functions" (>[]) which are called *dynamically* during user prompting. So the default value is obtained each time $INQUIRE is called, not just when the "questions array" is defined.


Anyway, enough examples. The needed code below can be loaded by cut-and-paste initially, or via /system/script. Once loaded, you can use $INQUIRE to invoke with an array defined like the "myquestions" above...

INQUIRE function code


# $INQUIRE - prompt for values using array with questions
# usage:
#    $INQUIRE <question_array> [<callback_function>] [as-value]
# returns:  associative array with key= as the index, and answer as value 
# param - <question_array>: index array, containing one or more associative arrays e.g. { 
#    {   text=<str>;    #question to ask
#        [key=<str>];   #key in returned $answer
#        [defval=<str|num|op|function>];   #default value, default is ""
#        [action=<op|function>];           #function to call after validated input
#        [validate=<op|function|"str"|"num"|"ip">];  #perform validation
#        [min=<num>]; #min num or string length
#        [max=<num>]  #max num or string length
#    }
# }
# param - <callback_function>: called after ALL questions have been asked 
#                              with $1 arg to function containing all answers/same as return
# param - as-value: if not provided, the answers will be output in as an array string
:global INQUIRE do={
    # store questions/prompts as $qr
    :local qr $1

    # variable to store answers to return
    :local answers [:toarray ""]
    
    # use array to map "ASCII code" (e.g. num type) to a "char" (e.g. str type of len=1)
    :local "asciimap" {
        "\00";"\01";"\02";"\03";"\04";"\05";"\06";"\07";"\08";"\09";"\0A";"\0B";"\0C";"\0D";"\0E";"\0F";
        "\10";"\11";"\12";"\13";"\14";"\15";"\16";"\17";"\18";"\19";"\1A";"\1B";"\1C";"\1D";"\1E";"\1F";
        "\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\2A";"\2B";"\2C";"\2D";"\2E";"\2F";
        "\30";"\31";"\32";"\33";"\34";"\35";"\36";"\37";"\38";"\39";"\3A";"\3B";"\3C";"\3D";"\3E";"\3F";
        "\40";"\41";"\42";"\43";"\44";"\45";"\46";"\47";"\48";"\49";"\4A";"\4B";"\4C";"\4D";"\4E";"\4F";
        "\50";"\51";"\52";"\53";"\54";"\55";"\56";"\57";"\58";"\59";"\5A";"\5B";"\5C";"\5D";"\5E";"\5F";
        "\60";"\61";"\62";"\63";"\64";"\65";"\66";"\67";"\68";"\69";"\6A";"\6B";"\6C";"\6D";"\6E";"\6F";
        "\70";"\71";"\72";"\73";"\74";"\75";"\76";"\77";"\78";"\79";"\7A";"\7B";"\7C";"\7D";"\7E";"\7F";
        "\80";"\81";"\82";"\83";"\84";"\85";"\86";"\87";"\88";"\89";"\8A";"\8B";"\8C";"\8D";"\8E";"\8F";
        "\90";"\91";"\92";"\93";"\94";"\95";"\96";"\97";"\98";"\99";"\9A";"\9B";"\9C";"\9D";"\9E";"\9F";
        "\A0";"\A1";"\A2";"\A3";"\A4";"\A5";"\A6";"\A7";"\A8";"\A9";"\AA";"\AB";"\AC";"\AD";"\AE";"\AF";
        "\B0";"\B1";"\B2";"\B3";"\B4";"\B5";"\B6";"\B7";"\B8";"\B9";"\BA";"\BB";"\BC";"\BD";"\BE";"\BF";
        "\C0";"\C1";"\C2";"\C3";"\C4";"\C5";"\C6";"\C7";"\C8";"\C9";"\CA";"\CB";"\CC";"\CD";"\CE";"\CF";
        "\D0";"\D1";"\D2";"\D3";"\D4";"\D5";"\D6";"\D7";"\D8";"\D9";"\DA";"\DB";"\DC";"\DD";"\DE";"\DF";
        "\E0";"\E1";"\E2";"\E3";"\E4";"\E5";"\E6";"\E7";"\E8";"\E9";"\EA";"\EB";"\EC";"\ED";"\EE";"\EF";
        "\F0";"\F1";"\F2";"\F3";"\F4";"\F5";"\F6";"\F7";"\F8";"\F9";"\FA";"\FB";"\FC";"\FD";"\FE";"\FF"
    }

    # some ANSI tricks are used in output to format input and errors
    :local "ansi-bright-blue" "\1B[94m"
    :local "ansi-reset" "\1B[0m"
    :local "ansi-dim-start" "\1B[2m"
    :local "ansi-dim-end" "\1B[22m"
    :local "ansi-clear-to-end" "\1B[0K"

    # main loop - ask each question provided in the $1/$qr array
    :for iq from=0 to=([:len $qr]-1) do={
        # define the current answer and use "defval" to populate
        :local ans ($qr->$iq->"defval")
        # if "defval" is inline function, call it to get default value
        :if ([:typeof $ans] ~ "op|array") do={
            :set ans [$ans ($qr->$iq)]
        }
        # ask the question, using an default in $ans
        :put "  $($qr->$iq->"text") $($"ansi-bright-blue") $ans $($"ansi-reset") "
        # last char code received
        :local kin 0
        # keep looking for input from terminal while $inputmode = true
        :local inputmode true
        :while ($inputmode) do={
            # re-use same terminal line
            /terminal cuu
            # get keyboard input, one char
            :set kin [/terminal/inkey]
            # if NOT enter/return key, add char to the current answer in $ans
            :if ($kin != 0x0D) do={
                # use ascii map to convert num to str/"char"
                :set ans "$ans$($asciimap->$kin)"
            } else={
                # got enter/return, stop input
                :set inputmode false
            }
            # if backspace/delete, remove the control code & last char
            :if ($kin = 0x08 || $kin =0x7F) do={
                :set ans [:pick $ans 0 ([:len $ans]-2)]
            }
            # assume input is valud
            :local isvalid true
            :local errortext ""
            # unless validate= is defined...
            # if validate=(>[]) is inline function
            :if ([:typeof ($qr->$iq->"validate")] ~ "op|array") do={
                # call question's validator function
                :set isvalid [($qr->$iq->"validate") $ans]
            }
            # if validate="num", make sure it a num type
            :if (($qr->$iq->"validate") = "num") do={
                # see if casting to num is num
                :if ([:typeof [:tonum $ans]] = "num") do={
                    # store as num type
                    :set ans [:tonum $ans] 
                    # valid so far
                    :set isvalid true
                    # if a min= is defined, check it
                    :if ([:typeof ($qr->$iq->"min")] = "num") do={
                        :if ($ans>($qr->$iq->"min")) do={
                            :set isvalid true
                        } else={
                            :set isvalid "too small, must be > $($qr->$iq->"min") "
                        }
                    }
                    # if a max= is defined, check it
                    :if ([:typeof ($qr->$iq->"max")] = "num") do={
                        # if already invalid, use that text first e.g. too small
                        :if ($isvalid = true) do={
                            :if ($ans<($qr->$iq->"max") && isvalid = true) do={
                                :set isvalid true
                            } else={
                                :set isvalid "too big, must be < $($qr->$iq->"max") "
                            }
                        }
                    }
                } else={
                    :set isvalid "must be a number"
                }
            }
            # if there is min= or max= but no validate=, assume validate str lengths
            :if ([:typeof ($qr->$iq->"validate")] ~ "nil|nothing") do={
               :if (([:typeof ($qr->$iq->"min")] = "num") || ([:typeof ($qr->$iq->"max")] = "num")) do={
                  :set ($qr->$iq->"validate") "str"
               }
            }
            # if validate="str", make sure it's a str type
            :if (($qr->$iq->"validate") = "str") do={
                :if ([:typeof [:tostr $ans]] = "str") do={
                    # save answer as str 
                    :set ans [:tostr $ans] 
                    :set isvalid true
                    # if min=, check length in range
                    :if ([:typeof ($qr->$iq->"min")] = "num") do={
                        :if ([:len $ans]>($qr->$iq->"min")) do={
                            :set isvalid true
                        } else={
                            :set isvalid "too short, must be > $($qr->$iq->"min") "
                        }
                    }
                    # if max=, check length in range
                    :if ([:typeof ($qr->$iq->"max")] = "num") do={
                        :if ($isvalid = true) do={
                            :if ([:len $ans]<($qr->$iq->"max")) do={
                                :set isvalid true
                            } else={
                                :set isvalid "too long, must be < $($qr->$iq->"max") "
                            }
                        }
                    }
                } else={
                    :set isvalid "must be a string"
                }
            }
            # if validate="ip", make sure it valid IP address
            # note: IPv6 is not supported
            :if (($qr->$iq->"validate") = "ip") do={
                # make sure it's num.num.num.num BEFORE using :toip to avoid .0 being appended
                :if ($ans ~ "^[0-9]+[\\.][0-9]+[\\.][0-9]+[\\.][0-9]+\$") do={
                    # then check it parsable using :toip
                    :if ([:typeof [:toip $ans]] = "ip") do={
                        :set ans [:toip $ans] 
                        :set isvalid true
                    } else={
                        :set isvalid "IP address not valid"
                    }
                } else={
                        :set isvalid "bad IP address format, must be x.y.z.a"
                }
            }
            # if answer is valid, store it in the $answers array
            :if ($isvalid = true) do={
                # if a key="mykeyname" is used, that becomes the key in array map
                :if ([:typeof ($qr->$iq->"key")] = "str") do={
                    :set ($answers->"$($qr->$iq->"key")") $ans
                } else={
                    # otherwise the key in returned array map is "promptN" where N is index
                    :set ($answers->"prompt$iq") $ans 
                }
                :set errortext ""
            } else={
                # if no valid... report the error, and continue input mode
                :set errortext $isvalid
                :set inputmode true
            }
            # finally output the question, using ANSI formatting
            :put "  $($qr->$iq->"text") $($"ansi-bright-blue") $ans $($"ansi-reset") $($"ansi-dim-start") $errortext $($"ansi-dim-end") $($"ansi-clear-to-end")"
            # if action= is defined & validated - call the action
            :if ($kin = 0x0D && isvalid = true) do={
                :if ([:typeof ($qr->$iq->"action")] ~ "op|array") do={
                    [($qr->$iq->"action") $ans]
                }
            }
        }
    }
    # end of questions

    # if 2nd arg is a function or "op" (e.g. inline function), call that with the $answers
    :if ([:typeof $2] ~ "op|array") do={
        [$2 $answers]        
    }
    # if 2nd or 3rd arg is "as-value", do not print the results to terminal
    :if (($2 = "as-value") || ($3 = "as-value")) do={
        :return $answers
    } else={
        :put $answers
        :return $answers
    }
}

I was more trying to play around with the inline functions $(>[]), so maybe bugs... But comment below anyone uses this and has issues.

Known Issues
- using "arrow keys" to move inline does NOT work
- ideally there be some "choice" type like the original Inquirer.JS code
- no support for IPv6 or "bool" types
- cursor shows on line below, although edit happens on line above

TODO's
- rename defval= to just val= to match $CHOICE array
- if validate/defval/action/when are fns, provide $answers and $questions as args
- add "type=" e.g. support non-text inputs like $CHOICE
- validate based type= automatically to avoid needing validate=
- add new "when=<function>|<bool>", default true but if false question skips question
- add type="choice" as type & add "choices=" as optional
- type=choice should support a "multiselect=yes" to allow multiple val= as array in answers
- keyboard shortcuts for type=choices
- add type=ipprefix
- add type=ip6
- add type=confirm + confirm=yesno|bool|num for a simple yes/no
- add type=password to mask password input
- add type=seperator to insert blank or text= (from inquirer.js)
- support a type="goto" and goto=<num-index>|<str=key> e.g. with when= also for a conditional jump
Last edited by Amm0 on Wed Jun 19, 2024 10:50 pm, edited 3 times in total.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Tue Jul 11, 2023 10:46 am

Bravo!!! Ti ci sei fatto male :lol:
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Tue Jul 11, 2023 10:52 am

viewtopic.php?p=572818#p572820 :lol: :lol: :lol:


I see the "GIF", not tested, ty to make the input at the same line, is more readable....
viewtopic.php?p=572818#p934069
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Tue Jul 11, 2023 5:55 pm

Bravo!!! Ti ci sei fatto male :lol:
The "do=[:return]" trick to collect input works ... just hated the ugly "value:[]" prompt it uses – but yeah it's dozens of lines of script to avoid that ;). But the instant feedback on bad input is pretty nifty, I thought at least.

In fairness, 90% of the code is dealing with types and validation. And nothing with script var types is easy.
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 8:45 am

Very cool !
Practically a preceptron of a neural network. A little more and there will be artificial intelligence on Mikrotik. :D
 
msatter
Forum Guru
Forum Guru
Posts: 2936
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 10:03 am

If you look at the input screen as a CRT TV then you can redrawn the page with the lines and entered data once on confirmed line, or even every key. Then you can correct previous entered data by using the cursor buttons by using the [:te cuu] for example to go up after redrawn page.

The page with lines and data is then like a canvas, and you move from field to field by the cursor keys and then edit. An other advantage is that see all the questions up front and then start with answering the first one.
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 10:22 am

By the way, what is the /terminal cu command and when did it appear?
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 10:39 am

Also, the Amm0 script is good as an example of using (>[]). Question to the author: now what do you think when in practice it is appropriate to use the new data type op (>[]) that you explored?
 
msatter
Forum Guru
Forum Guru
Posts: 2936
Joined: Tue Feb 18, 2014 12:56 am
Location: Netherlands / Nīderlande

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 10:46 am

Press TAB for options or F1 for help:
@ > [:terminal/

.. -- go up to root
cuu -- move cursor up
el -- erase line
inkey -- read key
style -- set output text style
Small menu example with active help and (audio) feedback on error:
{
:local readKeyString do={
# written by msatter 2020-2021
# keyFlag show if the input was caused by a key pressing, firstDisplay is showing if the input was pre-provided by $4 
# Default characters allowed to use.
:set $errorName "characters"; #default error name.
:set $sound "\a"; # Warning sound. Put a "#" in front to silence it.
:set $upKey  false; # set $upKey to false to not indicate default a manual change of a earlier entry. 
:set $endKey false; # set $endKey to false
 :set $ASCI " !\" \$ &'()*+,-./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}"; # allowed characters to be typed.
 :set $control [:pick $2 ([:find $2 ":"]) [:len $2]]; :set $2 [:pick $2 0 ([:find $2 ":"]+1)]; # Fill $control with the provided values in $2
 :if [:find $control *] do={:set $hide true;:set $unHide false} else={:set $hide false}
 :if [:find $control #] do={:set $allowedASCI "0123456789";:set $controlASCI "#";:set $errorName "numbers"};	# numbers allowed.
 :if [:find $control @] do={:set $allowedASCI "+-.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";:set $controlASCI "@";:set $endCheck true;:set $endCheckRegex "^[a-z0-9.+_-]+\\@[a-z]+\\.[a-z]{2,7}\$"}; # e-mail address.
 :if [:find $control /] do={:set $allowedASCI "!\$&()*+,-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";:set $controlASCI "/";:set $endCheck true;:set $endCheckRegex "[\\.|^][a-z0-9._-]+\\.[a-z]{2,7}[/]{0,1}[a-zA-Z0-9\$_.+!*(),-]*"}; # web address.
 :if [:find $control I] do={:set $allowedASCI "/.0123456789";:set $controlASCI "I";:set $endCheck true;:set $errorName "IPv4 address";
 :set $endCheckRegex "^(25[0-5]|2[0-4][0-9]|[01][0-9][0-9]|[0-9][0-9]|[[0-9])\\.(25[0-5]|2[0-4][0-9]|[01][0-9][0-9]|[0-9][0-9]|[[0-9])\\.(25[0-5]|2[0-4][0-9]|[01][0-9][0-9]|[0-9][0-9]|[[0-9])\\.(25[0-5]|2[0-4][0-9]|[01][0-9][0-9]|[0-9][0-9]|[[0-9])(/(3[0-2]|2[0-9]|[1][0-9]|[0-9]))\?\$"}; # numbers and special signs allowed.
     
 :if [:find $control 6] do={:set $allowedASCI " :/0123456789";:set $controlASCI "#";:set $errorName "IPv6 address"}; # numbers and special signs allowed.
 
 # (3[0-2]|2[0-9]|[1][0-9]|[0-9]) CIDR Or :put [:typeof (255.168.21.7/24 in 0.0.0.0/0)] bool is ok and if nothing the the IP address was invalid.
  
 :set $minLength [:pick $3 0 [:find $3 "-"]]
 :set $maxLength [:pick $3 ([:find $3 "-"]+1) [:len $3]]
 :if  $hide   do={:set $1 "$1 TAB key shows entry."}; # Add to help text that the hidden content can be made visible
 # Adds the minimal an maximal length displayed in the Help line. If no length is defined then nothing is displayed.  
 :if ($minLength = $maxLength && $minLength != 0) do={:set $1 "$1 (Set length: $maxLength )"} else={
   :if ($minLength=0 && $maxLength>0 && $maxLength!=255)	do={:set $1 "$1 (Maximal length: $maxLength)"}
   :if ($minLength>0 && $maxLength>0 && $maxLength!=255)	do={:set $1 "$1 (Minimal length: $minLength, maximal length: $maxLength)"}
   :if ($minLength>0 && $maxLength=255) 			do={:set $1 "$1 (Minimal length: $minLength)"} }   
 :te st varname-local; :te el; :put "> Help:\t $1"; :te cuu; :te cuu; :te st syntax-noterm; # Displays the (adapted) help text
 :local i 0xFFFF; :set $keyFlag false; [:te el];:set $firstDisplay true;
 :put "$2 _"; # write first time the label with a cursor (_)
 
 :if ([:len $4] > $maxLength) do={[:te cuu;:te st varname]; :put "$2 $display \t Ignored the provided entry. Enter one yourself.$sound"; [:te st none];:set $4;:set $keyFlag false}
 :if ([:len $4] < $minLength && [:len $4] > 0) do={[:te cuu;:te st varname]; :put "$2 $4 \t Error: the pre-provided entry is to short. Add to, or replace this entry.$sound"; [:te st none]; :set $display $4;:set $readString $4;:set $4}
 
 :do {
  
  :while ( (!$endKey) ) do={
    :if ([:len $readString] > 0) do={    
      :if ($i=8) do={:set $keyFlag false; :set $readString "$[:pick $readString 0 ([:len $readString]-1)]"; :set $display "$[:pick $display 0 ([:len $readString])]"}      
      :if (($i=9) && $hide && (!$unHide))	do={:set $unHide true; :set $tempDisplay $display;:set $keyFlag false;:set $i}
      :if (($i=9) && $unHide && $hide)		do={:set $unHide false;:set $display [:pick "$tempDisplay******************" 0 [:len $readString]];:set $keyFlag false;:set $i} }            
    :if $keyFlag 			do={:set $temp "$[:pick $ASCI ($i-32) ($i-31)]";}; :set $i;
   
    :if [:find $control $controlASCI]	do={:if ([:find $allowedASCI $temp] >= 0) do={} else={:set $temp} }
    
    :if (([:typeof $temp]~"(nil|nothing)" && $keyFlag) && (!$firstDisplay)) do={[:te cuu;:te el;:te st varname];:put "$2 $display \t ERROR: ignored invalid key input.$sound"; [:te st none];:set $keyFlag false}
    
    :if (!firstDisplay) do={       
    :if (([:len $readString] >= $maxLength) && $keyFlag) do={[:te cuu;:te st varname]; :put "$2 $display \t Error: maximal $maxLength $errorName allowed.$sound";	[:te st none]; :set $temp; :set $keyFlag false} }    
    :if $keyFlag do={:set $readString "$readString$temp"};	                                        # Here the accepted character is added to $readString as long the length is less than maximum 
    :if ($hide) do={:if $keyFlag do={:set $display "$display*"};} else={:set $display $readString};	# Sets the displayed text 
    :if ($unHide) do={:set $display $readString};	                                                # Unhide text that is hidden when pressing the TAB key
    :if $firstDisplay do={:set $display $4;:set $readString $5;:set $firstDisplay false;:set $4;:set $5};	# Sets display to the provided string if it is the first view
   
 # Loop that displays and wait for a key to be pressed         
   :do {[:te cuu]; :put "$2 $display_ "; :set $i [:te in 500ms]; :if ($i=0xFFFF) do={[:te cuu;]; :put "$2 $display  "; :set $i [:te in 500ms]}} while=($i=0xFFFF); :set $keyFlag true; 
   [:te cuu;:te el]; :put "$2 $display  "; # erases any error messages shown
   :if ($i=60932) do={:set $i 13}; # The arrow down key is the same here as the Enter key and any changes will be checked this way.
   :if ($i=13 || $i=60931) do={:set $endKey true}; # This will enable to use one value to indicate the completion of the entry or go for an other entry up to for editing.
  }; #while=( (!$endKey)....
  :if ($i != 60931) do={ # Skip checks because these values where already checked before.
   :if ($unHide) do={[:te cuu;:te el]; :put "$2 $tempDisplay"; :set $display $tempDisplay}; # On exit hide the text again, which was made visible by using the TAB key.   
   :if (([:len $readString]>0)&&([:len $readString]<$minLength)) do={[:te cuu;:te st varname]; :put "$2 $display \t Error: not enough $errorName entered. Minimal length is: $minLength.$sound";[:te st none]; :set $endkey false;:set $keyFlag false}
   :if (([:len $readString] = 0) && ($minLength > 0)) do={[:te cuu;:te st varname]; :put "$2 $display \t Error: this entry can't be left empty.$sound";	[:te st none]; :set $endKey false;:set $keyFlag false}  
   :if ($endCheck) do={
    :if ($readString ~ $endCheckRegex) do={:set $endCheck false} else={[:te cuu;:te st varname]; :put "$2 $display \t Error: format of entry is incorrect. See example in help underneath.$sound"; [:te st none]; :set $keyFlag false; :set $endKey false} }; # :if ($endcheck..
  } else={:set $upKey true}; # Skip the checks if change up to an other entry. $readString should be destroyed and ignored on return.
 :set $endKey false
 } while ((([:len $readString] < $minLength) || $endCheck) && $i != 60931 );  #while check minium
 #:log info "$upKey $i"
 :return {$readString;$display;$upKey}
}; # End of function readKeyString, the value where returned to the caller, saved there in an array

#####################################################################################################################
# defining variables and assign to them values if needed.
#:local readKeyString; # needed if is called to the :global
:global arrayResult   [:toarray ""]; :local arrayLabel [:toarray ""]; :local arrayControl [:toarray ""]; :global arrayDisplay [:toarray ""]
:set $keyString "123456789abcdefghijklmnopqrstuvwxyz"; # Limits the number of menu items and acts a sequencer of the menuitems.
# Store menu in an array
# The number/letters defines the sequence of in the menu, description/help and then the show menu item with always a ":".
# If needed a minimal and maximal length that should be entered and then control characters:
# @ = e-mail addres, / = web/url, # = numbers and * = should not be displayed 
:set $arrayLabel { "7 IPv4 address 192.168.0.1"="IPv4 address:I";
		   "1 Login name which you want to use for this device."="Login name:1-15";
                   "2 Enter or change password."="Your password:4-8*";
                   "3 Pin"="Pin:4-4#*";
                   "4 Remark"="Remark 2:"
                   "z Are all entries correct. If not use ARROW-UP to correct. Then press ENTER."="Ready:"}
                   #"5 Webpage Example: (www.)mikrotik.com"="Webpage:/";
                   #"6 e-mail address (example: you@mail.com)"="E-mail:@"

# The control characters used in $arrayLabel should be present in $arraControl. Manual entry needed in the key requests and error messages.                   
 :set $arrayControl {"/";"@";"#";"*";"I";"P"}; # URL e-mail number hidden	#"8 IPv6 address 192.168.0.1"="IPv4 address:P";}                  
# Fill the support array with their assinged values.
 :for i from=0 to=([:len $arrayLabel]-1) do={:set $a [:pick $arrayLabel $i];:set $a "$[:pick $keyString $i ($i+1)]$[:pick $a 0 ([:find $a ":"]+1)]";:set ($arrayDisplay->"$a") ""}; # Fill $arraydisplay with labels
 :for i from=0 to=([:len $arrayDisplay]-1) do={:set $h $i; :set $a [:tostr [:pick $arrayDisplay $i ($i+1)]]; :set $a [:pick $a 0 [:find $a "="]]}
 :set $count 0; # set to default value                 
# Prints the header of the menue
 :put "- Menu testing -"
 :put "----------------"
# Writes out the full menu and then return to the first line of th menu
 :foreach label,value in $arrayDisplay do={:put "$[:pick $label 1 [:len $label]] $value"};	# Shows the whole menu.
 :for i from=0 to=([:len $arrayDisplay]-1) do={:te cuu};	                                # Goes to the top of the shown menu. 
# Starting the calls to the readkey function.
:while ($count <= ([:len $arrayLabel]-1)) do={

:set $a [:tostr [:pick $arrayDisplay $Count ($count+1)]]; :set $label [:pick $a 1 [:find $a "="]]; # Gets the $label of the next menu-item and cleans it up.
:if (($returnArray->2) && $count = 0) do={} else={:put $label}; # Only display the label if it is a first display
:set $label	[:pic $arrayLabel $count]; # Gets the $label
:set $descriptLabel [:tostr [:pick $arrayLabel $count ($count+1)]];	# gets the array string including the key.
:set $descript [:pick $descriptLabel 2 [:find $descriptLabel "="]];	# Get key of array...why is that made so difficult. Also removes the first two index characters in one go.

# getting control characters and this can be simplified by adding a extra parameter to the called function
 :set $length;
 :set $control [:pick $label ([:find $label ":"]+1) [:len $label]]
 :set $label [:pick $label 0 ([:find $label ":"]+1)]; # restore the label without the number for length
 :foreach findControl in=$arrayControl do={
  :foreach findControl in=$arrayControl do={:if ([:typeof [:find $control $findControl ([:len $control]-2)]] != "nil") do={:set $control [:pick $control 0 ([:len $control]-1)]; :set $label "$label$findControl"}} 
 }; # nibling control away, to obtain only the $length and add at the same time those to $label
 :set $length $control; # The leftover is the lenght if defined.
 :set $control; # Erase $control
 
#:put "Label: $label / control: $control / length: $length / descript: $descript \n\n\n\n"
#:error ""

#:set $descript [:pick $descript 2 ([:len $descript])]; # Remove the first two characters of the string. These are only used to order the fields in the array.
 :if ([:len $length] = 0) do={:set $length "0-255"}; # Always set a length even if it is zero zero
#:log info "count: $count / return: $($returnArray->2)"
# Call to function
#:set $show "PassWord"
 :set $returnArray [$readKeyString $descript $label $length [:pick $arrayDisplay $count] [:pick $arrayResult $count]]; 	# Call to function
 :if (($returnArray->2) && $count > 0) do={[:te el]; :put "$[:pick $label 0 ([:find $label :]+1)] $[:pick $arrayDisplay $count]";[:te cuu;:te cuu];:set $count (count-1); :if ($count > 0) do={[:te cuu]} } else={
   :set $resultString	($returnArray->0);:set $displayString	($returnArray->1); # Get the result and display values from the array 
# put a number/letter from $keyString in front of the label so that sequence in which the array stays as entered. (no auto sort)
   :set $label	"$[:pick $keyString $count ($count+1)]$label";
   :set $label	[:pick $label 0 ([:find $label ":"]+1)]; # Remove any returned control characters
# store the returned entries and displayed
   :set ($arrayResult->"$label") $resultString
   :set ($arrayDisplay->"$label") $displayString
   :if ($returnArray->2) do={} else={:set $count ($count + 1)}; # Add one to the counter in While if the entry was ignored and the the arrow up key was pressed
 }; # On $upKey all the not confirmed by and ENTER are ignored and the previous entry is selected
};

# And show the entered values
 [:te style syntax-no]; :put "Returned entries:"
 :foreach label,value in=$arrayResult do={
  [:te st syntax-no];   :put "$[:pick $label 1 ([:find $label ":"]+1)] ------------------";	# "1" Removes the sort letter and the "+1" keeps the ":".
  [:te cuu;:te st esc]; :put "\t\t $value                                                "
  :terminal style none
 }; # foreach label,string
 :put "\n"
}
Have fun ;-)
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 11:16 am

Thanks a lot. Come in handy.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 11:35 am

gatto-felice.jpg
Bravi!!! Tutti, mi piace quando c'è collaborazione!!!
You do not have the required permissions to view the files attached to this post.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 3:57 pm

Also, the Amm0 script is good as an example of using (>[]). Question to the author: now what do you think when in practice it is appropriate to use the new data type op (>[]) that you explored?
The (>[]) is undocumented, so always a risk. While it provides some handy shortcut to define a function inline, it be easy to use the "normal" way like set ($myquestion->0->"validate") do={} – just not as clean as doing in a single array definition. Now... (>[]) use as a "inline function" as an argument to another function call is kinda handy – otherwise you'd have to define the function in another line.

My guess is it's artifact of their parser – operators like greater-than > are still functions internally but take input from the left side – most function take the argument from the right side of fn name...but since left side is nil/nothing/null, it somehow causes compare operator to "escape" (and returned). My bet is they have a lot of bugs to fix outside of mucking with the bowls of their script interpreter code, so likely "safe".
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Thu Jul 13, 2023 4:44 pm

Small menu example with active help and (audio) feedback on error:
Great work. Nice sound effects... now if only [:beep] worked using the terminal. I should fixup up the cursor position & borrow some of your validation. (Although theoretically UTF-8 input passthrough if terminal supported it - why it's not limited to lower ASCII)

But part of what I was going for is simplicity to get one question, so
:local iname [$INQUIRE ({{text="name?"}}) as-value]
works in a larger script to get some input.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 6:00 pm

One of the feature of the original JS library was a "choice" question type, essentially the equivalent of a "radio selector" in windows. But to show a menu is kinda different that collecting input, I wrote a seperate function to do it. I don't have time to merge it with the $INQUIRE right now – e.g. so the questions array can use a type="choice" and choices={"one";"two"}. But posting the code both so I don't forget & might be useful independent of $INQUIRE function anyway.

The $CHOICES function takes an array as 1st argument, and presents that as a selectable list in the terminal. Then, one of the items can be selected by moving around the UP/DOWN arrow keys. A value is selected by hitting enter/return on the selected (shown in reversed text). The selected item's string (or val=, see below) is the return as the value of the function. e.g.
:put [$CHOICES ({"Dog"; "Cat"; "Snake"; "Pig"; "Horse"; "Bird"; "Llama"})]

          Dog      
          Cat      
          Snake            
          **Pig**      
          Horse            
          Bird             
          Llama            
Pig
The provided array list can populated with "str" or contain inner associative array with val=, text=, and help= attributes. If just a string is used, the return value and what's displayed are the same. However, when an associative array, text= is what's displayed on the terminal in list, while val= is what will be returned by the function. With a help= that shows some string next to the selectable item e.g. in dimmed text, like a "tooltip"
:put [$CHOICES ({{val="yes";text="Yes";help="go ahead"};{val="no";text="No";help="skip it"}})]

The return value can be stored in a variable to use in other script code:
{
    :local d40 [$CHOICES ({{val="Yup"};{text="Nope"}})]
    :put "$d40 - good choice!"
}
 
Note: As shown above, the input array with the "choices" for $CHOICES, will try "fix up" empty values... So if you set just text=, that's what will be returned if selected even without a val=. Similar with only a val=, that becomes the text=.


Image


Eventually, I'll update $INQUIRE to use it. But here is the $CHOICES function as a standalone function:

CHOICES - creates selectable list in terminal code

:global CHOICES do={
    # :global CHALK
    :local lchoices $1
    :local isel 0
    :if ([:len $lchoices]>0) do={:set isel 0} else={:error "error - no choices"}
    :for uchoice from=0 to=([:len $lchoices]-1) do={
        # convert string list to array
        :if ([:typeof ($lchoices->$uchoice)]="str") do={
            :set ($lchoices->$uchoice) {val=($lchoices->$uchoice);text=($lchoices->$uchoice)} 
        }
        # if array, regularize it
        :if ([:typeof ($lchoices->$uchoice)]="array") do={
            :if ([:typeof ($lchoices->$uchoice->"val")]~"str|num") do={
                :if (([:typeof ($lchoices->$uchoice->"text")]~"str|num")) do={
                    # both text= and val=
                } else={
                    # val= but NO text=
                    :set ($lchoices->$uchoice->"text") ($lchoices->$uchoice->"val") 
                }
            } else={
                # NO val=
                :if (([:typeof ($lchoices->$uchoice->"text")]~"str|num")) do={
                    # use text= as val=
                    :set ($lchoices->$uchoice->"val") ($lchoices->$uchoice->"text") 
                } else={
                    #invalid
                    :set ($lchoices->$uchoice) {val="invalid$uchoice";text="$[:tostr ($lchoices->$uchoice)] [invalid$uchoice]"}  
                }
            }
        } else={
            # neither string nor array
            :set ($lchoices->$uchoice) {val="invalid$uchoice";text="$[:tostr ($lchoices->$uchoice)] [invalid$uchoice]"}  
        }
    }
    :if ([:typeof $selected] = "str") do={
       :set isel [:find $lchoicesnames ] 
    }
    :local lkey 0
    :local bsel ""
    :while ($lkey != 13) do={
        :if ($lkey != 0) do={:for icuu from=0 to=([:len $lchoices]-1) do={/terminal/cuu}}
        :for ichoice from=0 to=([:len $lchoices]-1) do={
            # avoid CHALK dependency             
            #:if ($isel = $ichoice) do={:set bsel "yes"} else={:set bsel "no"}
            #:put "\t$[$CHALK blue inverse=$bsel] $($lchoices->$ichoice->"text") $[$CHALK reset]\t$[$CHALK grey dim=yes]$($lchoices->$ichoice->"help")$[$CHALK reset]"
            :if ($isel = $ichoice) do={:set bsel "7;"} else={:set bsel ""}
            :put "\t \1B[$($bsel)34;49m $($lchoices->$ichoice->"text") \1B[0m \t \1B[90;49m $($lchoices->$ichoice->"help") \1B[0m"
        }
        :set lkey [/terminal/inkey]
        :if ($lkey = 60931) do={
            :set isel ($isel-1)
            :if ($isel < 0) do={:set isel 0}
        }
        :if ($lkey = 60932) do={
            :set isel ($isel+1)
            :if ($isel > ([:len $lchoices]-1)) do={:set isel 0}
        }        
    }
    :return ($lchoices->$isel->"val")
}
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 6:13 pm

Do you ultimately want to create a better "QuickSet"? :lol:
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 6:48 pm

Do you ultimately want to create a better "QuickSet"? :lol:
Close, but not exactly. [ @mrz put the kibosh to my feature request for CLI/API to quickset: viewtopic.php?t=152201&hilit=quickset - so this be one solution for a better QuickSet ;) ]

It's /container actually where I run into these "user input" needs. Today I use a script per container that manage a particular container. But it's hard to be generic, so the script requires a bunch of variable in the code – that might need to be changed for a particular router – and editing a script isn't very "user friendly". For example, if you look at my serial2http container, you can see how complex installation is to describe. A few user prompts to install avoid a lot of writing: https://github.com/tikoci/serial2http/t ... stallation

For example, what a container's root-dir should be is not that easy to abstract/guess, so be good to get a user to "confirm" the path. And if you think each question in $INQUIRE's array is mapped to each on of a container's environment variables, to confirm or alter how the container runs, the logic here might make more sense.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions, ft. inline functions (>[])

Mon Jul 17, 2023 6:58 pm

Tutti, mi piace quando c'è collaborazione!!!
BTW, It's actually the "ipprefix" type I need to add my INQUIRE script – going to have to borrow your :parse technique for that one (since there still isn't a :toipprefix ;) )
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 8:17 pm

I made just some useful things.... ;)

:lol:
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 9:12 pm

I made just some useful things.... ;)

:lol:
Well, you can put a wizard over your extensive encoding and converter libraries...

Image

{
    :local iftrans do={
        :global CHOICES
        :local gostspecs {"OST 8483";"GOST 16876-71 (1973";"T SEV 1362 (1978)";"GOST 7.79-2000 (2002)";"GOST 52535.1-2006 (2006)";"ALA-LC";"ISO/R 9";"ISO 9"}
        :if ($1 = "Latin-Transliterated Cyrillic") do={
            :put "There are many standards for transliteration...which one?"
            :return [$CHOICES $gostspecs]
        } else={:return [:nothing]} 
    }

    :local encodings {"Urlencoded";"Base64";"HexString";"UCS2";"GSM7";"UTF8";{text="CP1252 / Latin-1";val="CP1252";help="RouterOS default"};"CP1291";{val="ASCII";text="US ASCII"; help="us-ascii"};"Latin-Transliterated Cyrillic"}
    :local inouts {"global/local variable";"file";"escaped string text";"PDU field (big-endian,semi-octets)"}
    :put "How is the text already encoded?"
    :local inencoding [$CHOICES $encodings]
    :local ingosts [$iftrans $inencoding]
    :put "Where is it stored currently?"
    :local insrc [$CHOICES $inouts]
    :put "What encoding to you need output?"
    :local outencoding [$CHOICES $encodings]
    :local outgosts [$iftrans $outencoding] 
    :put "Which output do you need?"
    :local outdest [$CHOICES $inouts]

    :put "..."
    :put "SpamGPT says:"
    :put "..."
    :put "Help @rextended! I need $inencoding $ingosts stored in a $insrc, for output in $outencoding $outgosts to $outdest."
    :put "..."
    :put "@reextended says:"  
    :put "Do you not know how to search? \1B]8;;$http://forum.mikrotik.com/search.php?keywords=$($inencoding)to$($outencoding)\07http://forum.mikrotik.com/search.php?keywords=$($inencoding)to$($outencoding)\1B]8;;\07"  
    :put ""
}
edit1: minor typos in output text
edit2/3: added "clickable URL" and expected response
Last edited by Amm0 on Mon Jul 17, 2023 10:28 pm, edited 3 times in total.
 
holvoetn
Forum Guru
Forum Guru
Posts: 6279
Joined: Tue Apr 13, 2021 2:14 am
Location: Belgium

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 9:16 pm

Too bad there is no rep system here.

You definitely would have received a positive one for that last post :lol:
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12438
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 9:23 pm

Well, you can put a wizard over your extensive encoding and converter libraries...

Oh my go(o)d.... :shock:
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jul 17, 2023 10:30 pm

Well, you can put a wizard over your extensive encoding and converter libraries...
Oh my go(o)d.... :shock:
I'd forgot about HexString, URL encoding, Base64, likely others. And added an expected response...

*if you use SSH it creates a "clickable" URL to search in the output of my "Encoding $CHOICES" example:

Image
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Thu Jun 13, 2024 8:52 pm

Dear, Amm0 !
Could you correct the $CHOISES function so that it would be possible to use the "enter" key to select a menu item WITHOUT COMPLETING THE WORK? That is, the function would transfer the selected item to some global variable, while remaining in the selection loop to select another item (the ability to reselect the selected item is also preserved). And for some other functional key (not “input”), the menu could complete the work without selecting anything.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Thu Jun 13, 2024 10:33 pm

Could you correct the $CHOISES function so that it would be possible to use the "enter" key to select a menu item WITHOUT COMPLETING THE WORK? That is, the function would transfer the selected item to some global variable, while remaining in the selection loop to select another item (the ability to reselect the selected item is also preserved). And for some other functional key (not “input”), the menu could complete the work without selecting anything.
Well... $CHOICES was made to be simple. Couldn't you just use a :while loop and have a "fake" selection for "DONE" that you check in the loop? That be keep things simple.

And while more complex, $INQUIRE function has "function callbacks" that could be used to do this. The "validate=" in $INQUIRE can keep you in the same menu items.

Overall, I've tried to borrow from how "Inquirer.js" ( https://github.com/SBoudrias/Inquirer.js ) handles things. $CHOICE is same as "Select" in Inquirer.js. In that scheme, there are other "types" of these "TUI control". So @Sertik is more asking for some $CHECKBOX function. That possible but it should be separate from $CHOICE.
 
User avatar
Sertik
Member
Member
Posts: 489
Joined: Fri Sep 25, 2020 3:30 pm
Location: Russia, Moscow

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Fri Jun 14, 2024 10:15 am

No, I'm not asking for a new feature. Instead of :return ($lchoices->$isel->"val") you need to do:

:global ValChoices ($lchoices->$isel->"val")

and continue executing the $CHOICES function. My script will check $ValChoises and if a value appears in it, it will take it from there.

I don't want to tinker with your function. It is better and easier for the author to make changes to it. :)
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jun 17, 2024 5:44 pm

I don't want to tinker with your function. It is better and easier for the author to make changes to it. :)
$CHOICES was designed to be simple, and, eventually a "plugin" to $INQUIRE as a "type" in menu. Some future $SELECT is the missing function that stay in same menu to make multiple selections in this scheme, showing checkbox [X] things or the like....like InquireJS/similar do.

But it should be a one-line change to use a global - @Sertik I have faith in your abilities. But... As a general programming best practice, functions should not be operating on variables outside their scope - that's why there functions. And, using arbitrary globals in a function requires [:parse] tricks, which I don't like since errors are deferred to runtime as :parse is not syntax checked. Anyway setting a :global is not going to be the example. But totally cool if you want to modify it for your uses.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Mon Jun 17, 2024 6:03 pm

I did create another function, $qkeys in a different thread. This is simplified version of $INQUIRE, that just takes keypresses to either run a command, or present a menu if array contained another array.

See viewtopic.php?t=192475#p1080993

So for @Sertik's case, the $qkeymap could set a global, on a keypress (not choice) but that might work. For example, changing $qkeymap to:
:global count 0
:set qkeysmap {
       "+"={"+1";(>[{:global count; :set count ($count+1); :return $count}])}
       "-"={"-1";(>[{:global count; :set count ($count-1); :return $count}])} 
}
qkeys-global-add2.png
You do not have the required permissions to view the files attached to this post.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Wed Jun 19, 2024 5:18 am

Tutti, mi piace quando c'è collaborazione!!!
LOL, so Mikrotik @druvis did a good video to explain "Scripting Arrays": https://www.youtube.com/watch?v=eWCJw0uZ-lE

Essentially, the $INQUIRE script at top is just a "for" loop shown in the video, just with more stuff going on inside the loop. i.e. looping over the array of questions, instead of array from /interfaces as shown in the video. Inside the $INQUIRE loop gets more complex... largely because of the "11 data types", which I noticed there is another nice video on:
https://www.youtube.com/watch?v=9SeYC_s95rw
... but to ask a user for something practical like VLAN ID, you want to validate the "num" type is between 1 to 4094... so the "array loop" need to deal with all those data-types

Do you ultimately want to create a better "QuickSet"? :lol:
Well idea here was ANYONE could USE these functions to create their OWN QuickSet. Since that take just knowing the {} array syntax, and not the complex array/data-type processing which is hidden in the $CHOICE/etc functions here.

Anyway, those video might help explain the array syntax used in the $INQUIRE and $CHOICE, so thought I'd point out Mikrotik's contributions here ;).

Now $CHOICE and $QKEYS, use the "mixed array" formats that @druvis said "hurt his head". But CREATING an array is a little easier...than USING them in a script.
 
User avatar
Amm0
Forum Guru
Forum Guru
Topic Author
Posts: 4100
Joined: Sun May 01, 2016 7:12 pm
Location: California
Contact:

Re: $INQUIRE - prompt user for input using array of questions + $CHOICES

Wed Jun 19, 2024 10:21 pm

I did create another function, $qkeys in a different thread. [...]
See viewtopic.php?t=192475#p1080993
To keep things together and consistent, I updated the "qkeys" function in another thread, to a more sophisticated version $QKEYS function below that takes an array as a parameter (instead of using a global). With some more options, and "help" menu too. Additionally the "new QKEYS" uses the fancy <%% operator to, optionally, provide arguments to functions defined in the array with the menu choices.
Screenshot 2024-06-19 at 11.59.05 AM.png
Part of the idea here is that someone can create new commands using QKEYS to build a console UI for anything. So here is an example using the wttr.in REST service to show weather for a few locations. The code builds the array to display from a more simple array, and also shows providing arguments to the function defined for the "macro". So you can have "multiple menus" by just calling $QKEYS from within your own function.

wttr - show weather based on dynamically built menu choices code

Screenshot 2024-06-19 at 12.01.11 PM.png

Here is the code needed to run $wttr or build-your-console-ui:

QKEYS takses array of commands to make menu tree code


I don't want to tinker with your function. It is better and easier for the author to make changes to it. :)
As a general programming best practice, functions should not be operating on variables outside their scope - that's why there functions.


The new $QKEYS here no longer relays on a global for menu - but the functions can use globals. And, any globals STILL have to be declared in "op" type too).

:global count 0
$QKEYS ({
       "+"={"+1";(>[{:global count; :set count ($count+1); :return $count}])}
       "-"={"-1";(>[{:global count; :set count ($count-1); :return $count}])} 
})


And the more elaborate menu from the original version of $qkeys still works, with new $QKEYS, except $qkeysmap have to be provided as an argument to $QKEYS now:
viewtopic.php?t=192475#p1080993
$QKEYS inline=no $qkeymap
$QKEYS $qkeysmap inline=no
>
(4) ip
(6) ipv6
(C) clear
(`) edit macros
(b) bridge
(c) container
(e) export
(i) interfaces
(l) lte mon
(q) quit
(r) board
(v) vlans

> ip >
(/) top
(a) address
(f) firewall
(q) quit
(r) route

> ip > firewall >
(/) top
(c) connections
(f) filter
(m) mangle
(n) nat
(q) quit

> ip > firewall > mangle
Flags: X - disabled, I - invalid; D - dynamic
0 chain=input action=accept log=no log-prefix=""
You do not have the required permissions to view the files attached to this post.

Who is online

Users browsing this forum: ansh, FurfangosFrigyes and 11 guests