Write a command that supports `(no arg)` as arguments

You may have noticed this, but a few G’MIC commands can takes arguments, or not.
It is the case for instance with command sample, that can be run with an argument:

$ gmic sample rose output file.jpg

or without arguments:

$ gmic sample output file.jpg

(in which case, a random image is chosen).

Here, I’m not talking only about having default arguments defined, but the fact that a command can decide whether the item that just follows its invokation should be considered as a list of valid command arguments or not (in the latter case, it is just ignored by the interpreter for the invoked command, and the next item will be considered as the next command to execute).

There is actually a quite simple trick to allow this in your own custom command, here I explain how it can be done.

Suppose you want to create your own version of the blur command, called my_blur, but with a default standard deviation of 10:

my_blur : check "${1=10}>0"
  blur $1

Now of course, you can write:

$ gmic input.jpg my_blur

but not :

$ gmic input.jpg my_blur output file.jpg

because as command my_blur expects an argument, the next item output will be considered as the argument of my_blur and the first check will fail (output>0 is not a valid expression). This, even when you told the default value for argument $1 is 10. That’s because arguments are not typed, so there are no reasons the interpreter knows that in this case, the default value for $1 should be use rather than considering $1 = output.
Of course, one way to invoke my_blur with all arguments set to default value would be:

$ gmic input.jpg my_blur , output file.jpg

But if you really want/need to define a command my_blur such that writing $ gmic input.jpg my_blur output file.jpg works, then here is how you can do:

#@cli my_blur : _sigma[%]>0,_boundary_conditions : (no arg)
#@cli : Blur selected images with a gaussian filter of standard deviation 'sigma'.
my_blur : l[] { check "${1=10}>=0 && isint(${2=0}) && inrange($2,0,3)" $=a onfail noarg a0,a1,a2=$0,3,0 }
  e[^-1] "Blur image$?, with standard deviation "$a1" and boundary conditions "$a2"."
  blur $a1,$a2

What we do here is first check the validity of the received arguments, and if that fails, we catch the exception thrown by check in order to tell the interpreter the command actually takes no arguments, and set values for default arguments.
Here, the ‘meaningful’ arguments are set to named variables a0, a1, …, so we don’t use the classical $1, $2, … in the command, as $1 is still equal to the item next to the command invokation (output in our case).

So finally, with this trick,

$ gmic input.jpg my_blur output file.jpg

works as expected!

I don’t say you should use this everytime (most of the time, it’s better to force a command to have a list of arguments), but this is still something to keep in mind for particular cases.

Thanks for your attention, I’m going back to work :slight_smile:

1 Like

It’s cool to have the choice.
Don’t you think this should be added somewhere in the Documentation/Tutorials? Before it gets buried in the forum!

1 Like

Documentation/Tutorials happens on borrowed stolen time. :wink: in the meantime, Google <some gmic topic> site:discuss.pixls.us helps a bit with un-burying topics in the forum.

Well i just use the search function on this forum.
It’s just that some people just go for the documentation (like me) first.
( I usually don’t read/join forums. And I post even less… except here maybe :wink: )

I just thought that maybe it could be added to the doc first, then posted with a link in the forum? But it’s up to you anyway :slight_smile:

1 Like

I just tried this on a command that works better with this trick, and it does the job. Thank you.

Question, why the a0=$0? It’s not needed.

How do I force this to accept $1 when it’s greater than 1?

l[] { check "${1=10}>0 && isint($1)" $=arg onfail noarg arg1=10 }

arg1 returns 10 if arg1 is 1 or less, and I prefer it to raise a error.

EDIT:

This is what I did:

l[] { check "isint($1)" $=arg onfail noarg arg1=10 }
if !($arg1>1) error inv_base_arg fi

Better solution:

l[] { check "${1=10}>0 && isnum($1)" $=arg onfail noarg arg1=10 }
if !($arg1>1)||!isint($arg1) error inv_base_arg fi

And I find that you can do a mixture too:

#@cli rep_element_wise_addition: _base,_append_left_over_digits : (noarg)
#@cli : Performs element-wise addition on images(s) in boundary. Negative numbers are considered the end of number as well. Only horizontal direction are supported, and numbers must be equal or greater to 0 at the right-most edge. Numbers cannot be greater than base-1 or a error will occur.
#@cli : Default values: '_base=10,_append_left_over_digits=0'
rep_element_wise_addition:
skip ${2=0}
l[] { check "${1=10}>0 && isnum($1)" $=arg onfail noarg arg1=10 }
if !isint($arg1)||$arg1<2 error inv_base_arg fi