Open flowchartsman opened 1 year ago
Thanks for the suggestion, @flowchartsman! It sounds interesting. How, specifically, do you think the script
package should provide support for this idea? Maybe you could give an example of a script
-based program that shows what you have in mind.
So, thinking on it some more, the template execution solution is maybe a bit too "clever"; the mean idea is around inputs and template integration, where maybe you have something functions like Input(inputName string, <options...>)
or InputForm(formName, <options>)
which immediately spawn interactive inputs and return, respectively, equivalents of string
or map[string]string
. These would also store their values in a global user input value where individual user inputs can be accessed through functions attached to all templates.
Then you could do something like:
newSource := InputForm("source",
inputStr("name", "Source Name"),
inputStr("topic", "Output Topic"),
inputSelect("sourcetype", "Source Type", "option1", "option2"))
script.Exec("pulsar-admin sources create --name {{form source name}} --destination-topic {{form source topic}} --type {{form source sourcetype}}").WriteFile("create_"+newSource["name"]+".log)
Where user input can be accessed both through the newSource
variable in code context and {{form source ...}}
in the templates. This is just an untested sketch, of course, but that's the general idea)
You could even conceivably shorten the template portions by tracking a map of named inputs and creating a list of functions for accessing them on template execution so that it becomes
script.Exec("pulsar-admin sources create --name {{source name}} --destination-topic {{source topic}} --type {{source sourcetype}}").WriteFile("create_"+newSource["name"]+".log)
Yet another option would be a two-stage approach with multiple delimiter types, if you wanted to keep user inputs visually separate like:
script.Exec("pulsar-admin sources create --name ${source name} --destination-topic ${source topic} --type ${source sourcetype}").WriteFile("create_"+newSource["name"]+".log)
I think you could do something like your first example in three steps:
pulsar-admin
command in the context of the form datascript.Exec
That's actually more general and flexible than prompting the user for inputs on the fly, isn't it—because then the data for the template could come from many different sources, of which interactive forms might be just one.
One thing I'm having difficulty with is seeing where this interactive form-filling fits in with everything else that script
does. It sounds like it could be a useful add-on, but I'm not sure if there's an obvious way to integrate it into the model of "everything is a method on a pipe". What do you think?
I'm not sure if there's an obvious way to integrate it into the model of "everything is a method on a pipe". What do you think?
Yes, I think the template angle fits a bit more naturally for the "anywhere in the pipe" situation, though it's less in keeping with the functional-style shell pipe analogy, since it introduces side-effects. I'm am trying to think of any good examples from unixland where steps in a pipeline break out to ask for input, but I'm not actually coming up with any, which might be a good argument to have it apart from the pipeline.
Having said that, if you think of this as a library that wants to provide a clean API to do things users might otherwise want to do with shell scripts, then interactivity is still useful to provide, since it's something a lot of users want to have in scripts, but avoid because the boilerplate to do it in a script is kind of a pain. In fact, when I was filing this issue, I was actually inpspired in part by the gum tool, which tries to bridge that gap, and lets script writers do things like COMMIT_TYPE=$(gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore" "revert")
. I can see this being super useful for personal tooling, especially with flag overriding, and if the design is clean, it could really expand the types of programs that could be written with thescript
, without changing the core workflow.
So, while it might not have a home as a pipeline component (other than a source maybe?), interaction could find a place at initialization or between steps as a way to guide the flow of the "script".
It sounds like it could be a useful add-on
It could certainly work as an add-on, but for my part I'd be more inclined to just wrap the package into one of my own to give me access to all of the tools at once, so that I could have a go-to dependency for most of the tasks of this type that I want to write.
the other inspiration is that I wrote a tool recently to fill a similar niche to shell
to let me easily automate repetitive tasks, and I found myself wanting an way to do inputs too, so that I could just reach for that one library any time I needed. I also made the decision to have it be YAML-driven, but I didn't like the requirement of an extra file, when what I really wanted was a simple way to chain things together.
Then something in my memory clicked, and I remembered that shell
exists, so I thought I might propose an extension here instead and get the best of both worlds :)
Edit: I also wanted to add that I definitely see your point regarding the general pipeline nature of the library and keeping that core lean. So if you don't think this kind of extension has a place, no harm no foul. If anything, it can help clarify the mission statement about what is and what is not viable for the package.
It seems clear that there's a fairly common general requirement for 'getting user input as part of a pipeline', even if that's as simple as 'press any key to continue', or as complex as 'select from the following interactive menu'.
I'd like to invite some specific design proposals for this, from whoever's interested or has ideas. The best way to do this is probably to comment with a script
program you'd like to write, involving user input, showing how your proposed API would work in a realistic application.
So I've been doing some Pulsar work recently, and one of the things I wanted to do was to select a list of pulsar connectors to download locally, from the archive site, first by selecting from the available versions, then by selecting from the available connector files. With a goquery PR, which I've been kicking around, that might look something like:
const (
archiveURL = `https://archive.apache.org/dist/pulsar/`
connectorPfx = `https://archive.apache.org/dist/pulsar/pulsar-{{.}}/connectors/`
)
func main() {
pVersions := regexp.MustCompile(`pulsar-(?P<pversion>\d+\.\d+.\d+)/`)
script.Get(archiveURL).GoQuery(`a`).MatchRegexp(pVersions).ChooseOneFrom("{{.pversion}}").
Get(connectorPfx).GoQuery(`a`).ChooseAnyFrom("{{.}}").ExecForEach("curl -sLO {{.}}").Stdout()
}
Sounds interesting! Perhaps we could prototype this with gum
?
I'm absolutely down to give it a shot. Will probably need a couple weeks before some time frees up for OS work, but I'm happy to prototype an implementation!
Perhaps similar in spirit to some aspects of #116, I think you could provide a set of simple, interactive inputs using TUI elements provided by bubbletea and bubbles. There are a couple of different approaches that you could take, or combine. A compelling one is a technique I saw used in chezmoi where templates receive a funcmap that provides functions which, when executed, create a bubbles widget on the fly and interpolate its value. Here's a quick (and dirty) prototype.
Example input/output