Repository navigation
Add custom formatting functions to --jq similar to those added to --template #10262
Copy link
Copy link
Open
Labels
enhancementa request to improve CLIa request to improve CLIneeds-investigationCLI team needs to investigateCLI team needs to investigate
Description
Describe the feature or problem you’d like to solve
When using --template there are a number of extra functions available:
- https://cli.github.com/manual/gh_help_formatting
-
The
--templateflag requires a string argument in Go template syntax, and will only print those JSON values which match the query. In addition to the Go template functions in the standard library, the following functions can be used with this formatting directive:autocolor: likecolor, but only emits color to terminalscolor <style> <input>: colorize input using https://github.com/mgutz/ansijoin <sep> <list>: joins values in the list using a separatorpluck <field> <list>: collects values of a field from all items in the inputtablerow <fields>...: aligns fields in output vertically as a tabletablerender: renders fields added by tablerow in placetimeago <time>: renders a timestamp as relative to nowtimefmt <format> <time>: formats a timestamp using Go'sTime.Formatfunctiontruncate <length> <input>: ensures input fits within lengthhyperlink <url> <text>: renders a terminal hyperlink
-
It could be useful to have equivalents to some of these made available to --jq, which would prevent needing to re-implement them ourselves as I did in #659 (comment) :
eg.
gh api notifications \
--method GET \
-F participating=true \
--jq '
def timeago:
. | fromdateiso8601 | now - . | . / 60 | floor |
if . < 60 then
(. | tostring + " minutes ago")
elif . < 1440 then
(. / 60 | floor | tostring + " hours ago")
elif . < 43200 then
(. / 1440 | floor | tostring + " days ago")
elif . < 525600 then
(. / 43200 | floor | tostring + " months ago")
else
(. / 525600 | floor | tostring + " years ago")
end;
def ljust(width): . + " " * (width - (. | length));
def api_to_web_url:
if test("^https://api.github.com/repos/[^/]+/[^/]+/(issues|pulls)/[0-9]+$") then
sub("^https://api.github.com/repos/"; "https://github.com/")
elif test("^https://api.github.com/repos/[^/]+/[^/]+/issues/comments/[0-9]+$") then
capture("^https://api.github.com/repos/(?<repo>[^/]+/[^/]+)/issues/comments/(?<comment>[0-9]+)$") |
"https://github.com/\(.repo)/issues#issuecomment-\(.comment)"
elif test("^https://api.github.com/repos/") then
sub("^https://api.github.com/repos/"; "https://github.com/")
else
.
end;
def get_status:
if .subject.type == "Issue" then
if .state == "closed" then "🔴 Closed" else "🟢 Open" end
elif .subject.type == "PullRequest" then
if .state == "merged" then "🟣 Merged"
elif .state == "closed" then "🔴 Closed"
elif .draft then "⚪ Draft"
else "🟢 Open" end
else
" -"
end;
def format_item:
[
(.repository.full_name | ljust(25)),
(.subject.title | .[:50] | ljust(50)),
(.subject.type | ljust(12)),
(get_status | ljust(12)),
(.reason | ljust(8)),
(.updated_at | timeago | ljust(15)),
(.subject.url | api_to_web_url)
] | join(" ");
def format_grouped_item:
[
" ", # Indent for grouping
(.subject.title | .[:50] | ljust(50)),
(.subject.type | ljust(12)),
(get_status | ljust(12)),
(.reason | ljust(8)),
(.updated_at | timeago | ljust(15)),
(.subject.url | api_to_web_url)
] | join(" ");
def format_repo_header:
.repository.html_url |
sub("^https://github.com/"; "") |
"\n\u001b[1m\u001b[36m=== \(.) ===\u001b[0m";
# GROUPING SWITCH: Comment out one of these two blocks
# Block 1: No grouping - just format and output
.[] | format_item
# Block 2: With grouping
#group_by(.repository.full_name) |
#map({
# group: .,
# max_time: ([.[].updated_at | fromdateiso8601] | max)
#}) |
#sort_by(-.max_time) |
#.[].group |
#(.[0] | format_repo_header) as $header |
#($header, (.[] | format_grouped_item))' \
--paginate
Proposed solution
It will allow users to have the same expressive functionality available from --template's custom functions, when using --jq instead.
Additional context
See also:
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementa request to improve CLIa request to improve CLIneeds-investigationCLI team needs to investigateCLI team needs to investigate
Activity
[bin] add gh-notifications helper script
[config] gh: add alias: notifications -> call $HOME/.dotfiles/bin/gh-…
heaths commentedon Jan 19, 2025
While I think allowing
--jqto process data prior to--templatementioned in #8415 is great, IMO I don't see much point to adding formatting functions forjq(which may not even provide the same level of support asjq) since they are mainly for formatting, for which--templateis best suited.But what if
ghsupported adding a search path for thejqlibrary, assuming, again, it's actually even that compatible withjq?EDIT: Just checked https://pkg.go.dev/github.com/itchyny/[email protected] against https://github.com/heaths/jq functions and they work, so it does seem it's fairly well implemented...and even some potential improvements e.g., unicode chars instead of bytes.
That said, keeping the behaviors of functions the same written in entirely different grammars seems dubious. If you wanted to preprocess JSON via the
jqgrammar and then format some values you could still use--templateto transform the JSON, format, and emit JSON.Again, just my $0.02US.
heaths commentedon Jan 19, 2025
The
gojqCLI itself does set aModuleLoaderwith the same paths thatjqdoes: https://github.com/itchyny/gojq/blob/ca9fd97cd6247319f0bf999a670814bc1a3b071f/cli/cli.go#L237, so it may be reasonable so people can import their ownjqmodules.It can also implement functions but arguments may need to be parsed into appropriate types: https://pkg.go.dev/github.com/itchyny/[email protected]#WithFunction
Support useful template functions and modules in jq filters
heaths commentedon Jan 20, 2025
I created a couple PRs here and a dependency in the cli/go-gh repo. In addition to adding support for the
timeagoandtimefmtfunctions - the others either don't make sense e.g., color (jq is for emitting data while template formatting is for presentation) or are supported by built-in functions e.g.,join- I added library support which bothjqand thegojq(the CLI for the library used in theghCLI) support. I have some modules that I get some use out of and imagine other people have their own as well.cli/go-gh#178 has to be merged before #10275 can be readied and merged.
williammartin commentedon Jan 24, 2025
Unlike #8415 (comment), I haven't discussed this one with the team but the desire to have some built in formatting functions, and to be able to easily load shared functions is understandable. The approach @heaths has taken seems reasonable to me but fully speccing this out and reviewing it is probably pretty low on our backlog since there are ways to "make it work" already.
That said, the work done in the PR already makes this much more palatable to take on, so hopefully won't languish forever. Cheers.
heaths commentedon Aug 5, 2026
Wanted to circle back and see—after rebasing these PRs—we could still consider 1) allowing
--jqwith--templateas long as it still outputs JSON, and 2) allowing customjqfunctions. I'm trying to update a script-basedghextension and find myself limited by the lack of the first option.williammartin commentedon Aug 5, 2026
Ah jeez @heaths I'd love to get this in for you, we're just so thinly stretched at the moment that I find it hard to commit to it. The huge increase in
ghadoption by agents hasn't made things easier. We're currently onboarding a few people to try and get through our backlog of work a bit faster. In the meantime, let me bring your request to the team again and see if we can get it prioritised.