mirror of
https://github.com/github/gh-stack.git
synced 2026-09-14 20:26:28 +08:00
38aba1b710
* deprecate prefix functionality * clean up branch auto-naming * update docs * preserve literal hyphens when slugifying branch names
150 lines
4.2 KiB
Go
150 lines
4.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/cli/go-gh/v2/pkg/repository"
|
|
"github.com/cli/go-gh/v2/pkg/term"
|
|
|
|
ghapi "github.com/github/gh-stack/internal/github"
|
|
"github.com/github/gh-stack/internal/theme"
|
|
)
|
|
|
|
// Config holds shared state for all commands.
|
|
type Config struct {
|
|
Terminal term.Term
|
|
Out *os.File
|
|
Err *os.File
|
|
In *os.File
|
|
|
|
ColorSuccess func(string) string
|
|
ColorError func(string) string
|
|
ColorWarning func(string) string
|
|
ColorBold func(string) string
|
|
ColorBlue func(string) string
|
|
ColorMagenta func(string) string
|
|
ColorCyan func(string) string
|
|
ColorGray func(string) string
|
|
|
|
// GitHubClientOverride, when non-nil, is returned by GitHubClient()
|
|
// instead of creating a real client. Used in tests to inject a MockClient.
|
|
GitHubClientOverride ghapi.ClientOps
|
|
|
|
// ForceInteractive, when true, makes IsInteractive() return true
|
|
// regardless of the terminal state. Used in tests.
|
|
ForceInteractive bool
|
|
|
|
// SelectFn, when non-nil, is called instead of prompting via the
|
|
// terminal. Used in tests to simulate interactive selection.
|
|
SelectFn func(prompt, defaultValue string, options []string) (int, error)
|
|
|
|
// ConfirmFn, when non-nil, is called instead of prompting via the
|
|
// terminal. Used in tests to simulate yes/no confirmation prompts.
|
|
ConfirmFn func(prompt string, defaultValue bool) (bool, error)
|
|
|
|
// InputFn, when non-nil, is called instead of prompting via the
|
|
// terminal. Used in tests to simulate text input prompts.
|
|
InputFn func(prompt string) (string, error)
|
|
|
|
// RepoOverride, when non-nil, is returned by Repo() instead of
|
|
// calling repository.Current(). Used in tests to avoid depending on
|
|
// the real git repo context.
|
|
RepoOverride *repository.Repository
|
|
}
|
|
|
|
// New creates a new Config with terminal-aware output and color support.
|
|
func New() *Config {
|
|
terminal := term.FromEnv()
|
|
cfg := &Config{
|
|
Terminal: terminal,
|
|
Out: os.Stdout,
|
|
Err: os.Stderr,
|
|
In: os.Stdin,
|
|
}
|
|
|
|
if terminal.IsColorEnabled() {
|
|
cfg.ColorSuccess = theme.Success
|
|
cfg.ColorError = theme.Error
|
|
cfg.ColorWarning = theme.Warning
|
|
cfg.ColorBold = theme.Bold
|
|
cfg.ColorBlue = theme.Blue
|
|
cfg.ColorMagenta = theme.Magenta
|
|
cfg.ColorCyan = theme.Cyan
|
|
cfg.ColorGray = theme.Gray
|
|
} else {
|
|
noop := func(s string) string { return s }
|
|
cfg.ColorSuccess = noop
|
|
cfg.ColorError = noop
|
|
cfg.ColorWarning = noop
|
|
cfg.ColorBold = noop
|
|
cfg.ColorBlue = noop
|
|
cfg.ColorMagenta = noop
|
|
cfg.ColorCyan = noop
|
|
cfg.ColorGray = noop
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
func (c *Config) Successf(format string, args ...any) {
|
|
fmt.Fprintf(c.Err, "%s %s\n", c.ColorSuccess("\u2713"), fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (c *Config) Errorf(format string, args ...any) {
|
|
fmt.Fprintf(c.Err, "%s %s\n", c.ColorError("\u2717"), fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (c *Config) Warningf(format string, args ...any) {
|
|
fmt.Fprintf(c.Err, "%s %s\n", c.ColorWarning("\u26a0"), fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (c *Config) Infof(format string, args ...any) {
|
|
fmt.Fprintf(c.Err, "%s %s\n", c.ColorCyan("\u2139"), fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (c *Config) Printf(format string, args ...any) {
|
|
fmt.Fprintf(c.Err, format+"\n", args...)
|
|
}
|
|
|
|
func (c *Config) Outf(format string, args ...any) {
|
|
fmt.Fprintf(c.Out, format, args...)
|
|
}
|
|
|
|
// PRLink formats a PR number as a clickable, underlined terminal hyperlink.
|
|
// Falls back to plain "#N" when color is disabled.
|
|
func (c *Config) PRLink(number int, url string) string {
|
|
label := fmt.Sprintf("#%d", number)
|
|
if c.Terminal.IsColorEnabled() {
|
|
if url != "" {
|
|
// OSC 8 hyperlink
|
|
label = fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, label)
|
|
}
|
|
// Underline
|
|
label = fmt.Sprintf("\033[4m%s\033[24m", label)
|
|
}
|
|
return label
|
|
}
|
|
|
|
func (c *Config) IsInteractive() bool {
|
|
return c.ForceInteractive || c.Terminal.IsTerminalOutput()
|
|
}
|
|
|
|
func (c *Config) Repo() (repository.Repository, error) {
|
|
if c.RepoOverride != nil {
|
|
return *c.RepoOverride, nil
|
|
}
|
|
return repository.Current()
|
|
}
|
|
|
|
func (c *Config) GitHubClient() (ghapi.ClientOps, error) {
|
|
if c.GitHubClientOverride != nil {
|
|
return c.GitHubClientOverride, nil
|
|
}
|
|
repo, err := c.Repo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("determining repository: %w", err)
|
|
}
|
|
return ghapi.NewClient(repo.Host, repo.Owner, repo.Name)
|
|
}
|