New Go Module: tychoish/godmenu
A while back, I wrote a little Go wrapper library around the dmenu library and called it go-dmenu but then realized that without the hyphen the ambiguity between god-menu and go-dmenu was sort of delicious so I went with it.
This is not an exciting piece of software, and is just a wrapper that
calls dmenu
in a subprocess and passes it a list of options and returns
to that Go program, the selection provided by the user (via dmenu.)
That’s it. One function, one configuration structure, that’s it, no
dependencies, no more features.
Check the repo tychoish/godmenu, and also the docs.
In some ways that’s the post. I should end here, but…
dmenu
?#
dmenu is this great, super simple piece of software that is just a menu for selecting items from a list. That’s it. You send it a list of selections on standard input, it does it’s thing and returns, on standard output, the selection. That’s it.
It comes with this dmenu_run
script that gets a list of
all the executable files on the PATH
runs whatever
command you select. So it works kind of like a launcher, and I think by
and large, this is the main thing that it’s used for, but it’s so
simple, and so good at doing it’s thing that there’s no reason you
can’t use it for anything menu-ish or selection-ish.
What?#
godmenu
is just a wrapper that lets you do call dmenu
from Go
programs, so that it’s easy to use it from within Go programs, and so
that you don’t need to write shell scripts to use it. There’s sort of
nothing to the library, and maybe that’s the point: better to have it
be the kind of thing that you write once, get it right and then sort of
never have to think about it or do it again.
I’ve been thinking recently about what it means for a library to be useable or possible for a project to adopt or include it, and this is an experiment in a couple of these ideas: it’s simple and focused, provides a single piece of functionality, manages no state, has no dependencies, and makes it easier to do a thing that would otherwise be annoying.
Why?#
A while ago I decided that I was done writing tools for my own personal use as shell scripts, because I was tired of having to fix them after years of not thinking about them, or figuring out what their dependencies were when I got a new computer, and just generally in writing the kind of very defensive code that you end up needing to write.
I’ve gotten pretty good at subprocess management (mostly because of jasper, though the godmenu wrapper doesn’t use jasper to minimize the dependency proliferation,) but adding interactivity directly in a simple program gets very complex quickly. Beyond a certain point, as soon as you you need user input or interaction, writing a little script becomes incredibly complex. Command line only interfaces–subcommands and flags–are simple but there are lots of parsing edge cases and countless competing libraries/frameworks, and when there are lots of options and the “default” options aren’t frequently used, usability falls apart. Richer interfaces–GUI or console widgets–add a lot of technical complexity and overhead (almost all of the time,) and you still have to design the interface.
Using dmenu
selectors in your scripts, means you can (basically)
write a bunch of small commands, stick them in a menu, and get
fuzzy-text selection based on the options you provide. This gets you
pretty far with minimal additional technical or conceptual
complexity.
How?#
It’s a normal Go package, nothing crazy, here’s a minimal example, for a user to select between the first four lowercase ASCII characters.
output, err := godmenu.Do(ctx,
godmenu.Operation{
Selections: []string{"a", "c", "d", "b"},
Sorted: true,
DMenu: &godmenu.Configuration{
Path: godmenu.DefaultDMenuPath,
BackgroundColor: godmenu.DefaultBackgroundColor,
TextColor: godmenu.DefaultTextColor,
Font: "Source Code Pro-13",
Lines: 16,
Prompt: "=>>",
}
},
)
if err != nil {
return err
}
// &etc...
The Do
function returns the string the user selected with the menu,
or the empty string (""
) and the godmenu.ErrSelectionMissing
error.
Interesting options here:
-
the “lines” option makes the number of visable lines the menu uses configurable. I only discovered this option recently and I rather like it.
-
I don’t use it myself, but there’s a
Bottom
boolean option that makes the menu appear at the bottom rather than the top of the screen.
There’s also the Run
method which does the same thing, but uses the
so-called “functional arguments” pattern for a bit more flexibility at
the site. Since dmenu
and godmenu
have some reasonable default
options, you don’t need to specify all the options, or even many of
them, so something like the following would work.
out, err := godmenu.Run(ctx
godmenu.Selections("a", "b", "c", "d"),
godmenu.Prompt("godmenu =>>"),
godmenu.Sorted(),
)
if err != nil {
return err
}
// &etc...
There are also some helpers and aliases among the options provided, to make it easier to call this in various ways:
out, err := godmenu.Do(ctx,
godmenu.ResolveOptions(
godmenu.Items("a", "b", "c"),
godmenu.Prompt("=>"),
godmenu.WithFlags(godmenu.DefaultFlags())))
if err != nil {
return err
}
// or...
out, err = godmenu.Do(ctx, MakeOptions("a", "b", "c"))
if err != nil {
return err
}
// or ...
out, err = godmenu.Run(ctx, WithOptions(MakeOptions("a", "b", "c")))
if err != nil {
return err
}
Though possible, these exact examples might not make a lot of sense,
but it becomes possible to refactor the way you call godmenu
, to reuse
as much of the configuration as possible.
But other than this one operation–“select item from list”–and the ways you can call things… but this that’s it!
What if..?#
Nope.
I’ve been playing with building out some additional tools here, but
I’m disinclined to creep the scope (or the dependencies) of the core
package for this, in the same way that dmenu
itself is unlikely to
ever really gain new features:
-
For sequences of options or “nesting” of menus so that you can use
dmenu
either as a prompt for occasional interaction or simply as an easy way to help users navigate a tree-like structure.I think this is cool, but it feels like something that you could do in downstream code more easily and clearly.
-
I think it’d be cool if you could define a bunch of options, and not have to write the glue code between “the selection” and the option you have. Write the command/options as a map of strings to functions, and then call
dmenu
for the keys in the map and run the options.Seems cool to have routing/dispatching driven by
dmenu
, and totally possible, though again, this is probably shouldn’t be a core feature. -
The one exception to the “no new features ever” rule, might be to have an alternate top-level call that takes configuration as functional arguments rather in the structured form.Done, oops.
Anyway, give it a try!
And, if you thought this was cool, stay tuned, I’ve got a few more blog posts in the works, more thoughts on this kind of “tools for ‘personal computing’” software that I’ve been working on!