Modular Raku Command Line Apps

It was a three weeks before Christmas and Santa’s workshop was a mess. Elves were running around trying to get everything ready and it didn’t look like anything would be.

As soon as Santa walked in he was surrounded by a horde of unhappy elves all complaining at once.

“The system is too slow!” one piped up “It takes a second just to print out a help file.”

“And the help file is wrong! It’s out of date.” said a second.

“I just found the command to add move a child from the Naughty to Nice would except something else and die silently!!!” a third and extremely harrased voice, belonging to an elf with a very long list broke through.

Santa was worried, he’d spent much of the year working with a lot of the Dev-elfs to update their older systems to use Raku and they thought everything was going swimingly. All the different modules worked really well in testing and the single command to manage the various system was the only section he’d not really been involved in.

He looked around for Snowneth the Elf in charge of that project for some answers but couldn’t see him. A quick set of questions and Santa found out Snowneth had come down with Elf flu a few months ago and was still of sick.

Santa kicked himself for not keeping up with things, but there was so much to do, he made a note to pop in and check on Snowneth later. Now to find out what had gone wrong and why no one had told him!

He opened up his laptop and tried out the main system service in test mode :

helper -?

A couple of seconds later the system gave him a command line prompt… nothing else.

helper -h

This time he got a documentation page, a quick scan of it and he could see a number of the commands were documented incorrectly and there were at least two more recent ones missing. Fearing what he would see he opened up the git log, all the commits had been done by a number of names he recognised. The junior Dev-elfs who had been assigned to Snowneth’s team.

He closed his laptop and went to find a coffee machine.

A few hours, and coffees, later, Santa and a number of young worried looking elves were huddled in a small office. Santa had his laptop open and the code for the service script was open in front of them, some of the 1000 lines of it.

By now Santa and found out what had happened, Snoweth had gone sick and everyone had thought someone else was going to appoint a new team lead. Meanwhile the juniors did their best to get the job done. Santa took a moment to get his breath and compose his voice.

“Firstly I have to apologise. I’m sure you’ve all be under a lot of stress and you’ve all done your best to get this vital work done.”

The juniors perked up, by now they’d all got the message that their work was causing the workshop to run slow and maybe even cancel Christmas! They had been expecting to be shouted at, maybe even sent to work mucking out the reindeer.

The traditional job for an elf who had messed up.

“We can get into what happened later, for now we need to fix this code as fast as possible and get things unblocked. I’m hoping you can all help me to get this done.”

The juniors by now where nodding and smiling, Santa was happy, he should have been on top of this mess and he hoped by getting them involved in the clean up they could regain some confidence.

“Right, so what are the problems we’ve got?”

Everyone spoke up and they quickly whiteboarded a list :

Slow to start for any command

Some commands have incorrect input validation

Out of Date documentation

There were a few other things but there three seemed to be the main ones.

“Ok. So slow startup. I think this is obvious right?”

All the juniors looked at him, then one raised his hand.

“Sniffy isn’t it? What do you think? And no need to raise your hand, just speak up… In turn try not to shout over each other.”

“Is it because the script is so long? I think I read that a Raku script is compiled when you call it? If we make the script shorter it will run faster?”

“That’s pretty much it yes, though less complex would be maybe a better goal. We’ve got a lot of if clauses and the like. The important thing to remember is Module code is precompiled but the main script isn’t. So wherever possible we should be using modules.”

The junior nodded their heads. Then one spoke up.

“But we are using the Modules, they are all there at the top of the script but then we have to work out what command people are calling then work out what arguments they are passing. Then…” Santa held up his hand.

“I can see that Wibbly. I think that’s where we should start.” He pointed to a section of code on the screen. “Who can tell me what’s wrong with this.”

