204 lines
7.8 KiB
Go
204 lines
7.8 KiB
Go
// Package web constructs GitLink web page URLs and opens browsers.
|
|
//
|
|
// It is the single source of truth for the "terminal ↔ web" mapping used by
|
|
// the `show`, `browse`, and `--web` features. Every CLI command that needs a
|
|
// GitLink web URL must go through Builder instead of hand-rolling string
|
|
// concatenation, so the URL pattern table lives in exactly one place.
|
|
package web
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// DefaultBaseURL is the canonical GitLink front-end host.
|
|
const DefaultBaseURL = "https://gitlink.org.cn"
|
|
|
|
// ResourceURL represents a resolvable GitLink web resource.
|
|
//
|
|
// The JSON tags deliberately match the keys the demo script and AI Agents
|
|
// expect (html_url / resource / show_command), so a Builder result can be
|
|
// wrapped straight into an output envelope.
|
|
type ResourceURL struct {
|
|
URL string `json:"html_url"`
|
|
Resource string `json:"resource"`
|
|
Identifier string `json:"identifier,omitempty"`
|
|
CLICommand string `json:"show_command,omitempty"`
|
|
}
|
|
|
|
// Builder turns (resource type, identifiers) into GitLink web URLs.
|
|
//
|
|
// Builder never resolves owner/repo itself — callers pass them in, normally
|
|
// obtained from internal/context.ResolveOwnerRepo. This keeps Builder a pure
|
|
// formatter with no filesystem or git dependency.
|
|
type Builder struct {
|
|
BaseURL string
|
|
}
|
|
|
|
// NewBuilder returns a Builder pointed at DefaultBaseURL.
|
|
func NewBuilder() *Builder { return &Builder{BaseURL: DefaultBaseURL} }
|
|
|
|
// join concatenates the base URL with a path, tolerating missing/extra slashes.
|
|
func (b *Builder) join(path string) string {
|
|
base := strings.TrimRight(b.BaseURL, "/")
|
|
if path == "" {
|
|
return base
|
|
}
|
|
if !strings.HasPrefix(path, "/") {
|
|
path = "/" + path
|
|
}
|
|
return base + path
|
|
}
|
|
|
|
// make assembles a ResourceURL from its parts.
|
|
func (b *Builder) make(resource, path, identifier, cli string) *ResourceURL {
|
|
return &ResourceURL{
|
|
URL: b.join(path),
|
|
Resource: resource,
|
|
Identifier: identifier,
|
|
CLICommand: cli,
|
|
}
|
|
}
|
|
|
|
// repoBase returns the "/owner/repo" prefix shared by most URLs.
|
|
func repoBase(owner, repo string) string { return fmt.Sprintf("/%s/%s", owner, repo) }
|
|
|
|
// --- Repository-scoped resources -------------------------------------------
|
|
|
|
// RepoURL is the repository home page.
|
|
func (b *Builder) RepoURL(owner, repo string) *ResourceURL {
|
|
return b.make("repo", repoBase(owner, repo), "",
|
|
fmt.Sprintf("gitlink-cli show repo --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// IssueURL is a specific issue page (number > 0) or the issue list.
|
|
func (b *Builder) IssueURL(owner, repo string, number int) *ResourceURL {
|
|
if number <= 0 {
|
|
return b.make("issue", repoBase(owner, repo)+"/issues", "",
|
|
fmt.Sprintf("gitlink-cli show repo --owner %s --repo %s --tab issues", owner, repo))
|
|
}
|
|
return b.make("issue",
|
|
fmt.Sprintf("%s/issues/%d", repoBase(owner, repo), number),
|
|
fmt.Sprintf("#%d", number),
|
|
fmt.Sprintf("gitlink-cli show issue --owner %s --repo %s --number %d", owner, repo, number))
|
|
}
|
|
|
|
// PRURL is a specific pull request page (number > 0) or the PR list.
|
|
func (b *Builder) PRURL(owner, repo string, number int) *ResourceURL {
|
|
if number <= 0 {
|
|
return b.make("pr", repoBase(owner, repo)+"/pulls", "",
|
|
fmt.Sprintf("gitlink-cli show repo --owner %s --repo %s --tab pulls", owner, repo))
|
|
}
|
|
return b.make("pr",
|
|
fmt.Sprintf("%s/pulls/%d", repoBase(owner, repo), number),
|
|
fmt.Sprintf("#%d", number),
|
|
fmt.Sprintf("gitlink-cli show pr --owner %s --repo %s --number %d", owner, repo, number))
|
|
}
|
|
|
|
// WikiURL is a specific wiki page (page != "") or the wiki index.
|
|
func (b *Builder) WikiURL(owner, repo, page string) *ResourceURL {
|
|
if page == "" {
|
|
return b.make("wiki", repoBase(owner, repo)+"/wiki", "",
|
|
fmt.Sprintf("gitlink-cli show wiki --owner %s --repo %s", owner, repo))
|
|
}
|
|
encoded := url.PathEscape(page)
|
|
return b.make("wiki",
|
|
fmt.Sprintf("%s/wiki/%s", repoBase(owner, repo), encoded),
|
|
page,
|
|
fmt.Sprintf("gitlink-cli show wiki --owner %s --repo %s --page %s", owner, repo, page))
|
|
}
|
|
|
|
// MemberURL is the repository collaborator settings page.
|
|
func (b *Builder) MemberURL(owner, repo string) *ResourceURL {
|
|
return b.make("member", repoBase(owner, repo)+"/settings/collaboration", "",
|
|
fmt.Sprintf("gitlink-cli show member --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// WebhookURL is the repository webhook settings page.
|
|
func (b *Builder) WebhookURL(owner, repo string) *ResourceURL {
|
|
return b.make("webhook", repoBase(owner, repo)+"/settings/hooks", "",
|
|
fmt.Sprintf("gitlink-cli show webhook --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// LabelURL is the repository issue-labels management page.
|
|
func (b *Builder) LabelURL(owner, repo string) *ResourceURL {
|
|
return b.make("label", repoBase(owner, repo)+"/issues/labels", "",
|
|
fmt.Sprintf("gitlink-cli show label --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// MilestoneURL is the repository milestones page.
|
|
func (b *Builder) MilestoneURL(owner, repo string) *ResourceURL {
|
|
return b.make("milestone", repoBase(owner, repo)+"/milestones", "",
|
|
fmt.Sprintf("gitlink-cli show milestone --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// BranchURL is the branches list, or a specific branch page when branch != "".
|
|
func (b *Builder) BranchURL(owner, repo, branch string) *ResourceURL {
|
|
if branch == "" {
|
|
return b.make("branch", repoBase(owner, repo)+"/branches", "",
|
|
fmt.Sprintf("gitlink-cli show branch --owner %s --repo %s", owner, repo))
|
|
}
|
|
return b.make("branch",
|
|
fmt.Sprintf("%s/branches/%s", repoBase(owner, repo), url.PathEscape(branch)),
|
|
branch,
|
|
fmt.Sprintf("gitlink-cli show branch --owner %s --repo %s --branch %s", owner, repo, branch))
|
|
}
|
|
|
|
// ReleaseURL is the releases list, or a specific release when tag != "".
|
|
func (b *Builder) ReleaseURL(owner, repo, tag string) *ResourceURL {
|
|
if tag == "" {
|
|
return b.make("release", repoBase(owner, repo)+"/releases", "",
|
|
fmt.Sprintf("gitlink-cli show release --owner %s --repo %s", owner, repo))
|
|
}
|
|
return b.make("release",
|
|
fmt.Sprintf("%s/releases/%s", repoBase(owner, repo), url.PathEscape(tag)),
|
|
tag,
|
|
fmt.Sprintf("gitlink-cli show release --owner %s --repo %s --tag %s", owner, repo, tag))
|
|
}
|
|
|
|
// CommitURL is a specific commit page.
|
|
func (b *Builder) CommitURL(owner, repo, sha string) *ResourceURL {
|
|
if sha == "" {
|
|
return b.make("commit", repoBase(owner, repo)+"/commits", "",
|
|
fmt.Sprintf("gitlink-cli show repo --owner %s --repo %s --tab commits", owner, repo))
|
|
}
|
|
return b.make("commit",
|
|
fmt.Sprintf("%s/commits/%s", repoBase(owner, repo), url.PathEscape(sha)),
|
|
sha,
|
|
fmt.Sprintf("gitlink-cli show commit --owner %s --repo %s --sha %s", owner, repo, sha))
|
|
}
|
|
|
|
// CIURL is the repository CI/Actions page.
|
|
func (b *Builder) CIURL(owner, repo string) *ResourceURL {
|
|
return b.make("ci", repoBase(owner, repo)+"/actions", "",
|
|
fmt.Sprintf("gitlink-cli show ci --owner %s --repo %s", owner, repo))
|
|
}
|
|
|
|
// CompareURL is the branch/tag/commit comparison page.
|
|
func (b *Builder) CompareURL(owner, repo, base, head string) *ResourceURL {
|
|
path := fmt.Sprintf("%s/compare/%s...%s", repoBase(owner, repo), url.PathEscape(base), url.PathEscape(head))
|
|
return b.make("compare", path,
|
|
fmt.Sprintf("%s...%s", base, head),
|
|
fmt.Sprintf("gitlink-cli show compare --owner %s --repo %s --base %s --head %s", owner, repo, base, head))
|
|
}
|
|
|
|
// --- Global resources (no owner/repo) --------------------------------------
|
|
|
|
// NotificationURL is the platform notification center.
|
|
func (b *Builder) NotificationURL() *ResourceURL {
|
|
return b.make("notification", "/notifications", "", "gitlink-cli show notification")
|
|
}
|
|
|
|
// OrgURL is an organization page.
|
|
func (b *Builder) OrgURL(org string) *ResourceURL {
|
|
return b.make("org", fmt.Sprintf("/%s", org), org,
|
|
fmt.Sprintf("gitlink-cli show org --name %s", org))
|
|
}
|
|
|
|
// UserURL is a user profile page.
|
|
func (b *Builder) UserURL(login string) *ResourceURL {
|
|
return b.make("user", fmt.Sprintf("/%s", login), login,
|
|
fmt.Sprintf("gitlink-cli show user --login %s", login))
|
|
}
|