Having said that, I think that the inertia of "good enough" is a real danger to the concept. The new user that uses the less efficient GUI aspects of the interface to learn quickly may not have much incentive to go the next step and work the keyboard oriented workflow into their muscle memory. Also, if you realize that issue, there's also the problem that if the keyboard interface were less used, there would be less incentive to fix issues or add features to it as the application changed. Rightfully, it could become less useful because of less attention. I can tell you that in practice I find it fairly uncommon to see staff using the keyboard capabilities insofar as it's already supported by their GUI applications.
The nice thing is that by using the keyboard to navigate menus, you develop muscle memory for "shortcuts" without really trying. And if you ever get "stuck", not knowing how to complete the sequence, you only have to look at the currently open menu.
Formica doesn't do this, but this could be extended into rudimentary scripting.
This kind of paradigm won't fit all software, for example I don't think it's a good fit for software where typing mildly structured text is the main mode of interaction. It might be mainly useful for shortcut-heavy software.
[0]: The software in question: https://www.formica.cz/
This worked surprisingly well. New users mostly stuck to the menus to discover things, but then gradually switched over to commands for common actions, because they were faster. It also helped that the Command window had context-sensitive help, so once your menu action generated the corresponding command, you could easily open the manual page for it to read up on the details.
PS scripts (and even functions) declare command line arguments up front, and depending on how much time and thought you put into it, you can constrain the arguments quite well. See [0] for overview of the available functionality. A quick TL;DR:
- You can make parameters typed, and PS will handle relevant conversion if the user just types strings in. E.g. you can mark a parameter as [DateTime], and if the user types e.g. "2021-04-01", the script will receive an object that represents the midnight on that day. If the user types in "foobar" instead, they'll get an immediate error[1] telling them the parameter cannot be read as a date.
- Add [] to parameter type (e.g. [DateTime[]] instead of [DateTime]), and now the parameter can accept multiple values (arrays), - but behaves smartly if only one is given.
- You can mark parameters as mandatory. You can mark parameters as positional (i.e. provided without specifying parameter name on invocation). You can mark a parameter as designated "catch-all". You can define aliases for parameters, to allow user to write e.g. -Runs, -Run or -R, and have all three refer to the same parameter.
- You can mark parameters as "accepting values from pipeline" (and define how exactly). Since PS pipes transport typed objects, not raw bytes, this is how you can both set expectations, and write scripts that can flexibly mix pipes with commandline arguments.
- You can define validation for every parameter individually.
- You can group parameters into sets, based on what other parameters are provided, or some custom logic. So e.g. if arguments -A and -B only make sense together, and then -C doesn't do anything, you can encode this cleanly.
- Autocomplete in PowerShell is aware of all these things! It'll hint you appropriately. And you can provide your own autocomplete hints too.
How is all that relevant to this thread? The last point is a spoiler: all these declarations can be extracted from the script, and they provide enough information to build a high quality GUI for the script automatically. PowerShell ISE, that comes with Windows, exploits this to a limited degree - it contains a GUI for running PS cmdlets that lists parameters and their types, makes use of optional/mandatory status, and groups them by parameter sets. There's nothing stopping one from building an equivalent UI generator that would also provide custom widgets based on argument types (e.g. file pickers for paths, calendars for DateTimes, etc.).
--
[0] - https://docs.microsoft.com/en-us/powershell/module/microsoft...
[1] - Errors in PowerShell are a bit user-unfriendly. Niceties like invocation with underlined mistake are surrounded by long error text and small stack trace. I wish they improved on that; even for script developers, most of the error message is quite useless.
TypeScript is not yet expressive enough for direct shell usage, but not much is missing imo (most importantantly a pipe operator).
But I get it. It's the consequence of trying to use the same language for code and command line input. > and < are already used in pretty much every single shell to indicate IO redirection; particularly with >, it would be really dangerous for it to have context-dependent meanings. And then foo -match bar is more CLI-input-friendly than foo.match(bar) or match(foo, bar). You can get a similar experience if you try to use (or design) a Lisp shell. S-expressions are awesome, but having to move the caret back and forth to add/modify structure in your command is too annoying for a shell language. For shells, you essentially want as much appending and as little editing as possible.
And as much as I don't like JavaScript ecosystem, I would like something like TypeScript in the shell. Personally, my main requirements are 1) optionally typed, and 2) piping structured data instead of unstructured text/byte streams.
(And then I'd like a terminal emulator that makes full use of type system in pipes and command arguments.)
--
[0] - They started simple, then they grew, and before I noticed, they've crossed the threshold where, if I were to write them again, I'd use a regular programming language. I think the point in which I started including bits of C# code in the scripts should've been a wakeup call... but on the other hand, isn't it just cool you can drop down to C# in the middle of a script and have it Just Work?
PS C:\> 1/0
Attempted to divide by zero.
At line:1 char:1
+ 1/0
+ ~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
is now: PS C:\> 1/0
RuntimeException: Attempted to divide by zero.
And everywhere else they've tried to make a shorter, clearer default error.