Skip to content

Add custom formatting functions to --jq similar to those added to --template #10262

@0xdevalias

Description

@0xdevalias

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 --template flag 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: like color, but only emits color to terminals
      • color <style> <input>: colorize input using https://github.com/mgutz/ansi
      • join <sep> <list>: joins values in the list using a separator
      • pluck <field> <list>: collects values of a field from all items in the input
      • tablerow <fields>...: aligns fields in output vertically as a table
      • tablerender: renders fields added by tablerow in place
      • timeago <time>: renders a timestamp as relative to now
      • timefmt <format> <time>: formats a timestamp using Go's Time.Format function
      • truncate <length> <input>: ensures input fits within length
      • hyperlink <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:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions