-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve menu items to allow templated strings #992
Conversation
614e2b1
to
768c436
Compare
Moving this PR back to draft status while I refactor how template data is injected in to desktop. |
…op via JSON. Applied some feedback as well.
5c334d0
to
3195231
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to try this, though I think I have 2 questions.
First, I think this is doing the template expansion in the Desktop process, correct? That feels correct, in that it gets the desktop version, but I think it's going to be harder to pass highly dynamic data through. (Which is mostly only in the daemon) OTOH, maybe we'll drop a bunch of variables, and a template file. 🤷
Second, I think this is doing the templating expansion at each string? Does it make sense to expand once at the initial file read time?
Yes, you're correct the template expansion is handled by desktop process. I hadn't thought about doing that in launcher proper, but it could work and may be a better approach. Launcher proper can easily access more data and handle more dynamic data queries/templates, but the user process could be better suited to handle things like i18n and user-centric data. I like the idea of expanding once - it simplifies some of the design. In order to do that, I'd have to take the JSON from k2 control server, feed the raw JSON bytes to the template parsing, and then unmarshal the expanded output back into JSON. One potential drawback is that any templating error in any field would cause all of the menu data to be treated as invalid, rather than the one field. But I still kind of like desktop being more "dumb" and not having to worry about template expansion, etc. I'll see how easy it is to move some of this to launcher proper. |
Yes, I think that's right. Template expand before the json unmarshal. Template errors would produce bad content, but then again, so does bad json. So 🤷 server has to be careful either way |
c636567
So I tried going down this path, but it has some wrinkles. My biggest concern was this: performing the template parsing/expansion on raw JSON strings means that the replacement strings in the template data must be JSON-compliant, and any special characters must be escaped. I felt that was a weird restriction to put on the template data, and it made me worried that adding this sort of non-standard manipulation of JSON exposes us to various ways we could produce malformed JSON as a result. Having launcher desktop to do the template expansion also enables the default/fallback menu to use templates too. So if we're okay with doing the template expansion in launcher desktop, the second question is should we do it on-demand per-field (how it's working now), or all at once. It probably doesn't make a big difference either way, other than that doing it up front entirely simplifies some design parts where the textParser dependency doesn't need to be passed to as many things. |
Of the 4 choices enumerated (launcher vs desktop and single shot vs per-field) I'm good with whatever you chose. I suspect doing the parsing as a single shot is going to be both simpler, and more powerful, so I'd bias that way. But, it's pretty easy to iterate. |
…ugh the menu data, which reduces dependencies within menu code.
I made the change to expand templates up-front and as a single-pass of the entire menu data. I'm happy with this approach, as it reduces complexity in the desktop process, reduces dependencies of the menu itself, and we get the nice property that any templated data added to menus in the future will automatically get parsed with out additional launcher changes (other than adding new templated fields). |
return "", fmt.Errorf("templateData is nil") | ||
} | ||
|
||
t, err := template.New("menu_template").Parse(text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template dohicky is really cool and from stdlib ❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really clean. Some nits.
return "", fmt.Errorf("could not parse template: %w", err) | ||
} | ||
|
||
var b strings.Builder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this. But... Since Execute takes an io.Writer
, I'd probably move that up to the method call. Then you get to do things like return t.Execute(w, tp.td)
. I suspect callers will get simpler too
Co-authored-by: James Pickett <James-Pickett@users.noreply.github.com>
_, err := os.Stat(menuPath) | ||
|
||
if os.IsNotExist(err) { | ||
if err := r.generateMenuFile(bytes.NewReader(menu.InitialMenu)); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not totally sure what I think. I'll go with it, but it feels a little tangled using menu.InitialMenu
here. I think to use that, we need to add in the defaults. Which was the purpose of calls like newInitialMenuData
.
So this is going to write a partial menu, which then has to get read and have the defaults added in. Is it better to leave this blank and fall through later? I don't know. (And thus feel happy to roll with this)
parser := menu.NewTemplateParser(td) | ||
parsedMenuDataStr, err := parser.Parse(string(menuDataBytes)) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse menu data: %w", err) | ||
} | ||
|
||
// Convert the parsed string back to bytes, which can now be decoded per usual | ||
parsedMenuDataBytes := []byte(parsedMenuDataStr) | ||
|
||
var menu menu.MenuData | ||
if err := json.NewDecoder(bytes.NewReader(parsedMenuDataBytes)).Decode(&menu); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say lets followup, but I think this is all cleaner if Parse
works off io readers and writers. There's a lot of extra buffering
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I'll add this to a follow up PR!
Fixed #988
This PR introduces functionality to the menu which allows templated strings to be used within menu item text fields. This enables launcher to fill in data that k2 may not be able to get, and further customize the experience to the individual launcher instance.
I've limited the template data to a few simple fields. We can always add more to the list to be used in future menus.
The template data is generated by the desktop runner, and applied to either menu updates from the control server, or the default menu. The template expansion is performed on the raw JSON from the control server, so we are able to parse and expand any and all fields, rather than parsing as-needed and per-field. The default menu is created if no there is no existing menu.