use Workshop::ListController::Nice; my $command = @*ARGV[0]; if ( $command ~~ 'list' ) { my $list-command = @*ARGV[1]; if ( $list-command ~~ 'add' ) { if ( @*ARGV[2] ~~ 'nice' ) { Workshop::ListController::Nice.add-child( @*ARGV[3] ); }

And so on. The juniors looked at it and talked among themselves. Sniffy spoke up again.

“It’s a bit complicated, it didn’t start that way but as we added commands it got bigger. Also we’re not checking the childs name is valid”

“That’s true but what I wondered was why are you reading @*ARGS?”

There was a look of confusion on their faces.

“Why don’t we have a MAIN sub routine?”

Still confusion.

“Ok. You all go look that up. I’lll whip up an example.”

He quickly typed while the elves went to the Raku Docs site and started searching. As he heard their exclamations raise then slowly quieten he’d turned away from the keyboard.

use Workshop::ListController::Nice; multi sub MAIN( "list", "add", "nice", ValidChildName $child ) { Workshop::ListController::Nice.add-child( @*ARGV[3] ); } multi sub MAIN( "list", "add", "nice", Str $invalid-child ) { die "Invalid childname {$invalid-child}"; }

There was a round of applause from the juniors.

“So we can use the Raku multi dispatch with MAIN to create all our commands down to a quite granular level and also have type checking in place. I think this is a good place to start. I’d like us to get to the point where all we have in this script is MAIN off we go.”

The next day and things were looking better, the script was faster to start and the input validation issues had been resolved. Everyone was feeling a lot better. Santa had even got the time to see Snoweth and make sure he was OK.

“Ok… Now lets look at this.”

He pointed at the largest subroutine left in the file, it started like this.

multi sub MAIN( :$h where so * ) { say "Usage:"; say " helper list add nice [child] : Adds a child to the Nice list." say " helper stable rota list : Lists the current rota for cleaning the stables."

One of the elves jumped to her feet, eyes sparkling.

“Gimlina I believe you have some thoughts on this.”

“We should use declarator blocks!” Santa smiled, he’d had to hold back the young elf’s enthusiasm yesterday in order to keep the team focused on the task in hand. He smiled and nodded.

“Carry on.”

“If we add declarator blocks to our subroutines and arguments we get pregenerated documentaion for free. And it gets updated whenever the code changes.”

“Can you give a demonstration?”

She smiled and brought up some code.

#| Add a child to the nice list multi sub MAIN( "list", "add", "nice", ValidChildName $child #= Child name to add ) { Workshop::ListController::Nice.add-child( @*ARGV[3] ); } multi sub MAIN( "list", "add", "nice", Str $invalid-child ) is hidden-from-USAGE { die "Invalid childname {$invalid-child}"; }

“So the #| does what?”

“Attaches the block to whatever comes after it. #= attaches the block to the preceding item.”

“And is hidden-from-USAGE?”

“Well when you call the script and it doesn’t know what to do, or you pass -? it calls the USAGE function and that displays the $*USAGE string. Which is generated from the declarator blocks. But some subs you don’t want to display, so you can hide them.”

She quickly typed

helper -? Usage: helper list add nice [child] -- Add a child to the nice list Child name to add

Santa nodded and the rest of the juniors burst into enthusiastic cheers. Gimlina looked happy as they all turned to adding documentation to the helper script.

Things were looking better, the workshop was sounding a lot happier and the documentation was going well. Santa was sitting in with the juniors just checking final things over when he saw a merge request in one of the module repositories. He looked confused, they’d been in code freeze for a week now and only urgent bug fixes should be raised and he’d not heard of any issues beyond the helper script. He cursed himself again and thought that maybe he did need some product manager elves.

When he opened the merge request his eyes widened, he turned to one of the quieter juniors, they were very smart but tended to not speak up, just kept their head down and worked on things.

“Erry? What’s the MR in the Workshop::ListController::Nice module?”

As he asked, and waited on them to come over he took a look at the code, his eyes now so wide they looked like they would bulge out of his head.

#| Add a child to the nice list multi sub MAIN is export(:MAIN) ( "list", "add", "nice", ValidChildName $child #= Child name to add ) { Workshop::ListController::Nice.add-child( @*ARGV[3] ); } multi sub MAIN( "list", "add", "nice", Str $invalid-child ) is hidden-from-USAGE is export(:MAIN) { die "Invalid childname {$invalid-child}"; }

“Well I was thinking sir” the elf, in a suprisingly loud voice, said from next to him. “You said the module code is precompiled. So if we moved our MAIN subs into the modules they’d be precompiled.”

“And the module teams could manage their own command line interfaces and documentation!” Santa exclaimed happily, Erry nodded smiling.

The rest of the day was a riot of work as all the other dev-elfs (who had been looking forward to a nice month long code freeze before the post holiday backlog fighting) got pulled in and told the new plan.

By the end of the day the helper script was a long line of module use statements and one solitary function.

use Workshop::ListController::Nice :MAIN; #| Display the help multi sub MAIN( :h($help) where so * ) { say $*USAGE; }

Santa looked at it and at Snowneth who had finally made it back to the workshop that afternoon. He shrugged.

“I didn’t know about -? and I have got used to used to -h . Can’t hurt right?”

Santa nodded and they went off to help work in the workshop.