Class: Agentilda::GitHub
- Inherits:
-
Object
- Object
- Agentilda::GitHub
- Defined in:
- lib/agentilda/github.rb
Overview
The gh CLI, wrapped thinly.
It is a seam rather than a convenience: every example in the suite injects a double here, so nothing in the tests reaches the network or a real repository.
Constant Summary collapse
- FIELDS =
Fields asked of
gh pr list. %w[number title url headRefName files state isDraft mergedAt].freeze
- VIEW_FIELDS =
Fields asked of
gh pr view, which unlikepr listcan be told about one pull request in another repository. %w[number title url state isDraft mergedAt body].freeze
- REF =
A reference to one pull request: a bare number, a
#-prefixed number, or a full URL to a GitHub pull request or a GitLab merge request. %r{\A(?:\#?\d+|https?://\S+?/(?:pull|merge_requests)/\d+/?)\z}
Class Method Summary collapse
-
.parse_refs(text) ⇒ Array<String>
Split and validate a
--prsvalue before any of it reaches the network, so a typo fails in a hundredth of a second with the offending token named rather than after four round trips with aghdiagnostic. -
.state_label(pr) ⇒ String
ghspeaks in enums;pull-requests.mdspeaks in the words the state machine parses.
Instance Method Summary collapse
-
#available? ⇒ Boolean
Whether
ghis installed and authenticated. -
#initialize(command: TTY::Command.new(printer: :null), limit: 200) ⇒ GitHub
constructor
A new instance of GitHub.
-
#no_output_message ⇒ String
The diagnosis for a silent
gh. -
#pull_request(ref) ⇒ Hash
One pull request, by number or URL.
-
#pull_requests(refs) ⇒ Array<Hash>
Several pull requests, in the order asked for.
-
#pulls(state: "all") ⇒ Array<Hash>
Every pull request, normalised into plain hashes.
-
#retitle(number:, title:) ⇒ void
Change a pull request's title.
Constructor Details
#initialize(command: TTY::Command.new(printer: :null), limit: 200) ⇒ GitHub
Returns a new instance of GitHub.
17 18 19 20 |
# File 'lib/agentilda/github.rb', line 17 def initialize(command: TTY::Command.new(printer: :null), limit: 200) @command = command @limit = limit end |
Class Method Details
.parse_refs(text) ⇒ Array<String>
Split and validate a --prs value before any of it reaches the network,
so a typo fails in a hundredth of a second with the offending token named
rather than after four round trips with a gh diagnostic.
86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/agentilda/github.rb', line 86 def self.parse_refs(text) refs = text.to_s.split(",").map(&:strip).reject(&:empty?) raise Error, "no pull requests given" if refs.empty? bad = refs.reject { |r| r.match?(REF) } unless bad.empty? raise Error, "not a pull request number or URL: #{bad.join(", ")}" end refs.uniq end |
.state_label(pr) ⇒ String
gh speaks in enums; pull-requests.md speaks in the words the state
machine parses. Translate once, here, rather than at each call site.
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/agentilda/github.rb', line 133 def self.state_label(pr) return "Merged 🟣" if pr["mergedAt"] return "WIP 🟡" if pr["isDraft"] case pr["state"].to_s.upcase when "OPEN" then "Open 🟡" when "CLOSED" then "Closed 🔴" else "Unknown" end end |
Instance Method Details
#available? ⇒ Boolean
Returns whether gh is installed and authenticated.
156 157 158 |
# File 'lib/agentilda/github.rb', line 156 def available? @command.run!("gh", "auth", "status").success? end |
#no_output_message ⇒ String
Returns the diagnosis for a silent gh.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/agentilda/github.rb', line 55 def <<~MESSAGE.strip `gh` produced no output and did not report an error. That is almost always authentication rather than an empty repository: - Check `gh auth status`. An invalid GH_TOKEN in the environment shadows a working keyring login and fails without saying so. - A non-interactive shell may have no access to the system keyring even when an interactive one does. Verify with: gh pr list --state all --limit 1 MESSAGE end |
#pull_request(ref) ⇒ Hash
One pull request, by number or URL.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/agentilda/github.rb', line 103 def pull_request(ref) out = @command.run("gh", "pr", "view", ref.to_s, "--json", VIEW_FIELDS.join(",")).out raise Error, if out.to_s.strip.empty? pr = JSON.parse(out) { number: pr["number"], title: pr["title"].to_s, url: pr["url"].to_s, state: self.class.state_label(pr), body: pr["body"].to_s } rescue TTY::Command::ExitError, JSON::ParserError => e raise Error, "could not read pull request #{ref}: #{e..lines.first.to_s.strip}" end |
#pull_requests(refs) ⇒ Array<Hash>
Several pull requests, in the order asked for.
123 124 125 126 |
# File 'lib/agentilda/github.rb', line 123 def pull_requests(refs) UI.stepping(refs, "Fetching pull requests") { |ref| ref } refs.map { |ref| pull_request(ref) } end |
#pulls(state: "all") ⇒ Array<Hash>
Every pull request, normalised into plain hashes.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/agentilda/github.rb', line 26 def pulls(state: "all") out = UI.spinning("Fetching pull requests from GitHub") { @command.run("gh", "pr", "list", "--state", state, "--limit", @limit.to_s, "--json", FIELDS.join(",")).out } # `gh` can exit 0 having printed NOTHING — most often when it cannot reach # the credential store, as in a non-interactive shell that has no keyring # access, or when GH_TOKEN is set to something invalid and shadows a # working login. Left alone this parses as a JSON error and reports as # "bad output", sending you to look at the wrong thing entirely. raise Error, if out.to_s.strip.empty? JSON.parse(out).map do |pr| { number: pr["number"], title: pr["title"].to_s, url: pr["url"], branch: pr["headRefName"].to_s, files: Array(pr["files"]).map { |f| f["path"] }.compact, state: self.class.state_label(pr), open: pr["mergedAt"].nil? && pr["state"].to_s.upcase == "OPEN" } end rescue TTY::Command::ExitError, JSON::ParserError => e raise Error, "could not list pull requests via `gh`: #{e..lines.first.to_s.strip}" end |
#retitle(number:, title:) ⇒ void
This method returns an undefined value.
Change a pull request's title.
149 150 151 152 153 |
# File 'lib/agentilda/github.rb', line 149 def retitle(number:, title:) @command.run("gh", "pr", "edit", number.to_s, "--title", title) rescue TTY::Command::ExitError => e raise Error, "could not retitle ##{number}: #{e..lines.first.to_s.strip}" end |