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
Add table and helper template functions #3519
Conversation
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.
This is going along great! Thanks for working on this ❤️
In practical manual testing, I found this doesn't work with pagination because the template is executed for each page. I have a couple ideas how to handle that, but feel like it might warrant a separate issue and that for most cases this PR is good enough and it provides time for any other user feedback. That said, I can tackle it as part of this PR if you'd prefer. For example, one way is to merge output like built-in commands do, but this might have to be opt-in (perhaps we could scan if "{{table" is included in the template) so there are no backcompat issues if users were expecting a JSON array of results that themselves have a arrays, On a related note, the template is parsed each time it's executed. While that isn't a particular lengthy operation (especially for a short-lived console app), making template execution stateful and parsing once would be more efficient. If/when we solve the multiple template execution issue, that would be a good time (perhaps even necessary time) to refactor that. |
I refactored to use TablePrinter and solved the pagination issue while I was at it by making the template stateful and rendering the TablePrinter after all pages were processed. I tested pagination on a variant of https://heaths.dev/tips/2021/04/22/gh-user.html using the simple template Apart from adding some more test cases, I also did some manual regression testing. (Admittedly it looks pretty long, but I repeated the row to handle the case without labels and not printing "()".) |
I've verified this in a simple terminal installed with xfce on Ubuntu 18.04 as well. Table prints properly both to the terminal as well as to a pipe (less). |
After installing the private build to my ❔ Do you think it'd be fine to add the |
@mislav anything else you'd like me to update? And what do you think about me adding the I've been using my private version for all operations (going through the new code paths or not) and everything has been working well. |
@mislav, @vilmibm is there anything else you'd like me to change? I rebased on master for a few changes that went out in the recent 1.10 release. I had simplified |
Rebased on trunk for conflict in go.mod. |
If it makes things easier to review, I recommend not reviewing the changes since my first iteration. I pulled most of that code out and the diff will be larger and largely unnecessary to review. Reviewing the PR anew will probably be easier. |
@heaths Thanks for keeping the PR up to date. I still think the feature is worthwhile. However, I still haven't been able to test out how this works in real-world scenarios and think about whether I would want some API changes before we ship. I will be leaving for a short vacation for a few weeks. If no other engineers pick this up in the meantime, I will be circling back to this as soon as I come back. 🙇 |
The changes I committed a couple months ago to use the existing table formatter greatly simplified the public API to a single I have a couple examples of use here which not only replicate the built-in Hopefully these can help with practical examples. aliases:
issues: |-
issue list --json number,title,labels,updatedAt --template '{{range .}}{{if .labels}}{{row (printf "#%v" .number | autocolor "green") .title (pluck "name" .labels | join ", " | printf "(%s)" | autocolor "gray+h") (timeago .updatedAt | printf "about %s" | autocolor "gray+h")}}{{else}}{{row (printf "#%v" .number | autocolor "green") .title "" (timeago .updatedAt | printf "about %s" | autocolor "gray+h")}}{{end}}{{end}}'
users: |-
api graphql --paginate
--template '{{range .data.repository.assignableUsers.nodes}}{{if .status}}{{row (autocolor "green" .login) .name (autocolor "gray+h" .email) (autocolor "yellow" .status.message)}}{{else}}{{row (autocolor "green" .login) .name (autocolor "gray+h" .email) ""}}{{end}}{{end}}'
-F owner=':owner' -F repo=':repo' -F name='$1' -f query='
query ($repo: String!, $owner: String!, $name: String!, $endCursor: String) {
repository(name: $repo, owner: $owner) {
assignableUsers(first: 100, after: $endCursor, query: $name) {
nodes {
login
name
email
status {
message
}
},
pageInfo {
hasNextPage
endCursor
}
}
}
}
' With the latter one, even if I provide an empty string e.g. |
It's also a quick and simple way to list labels: gh api /repos/Azure/azure-sdk-for-net/labels --template '{{range .}}{{row .name .color .description}}{{end}}' --paginate What'd be really cool to build on top this is a function to take the HTML color code and turn that into appropriately-colored labels. Even without that, |
To follow up on my comment suggesting you review this anew, it's actually less code if reviewed anew than the PR was originally since I ended up removing a bunch of code. There are more files touched, but most are 1- or 2-liners to change interface implementations to pass in a different parameter type, which should mitigate having to change those signatures as much in the future and may help increase code coverage since you can now mock all of |
Resolved conflicts with upstream. |
@mislav now that you're back, are you able to take a look at this? The majority of changes to the 22 files in the PR are just a couple lines of refactoring if you review the PR anew and not as a diff from when I added my own table formatter, which I took out to use the one you already had. Given that, none of the file diffs are particular large. The largest appears to be documentation updates I made to the usage text. |
Sure I will take a look tomorrow! Thanks for your patience. 🙇 |
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.
This is all very close! I was at first alarmed at the amount of changes, but then I realized that most of them were because a Template now needs access to the IOStream instance, and for good reason, so all call points needed to be edited. 👍
Apart from mostly nitpicky code comments, my last concern is about what would happen if my Go template also emitted some content outside of the row
helper? E.g.
START TABLE
{{range .}}{{row "a" "b"}}{{end}}
END TABLE
some footer content
As it stands right now, it looks like all outputted content will be present early in the document, with the table at the very end. Per example above, "footer content" appearing before the whole table might be counter-intuitive to the caller.
Should we reconsider an explicit helper to render a table? E.g.
START TABLE
{{range .}}{{row "a" "b"}}{{end}}
{{tablerender}}
END TABLE
This comment was marked as spam.
This comment was marked as spam.
I'll look into options regarding the table print order. I was able to repro where the table was always last. If something like '{{tablerender}}' (or perhaps '{{tableend}}' would be more idiomatic) were necessary, I would hope making it optional is okay. With out it, the table would render at the end as it does before any changes. With it, it would render immediately. Any other comments inline. |
@mislav when adding some tests for |
I went with
As an example of using multiple tables, here's an alias I added to my ~/.config/gh/config.yml: aliases:
prcomments: |
pr view $1 --json number,title,reviewDecision,body,assignees,comments --template '{{printf "#%v" .number | autocolor "green"}} {{.title}} ({{autocolor "yellow" .reviewDecision}})
{{.body}}
{{row (autocolor "gray+h" "ASSIGNEE") (autocolor "gray+h" "NAME")}}{{range .assignees}}{{row (autocolor "green" .login) .name}}{{end}}{{endtable}}
{{row (autocolor "gray+h" "COMMENTER") (autocolor "gray+h" "ROLE") (autocolor "gray+h" "COMMENT")}}{{range .comments}}{{row (autocolor "green" .author.login) .authorAssociation .body}}{{end}}' |
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.
This looks good! So as it stands right now, endtable
is optional because every template implicitly ends with an endtable
? I like this behavior because it simplifies table-only templates, but wonder whether the magic-ness of this will come back to bite us. For now, I'm fine with this approach. Thanks for the updates!
Questions for team:
- How do we feel about
row
/endtable
helper names? To drive a point further that they are related, should we call themtablerow
/tablerender
, even if that's more verbose? - Should tables be rendered in TSV format for non-tty output streams instead of as a table? This is the default behavior of table-rendering commands such as
gh issue list
. When rendering from Go templates, I wonder whether this magic-ness would be undesireable. Note that ourcolor
helper always outputs color, and to pay respect to TTY status we have theautocolor
helper.
@mislav I renamed the functions and rebased on trunk, but realized printing a formatted table to TSV will require passing
Is there an advantage to writing a formatted table to a file? |
That said, I do have that change ready as well if that's indeed the direction to go. But would it then make sense to pull back in the |
Resolves PR feedback
With always printing a formatted table, a truncate function would be useful to limit potentially long columns.
So as not to hold this up further, I've made the always-TTYL change and added the |
In my testing I've found that measuring terminal width even when stdout is not attached to the terminal is possible: https://github.com/cli/cli/pull/4146/files#diff-ae00d644eeb217b62415039b66c26ca1ae36b4ac3411c774995bfb9f38ab24d7 |
Platform-specific, though. Do you see any problem with just using MaxInt when redirected? If you're directing to a file, you may not want to be constrained by a smaller terminal width when you can put more in a file. This has been one of my major gripes with PowerShell since I started using it as "Monad": redirecting to a file still uses the terminal width, so you have to pipe to If columns could be too long, users could use the FWIW, my inspiration for this change earlier this year was Docker, which uses tables (but not through a template and cannot be combined with other template functions), and it too emits effectively space-delimited text to a file as to a TTY. So I suppose it's not all that odd. It certainly makes more sense when combined with more tables or headers/footers like in my help topic sample. |
Hoping with these changes it's close to being merged. I use table templates quite often and I think people will like them. That said, through practical use, I found one issue I could either push to this PR or open another if it helps get this PR in sooner. I doubt most people would hit it anyway. If you output multiple lines of text - like comments - the newlines are rendered, which throws the table off (columns still line up, though): I propose a change that effectively "truncates" these lines, where the first new line is effectively replaced with "..." and the test dropped. It would still use The question is where? While I couldn't find any built-in use of the |
That's a great catch. I would vote to disallow any literal newline character appearing in a table cell. It would always ruin a table. How about replacing all |
I've got a change almost done. Just adding test cases. So would you want this to always run for I created a separate One behavioral difference is that I replace "\r" or "\n" with "..." instead of just a space because the text does go on, which is exactly what the ellipsis represents. I don't want to give the impression the first line - if less than |
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.
Fantastic. Thank you for the hard work!!
Resolves #3488