NSI script : calling one macro from another using insertmacro gives error - macros

I am trying to call 1 macro from another in NSI script. Both the macros have MB_OKCANCEL . It gives following error when compiled:
**
[exec] Error: label "abort_inst:" already declared in function
**
!include "MUI2.nsh"
OutFile abc.exe
!macro installA
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
ABORT
GoTo lblinstall
lblinstall:
!macroend
!macro uninstallA
MessageBox MB_OKCANCEL "?" IDOK install_A IDCANCEL abort_uninstall
abort_uninstall:
ABORT
install_A:
!insertmacro installA
!macroend
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
!insertmacro installA
${Else}
!insertmacro uninstallA
${EndIf}
FunctionEnd
Section "required" main_section
SectionEnd
Please help

(Next time, please make sure your code doesn't do weird line breaks)
When inserting a macro, all the code between !macro and !macroend will substitute your !insertmacro. Therefor, you should't use static labels in macros–you could only insert the macro once (making a macro pointless!) You can use relative jumps (e.g. Goto +2) or make your labels dynamic by adding parameters to your labels, e.g.:
!macro myMacro param1
${param1}_loop:
MessageBox MB_YESNO "Loop this message?" IDYES ${param1}_loop
# some more code
${param1}_end:
!macroend
However, since you don't pass any parameters to your macros, why don't you simply use functions?
Function installA
# your code here
Function
Function uninstallA
# your code here
Call installA
FunctionEnd

This got resolved by converting one macro into Function, as suggested by #idelberg

Again, as long as you're working with macros, you cannot use static labels. A static label can only be used once in a function or section. Your use of macros would translate to the following:
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
Abort
Goto lblinstall
lblinstall: # FIRST TIME USE
${Else}
MessageBox MB_OKCANCEL "?" IDOK install_A IDCANCEL abort_uninstall
abort_uninstall:
Abort
install_A:
MessageBox MB_OKCANCEL "A?" IDOK lblinstall IDCANCEL abort_inst
abort_inst:
Abort
Goto lblinstall
lblinstall: # SECOND TIME USE
${EndIf}
FunctionEnd
So, that won't work because of the lblinstall label which is being used twice. Instead, you could do something like this:
Function installA
MessageBox MB_OKCANCEL "A?" IDOK lblinstall
Abort
lblinstall:
FunctionEnd
Function uninstallA
MessageBox MB_OKCANCEL "?" IDOK install_A
Abort
install_A:
Call installA
FunctionEnd
Function .onInit
ReadRegStr $0 HKLM "x" "version"
${If} $0 == ""
Call installA
${Else}
Call uninstallA
${EndIf}
FunctionEnd
(I also took the freedom to strip some unnecessary labels from your example)

Related

ZSH autocomplete function using existing autocompletions

I have a function mycmd to launch a program that I wrote. The program needs the first argument to be foo, ssh or ls. The second argument depends on the first argument as follows,
foo -> No second argument
ssh -> Something to ssh to
ls -> A file
I want to write zsh autocomplete function for mycmd which suggest the second argument depending on the first argument. In the simplest form, I know that I can do the following for the first argument
_mycmd() {
compadd foo ssh ls
}
compdef _mycmd mycmd
I have a hard time understanding what to do for the second argument from here. How do I use _ssh autocomplete for ssh argument and _ls autocomplete for ls argument? (And nothing for foo as well)
To inspect the current command line, it could be used $words and $CURRENT that are the completion special parameters.
CURRENT
This is the number of the current word, i.e. the word the cursor is currently on in the words array.
...
words
This array contains the words present on the command line curently being edited.
--- zshcompwid(1), completion special parameters, zshcompwid - zsh completion widgets
The completion function could modify $words and $CURRENT (and/or other variables) and then start the entire completion system based with its modified command line. For example:
$ mycmd ls -al<TAB> ;# This is the input, and
;# $words == ("mycmd" "ls" "-al") ;# original value for $words.
;# $words=("ls" "-al") ;# We could update $words for making zsh
;# $ ls -al<TAB> ;# to start the completion system with
;# its modified command line.
;# Finally, _ls would be called.
The utility function _normal could be used.
_normal
...
A second use is to reexamine the command line specified by the $words array and the $CURRENT parameter after those have been modified.
-- zshcompsys(1), utility function, _normal
_mycmd could be listed below:
_mycmd () {
if ((CURRENT == 2)); then
compadd foo ssh ls
elif ((CURRENT > 2)); then
case "$words[2]" in
(ssh|ls)
shift words
((CURRENT--))
_normal -p mycmd
;;
(foo)
_nothing
;;
(*)
_message "mycmd: invalid subcommand or arguments"
;;
esac
fi
return $?
}
or even, more use of the completion builtin/utility functions like below:
_mycmd () {
local curcontext="${curcontext}" state context line
local -A opt_args
_arguments '*:: :->subcmd'
if [[ "$state" == "subcmd" ]]; then
if ((CURRENT == 1)); then
_describe -t mycmd-subcmd "mycmd command" '(foo ssh ls)'
else
curcontext="${curcontext%:*:*}:mycmd-$words[1]:"
case "$words[1]" in
(ssh|ls)
compset -n 1
_normal -p $service
;;
(foo)
_nothing
;;
(*)
_message "mycmd: invalid subcommand or arguments"
;;
esac
fi
fi
return $?
}

How to define a function that either takes arguments or doesnt?

I am using Fish shell....
Basically, to do something like this:
if (first argument == --r) {
do something
} else {
Do something
if (first argument == --n) {
do more
}
}
To achieve the first if statement I tried:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
But that gives a message:
test: Missing argument at index 6
Functions in Fish don't require their parameters to be specified when you define the function. Any arguments sent to the function by the user are automatically stored in an array called argv. In order to determine whether arguments were sent, you can either count the number of elements in the array, or determine the length of the array as a string. I do the latter:
function my_func
if [ -z "$argv" ]; # No arguments
echo "No arguments supplied"
return
else # At least one argument
if [ "$argv[1]" = "--r" ];
echo "Excellent!"
return
end
end
end
If you prefer to use count, then it will look more like this:
function my_func
if [ (count $argv) -eq 1 -a "$argv[1]" = "--r" ];
# Exactly one argument with specified value "--r"
echo "Excellent!"
return
else # May have arguments, but none equal to "--r"
echo "Give me the right arguments"
return
end
end
Your use of set -q argv[1] is also a good option. But when you're checking for string equality, don't forget to surround your variable in quotes, like this: test "$argv[1]" = "--r".
Here's another method, using the switch...case conditional test:
function my_func
# No arguments
if [ -z "$argv" ]; and return
# At least one argument
switch $argv[1];
case --r;
# do some stuff
return
case "*";
# Any other arguments passed
return
end
end
end
This worked for me:
if set -q argv[1] ;and test $argv[1] = "--r"
Let's start with the error you get when executing this:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
That happens because fish first expands $argv[1] then executes test. If argv has no values then that statement turns into
if test 0 -eq 1 -a = '--r'
Which isn't valid syntax for the test command. It doesn't matter that the first sub-expression evaluates to false since test parses the entire expression before evaluating it.
Rather than doing test (count $argv) -eq 1 just do set -q argv[1] to test if argv has at least one argument. Note the lack of a dollar-sign.
If you're using fish 2.7.0 or newer I recommend the new argparse builtin for handling arguments. Several of the standard functions that ship with fish use it so you can look at them, as well as man argparse, for examples of how to use it. Using argparse is almost always safer, less likely to result in bugs due to sloppy argument parsing using hand written fish script, and will provide argument parsing semantics identical to most commands including all the fish builtins. Including correctly handling short and long flags.
well if the argument is optional then you can do this by:
//check if variable exists
if (typeof variable === 'undefined'){
}
else{
if(typeof variable){}
}

why the syntax `&name arg1 arg2 ...` can't be used to call a Perl subroutine?

for a Perl subroutine, if passing 0 argument, I can use 4 forms to call it. But if passing 1 or more arguments, there is one form that I can't use, please see below:
sub name
{
print "hello\n";
}
# 4 forms to call
name;
&name;
name();
&name();
sub aname
{
print "#_\n";
}
aname "arg1", "arg2";
#&aname "arg1", "arg2"; # syntax error
aname("arg1", "arg2");
&aname("arg1", "arg2");
The error output is
String found where operator expected at tmp1.pl line 16, near "&aname "arg1""
(Missing operator before "arg1"?)
syntax error at tmp1.pl line 16, near "&aname "arg1""
Execution of tmp1.pl aborted due to compilation errors.
Can someone explain the error output from the compiler's point of view? I don't understand why it complains about missing operator.
Thanks
It's documented in perlsub:
To call subroutines:
NAME(LIST); # & is optional with parentheses.
NAME LIST; # Parentheses optional if predeclared/imported.
&NAME(LIST); # Circumvent prototypes.
&NAME; # Makes current #_ visible to called subroutine.
With &NAME "arg", perl sees &NAME() "ARG", so it thinks there's an operator missing between the sub call and "ARG".
In Perl 5, you don't need the & in most cases.

Display contents in the GUI using tcl

I am new to GUI and i was trying to create a simple GUI in tcl. It have a push button which when pressed runs a code and generates a output '.l' file in the directory. But i want the output to be printed in the GUI itself. SO how am i supposed to change this code to do the task.
proc makeTop { } {
toplevel .top ;#Make the window
#Put things in it
label .top.lab -text "This is output Window" -font "ansi 12 bold"
text .top.txt
.top.txt insert end "XXX.l"
#An option to close the window.
button .top.but -text "Close" -command { destroy .top }
#Pack everything
pack .top.lab .top.txt .top.but
}
label .lab -text "This is perl" -font "ansi 12 bold"
button .but -text "run perl" -command { exec perl run_me }
pack .lab .but
Can anyone help me in displaying the contents of output file XXX.l in the GUI itself???
For simple programs that just prints their results to stdout, then it's simple: exec returns all standard output of the program. So you just need to read the return value of your exec call:
proc exec_and_print {args} {
.top.txt insert end [exec {*}$args]
}
But remember, exec only returns after the program have exited. For long running programs where you want the output to appear immediately in your text box you can use open. If the first character of the file name passed to open is | then open assumes that the string is a command line to be executed. With open you get an i/o channel that you can continuously read from:
proc long_running_exec {args} {
set chan [open "| $args"]
# disable blocking to prevent read from freezing our UI:
fconfigure $chan -blocking 0
# use fileevent to read $chan only when data is available:
fileevent $chan readable {
.top.text insert end [read $chan]
# remember to clean up after ourselves if the program exits:
if {[eoc $chan]} {
close $chan
}
}
}
The long_running_exec function above returns immediately and uses events to read the output. This allows your GUI to continue functioning instead of freezing while the external program runs. To use it simply do:
button .but -text "run perl" -command { long_running_exec perl run_me }
Additional answer:
If the program generates a file as output and you want to simply display the contents of the file then just read the file:
proc exec_and_print {args} {
exec {*}$args
set f [open output_file]
.top.txt insert end [read $f]
close $f
}
If you know where the file is generated but don't know the exact file name then read the manual for glob on how to get a list of directory contents.

Getting Error result in System::call for MsiEnumProducts and GetLastError in NSIS script

I have written the following script to Enumerate Installed Products from MSI.
!include LogicLib.nsh
!define MsiGetProductCodeFromName "!insertmacro MsiGetProductCodeFromName"
!macro MsiGetProductCodeFromName MSINSISPRODUCTNAME
Push ${MSINSISPRODUCTNAME}
Call MsiGetProductCodeFromName
!macroend
Function MsiGetProductCodeFromName
;================
; Stack : <ProductName>, ...
;================
Exch $0 ; ProductName
Push $1 ; For internal use as buffer for ProductCode
Push $2 ; For internal use as counter to MsiEnumProducts
Push $3 ; For internal use as result of MsiEnumProducts
; loop through all productcodes
Strcpy $2 0
System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
DetailPrint "User name - $0"
DetailPrint "String length - $1"
DetailPrint "Return value - $2"
loop:
DetailPrint "Calling MsiEnumProducts ( $2, $1 )"
System::call "msi::MsiEnumProducts (i r2, t .r1) i.r3"
DetailPrint "Result = $3"
System::call "kernel32::GetLastError () i.r3"
DetailPrint "LastError = $3"
${If} $R1 == 0
DetailPrint $1
Intop $R0 $R0 + 1
goto loop
${Endif}
end:
Pop $3
Pop $2
Exch
Pop $0
;=========================
; Stack : <ProductCode>, ...
; ========================
FunctionEnd
When I use the above script in the following manner:
${MsiGetProductCodeFromName} ${PRODUCT_NAME}
I am getting the following output:
Calling MsiEnumProducts(0, )
Result = error
LastError = error
Completed
Please note that this is excluding the output of System call to GetUserName, which is working fine.
Any help would be appreciated.
The first call to MsiEnumProducts has to start at index 0 so add Strcpy $2 0 before the loop label.
I'm not sure if you can have a space between dll::function and (), when you just get "error" from the system plugin it usually means there is something wrong with the syntax.
You cannot call kernel32::GetLastError and get a valid result, it has to be part of the original call: System::Call 'dll::function() ?e' and then pop the error code off the stack...