mirror of
https://github.com/ivuorinen/tsm.git
synced 2026-01-26 03:24:01 +00:00
chore: update main.go
This commit is contained in:
207
main.go
207
main.go
@@ -24,11 +24,23 @@ import (
|
|||||||
const (
|
const (
|
||||||
appName = "tsm"
|
appName = "tsm"
|
||||||
defaultTimeout = 6 * time.Second
|
defaultTimeout = 6 * time.Second
|
||||||
cfgDirName = "tsm"
|
|
||||||
cfgBaseName = "config"
|
|
||||||
pageStep = 5 // PgUp/PgDn step
|
pageStep = 5 // PgUp/PgDn step
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ldflags-set at build time by goreleaser or Makefile
|
||||||
|
var (
|
||||||
|
version = "dev"
|
||||||
|
commit = "none"
|
||||||
|
date = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------- Options ----------------
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
ConfigPath string
|
||||||
|
Print bool
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- Config ----------------
|
// ---------------- Config ----------------
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -39,7 +51,6 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultExclude() []string {
|
func defaultExclude() []string {
|
||||||
// Common build/vendor/cache dirs across ecosystems
|
|
||||||
return []string{
|
return []string{
|
||||||
".git", "node_modules", "vendor", "dist", "build", "target", "out", "bin",
|
".git", "node_modules", "vendor", "dist", "build", "target", "out", "bin",
|
||||||
".cache", ".next", ".nuxt", ".pnpm-store", ".yarn", ".yarn/cache",
|
".cache", ".next", ".nuxt", ".pnpm-store", ".yarn", ".yarn/cache",
|
||||||
@@ -59,8 +70,8 @@ func loadConfig(explicit string) (Config, error) {
|
|||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
xdg = filepath.Join(home, ".config")
|
xdg = filepath.Join(home, ".config")
|
||||||
}
|
}
|
||||||
v.AddConfigPath(filepath.Join(xdg, cfgDirName))
|
v.AddConfigPath(filepath.Join(xdg, "tsm"))
|
||||||
v.SetConfigName(cfgBaseName)
|
v.SetConfigName("config")
|
||||||
}
|
}
|
||||||
_ = v.ReadInConfig() // best-effort
|
_ = v.ReadInConfig() // best-effort
|
||||||
var cfg Config
|
var cfg Config
|
||||||
@@ -70,7 +81,7 @@ func loadConfig(explicit string) (Config, error) {
|
|||||||
cfg.Exclude = defaultExclude()
|
cfg.Exclude = defaultExclude()
|
||||||
}
|
}
|
||||||
if cfg.MaxDepth == 0 {
|
if cfg.MaxDepth == 0 {
|
||||||
cfg.MaxDepth = 3 // default per request
|
cfg.MaxDepth = 3
|
||||||
}
|
}
|
||||||
if len(cfg.ScanPaths) == 0 {
|
if len(cfg.ScanPaths) == 0 {
|
||||||
if home, _ := os.UserHomeDir(); home != "" {
|
if home, _ := os.UserHomeDir(); home != "" {
|
||||||
@@ -89,7 +100,7 @@ func xdgConfigPath() (string, error) {
|
|||||||
}
|
}
|
||||||
xdg = filepath.Join(home, ".config")
|
xdg = filepath.Join(home, ".config")
|
||||||
}
|
}
|
||||||
return filepath.Join(xdg, cfgDirName, cfgBaseName+".yaml"), nil
|
return filepath.Join(xdg, "tsm", "config.yaml"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDefaultConfig(w io.Writer) error {
|
func writeDefaultConfig(w io.Writer) error {
|
||||||
@@ -118,7 +129,7 @@ func writeDefaultConfig(w io.Writer) error {
|
|||||||
if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil {
|
if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Wrote default config → %s\n", path)
|
_, _ = fmt.Fprintf(w, "Wrote default config → %s\n", path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,12 +178,11 @@ func sanitize(base string) string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionNameFromPath builds "<parent>_<base>" to avoid collisions.
|
// "<parent>_<base>" — /home/u/Code/ivuorinen/a -> "ivuorinen_a"
|
||||||
// e.g., /home/u/Code/ivuorinen/a -> "ivuorinen_a"
|
|
||||||
func sessionNameFromPath(dir string) string {
|
func sessionNameFromPath(dir string) string {
|
||||||
base := sanitize(filepath.Base(dir))
|
base := sanitize(filepath.Base(dir))
|
||||||
parent := sanitize(filepath.Base(filepath.Dir(dir)))
|
parent := sanitize(filepath.Base(filepath.Dir(dir)))
|
||||||
if parent == "" || parent == "." || parent == string(os.PathSeparator) {
|
if parent == "" || parent == "." || parent == string(filepath.Separator) {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
return parent + "_" + base
|
return parent + "_" + base
|
||||||
@@ -185,21 +195,21 @@ type Shell interface {
|
|||||||
Run(ctx context.Context, name string, args ...string) error
|
Run(ctx context.Context, name string, args ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecShell struct{}
|
type execShell struct{}
|
||||||
|
|
||||||
func (ExecShell) Output(ctx context.Context, name string, args ...string) ([]byte, error) {
|
func (execShell) Output(ctx context.Context, name string, args ...string) ([]byte, error) {
|
||||||
cmd := exec.CommandContext(ctx, name, args...)
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
return cmd.Output()
|
return cmd.Output()
|
||||||
}
|
}
|
||||||
func (ExecShell) Run(ctx context.Context, name string, args ...string) error {
|
func (execShell) Run(ctx context.Context, name string, args ...string) error {
|
||||||
cmd := exec.CommandContext(ctx, name, args...)
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
var shell Shell = ExecShell{}
|
var shell Shell = execShell{}
|
||||||
|
|
||||||
func listTmuxSessions(ctx context.Context) []string {
|
func listTmuxSessions(ctx context.Context) []string {
|
||||||
out, err := shell.Output(ctx, "tmux", "list-sessions", "-F", "#S")
|
out, err := shell.Output(ctx, "tmux", "list-sessions", "-F", "#S")
|
||||||
@@ -239,6 +249,8 @@ func createOrSwitchForDir(ctx context.Context, sess, dir string, inTmux bool) er
|
|||||||
return switchToSession(ctx, sess, inTmux)
|
return switchToSession(ctx, sess, inTmux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInTmux() bool { return os.Getenv("TMUX") != "" }
|
||||||
|
|
||||||
// ---------------- Discovery (concurrent) ----------------
|
// ---------------- Discovery (concurrent) ----------------
|
||||||
|
|
||||||
func expandPath(p string) (string, bool) {
|
func expandPath(p string) (string, bool) {
|
||||||
@@ -321,9 +333,8 @@ func scanGitReposConcurrent(cfg Config) []string {
|
|||||||
return repos
|
return repos
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Simple fuzzy UI ----------------
|
// ---------------- Fuzzy UI ----------------
|
||||||
|
|
||||||
// score: simple subsequence match, higher is better, prefer prefix
|
|
||||||
func fuzzyScore(needle, hay string) int {
|
func fuzzyScore(needle, hay string) int {
|
||||||
if needle == "" {
|
if needle == "" {
|
||||||
return 1
|
return 1
|
||||||
@@ -331,7 +342,7 @@ func fuzzyScore(needle, hay string) int {
|
|||||||
ni, score, streak := 0, 0, 0
|
ni, score, streak := 0, 0, 0
|
||||||
for i := 0; i < len(hay) && ni < len(needle); i++ {
|
for i := 0; i < len(hay) && ni < len(needle); i++ {
|
||||||
if toLower(hay[i]) == toLower(needle[ni]) {
|
if toLower(hay[i]) == toLower(needle[ni]) {
|
||||||
score += 2 + streak // reward streaks
|
score += 2 + streak
|
||||||
ni++
|
ni++
|
||||||
streak++
|
streak++
|
||||||
} else {
|
} else {
|
||||||
@@ -366,8 +377,7 @@ func filterAndRank(items []Item, q string, limit int) []viewItem {
|
|||||||
if it.Path != "" {
|
if it.Path != "" {
|
||||||
key += " " + it.Path
|
key += " " + it.Path
|
||||||
}
|
}
|
||||||
s := fuzzyScore(q, key)
|
if s := fuzzyScore(q, key); s >= 0 {
|
||||||
if s >= 0 {
|
|
||||||
out = append(out, viewItem{Item: it, score: s})
|
out = append(out, viewItem{Item: it, score: s})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,8 +393,6 @@ func filterAndRank(items []Item, q string, limit int) []viewItem {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal UI: minimal raw-mode UI for live filtering.
|
|
||||||
// On Windows, we degrade: read query then print numbered list to select.
|
|
||||||
func interactiveSelect(items []Item) (Item, error) {
|
func interactiveSelect(items []Item) (Item, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
fmt.Println("Query: ")
|
fmt.Println("Query: ")
|
||||||
@@ -403,7 +411,6 @@ func interactiveSelect(items []Item) (Item, error) {
|
|||||||
return cands[n-1].Item, nil
|
return cands[n-1].Item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNIX: ANSI raw-mode small TUI
|
|
||||||
_, restore, err := enableRawMode()
|
_, restore, err := enableRawMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return promptOnce(items)
|
return promptOnce(items)
|
||||||
@@ -416,7 +423,7 @@ func interactiveSelect(items []Item) (Item, error) {
|
|||||||
|
|
||||||
render := func() {
|
render := func() {
|
||||||
clearScreen()
|
clearScreen()
|
||||||
fmt.Printf("tsm — filter (↑/↓, Ctrl-N/P, Enter, Backspace, Ctrl-U clear, Tab preview, Home/End, PgUp/PgDn, Ctrl-C cancel)\n")
|
fmt.Printf("tsm — %s (commit %s) — filter (↑/↓, Ctrl-N/P, Enter, Backspace, Ctrl-U, Tab, Home/End, PgUp/PgDn, Ctrl-C)\n", version, commit)
|
||||||
fmt.Printf("> %s\n\n", query)
|
fmt.Printf("> %s\n\n", query)
|
||||||
cands := filterAndRank(items, query, 30)
|
cands := filterAndRank(items, query, 30)
|
||||||
if idx >= len(cands) {
|
if idx >= len(cands) {
|
||||||
@@ -463,50 +470,45 @@ func interactiveSelect(items []Item) (Item, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return cands[idx].Item, nil
|
return cands[idx].Item, nil
|
||||||
case 21: // Ctrl-U: clear
|
case 21: // Ctrl-U
|
||||||
query = ""
|
query, idx = "", 0
|
||||||
idx = 0
|
case 9: // Tab
|
||||||
case 9: // Tab: toggle preview
|
|
||||||
showPreview = !showPreview
|
showPreview = !showPreview
|
||||||
case 127, 8: // Backspace
|
case 127, 8: // Backspace
|
||||||
if len(query) > 0 {
|
if len(query) > 0 {
|
||||||
query = query[:len(query)-1]
|
query = query[:len(query)-1]
|
||||||
}
|
}
|
||||||
case 27: // ESC… parse arrow/Home/End/PgUp/PgDn
|
case 27:
|
||||||
// Expect sequences like: ESC [ A/B (arrows), ESC [ H/F (home/end),
|
|
||||||
// ESC [ 5 ~ (PgUp), ESC [ 6 ~ (PgDn), ESC [ 1 ~ (Home), ESC [ 4 ~ (End)
|
|
||||||
b1, _ := readKey.ReadByte()
|
b1, _ := readKey.ReadByte()
|
||||||
if b1 != '[' {
|
if b1 != '[' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
b2, _ := readKey.ReadByte()
|
b2, _ := readKey.ReadByte()
|
||||||
switch b2 {
|
switch b2 {
|
||||||
case 'A': // up
|
case 'A':
|
||||||
idx--
|
idx--
|
||||||
case 'B': // down
|
case 'B':
|
||||||
idx++
|
idx++
|
||||||
case 'H', '1': // Home
|
case 'H', '1':
|
||||||
if b2 == '1' {
|
if b2 == '1' {
|
||||||
_, _ = readKey.ReadByte() // expect '~'
|
_, _ = readKey.ReadByte()
|
||||||
}
|
}
|
||||||
idx = 0
|
idx = 0
|
||||||
case 'F', '4': // End
|
case 'F', '4':
|
||||||
if b2 == '4' {
|
if b2 == '4' {
|
||||||
_, _ = readKey.ReadByte() // expect '~'
|
_, _ = readKey.ReadByte()
|
||||||
}
|
}
|
||||||
// set at end after we know candidate count in render
|
|
||||||
cands := filterAndRank(items, query, 30)
|
cands := filterAndRank(items, query, 30)
|
||||||
if len(cands) > 0 {
|
if len(cands) > 0 {
|
||||||
idx = len(cands) - 1
|
idx = len(cands) - 1
|
||||||
}
|
}
|
||||||
case '5': // PgUp
|
case '5':
|
||||||
_, _ = readKey.ReadByte() // consume '~'
|
_, _ = readKey.ReadByte()
|
||||||
idx -= pageStep
|
idx -= pageStep
|
||||||
case '6': // PgDn
|
case '6':
|
||||||
_, _ = readKey.ReadByte() // consume '~'
|
_, _ = readKey.ReadByte()
|
||||||
idx += pageStep
|
idx += pageStep
|
||||||
default:
|
default:
|
||||||
// consume any trailing '~' if present
|
|
||||||
if b2 >= '0' && b2 <= '9' {
|
if b2 >= '0' && b2 <= '9' {
|
||||||
_, _ = readKey.ReadByte()
|
_, _ = readKey.ReadByte()
|
||||||
}
|
}
|
||||||
@@ -516,7 +518,6 @@ func interactiveSelect(items []Item) (Item, error) {
|
|||||||
case 16: // Ctrl-P
|
case 16: // Ctrl-P
|
||||||
idx--
|
idx--
|
||||||
default:
|
default:
|
||||||
// printable?
|
|
||||||
if r >= 32 && r <= 126 {
|
if r >= 32 && r <= 126 {
|
||||||
query += string(r)
|
query += string(r)
|
||||||
}
|
}
|
||||||
@@ -542,12 +543,11 @@ func promptOnce(items []Item) (Item, error) {
|
|||||||
return cands[n-1].Item, nil
|
return cands[n-1].Item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw mode helpers (unix)
|
// Raw mode via stty (no extra deps)
|
||||||
func enableRawMode() (bool, func(), error) {
|
func enableRawMode() (bool, func(), error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return false, func() {}, nil
|
return false, func() {}, nil
|
||||||
}
|
}
|
||||||
// very small shim using stty to avoid cgo/term deps
|
|
||||||
cmd := exec.Command("stty", "-g")
|
cmd := exec.Command("stty", "-g")
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
old, err := cmd.Output()
|
old, err := cmd.Output()
|
||||||
@@ -567,62 +567,23 @@ func enableRawMode() (bool, func(), error) {
|
|||||||
return true, restore, nil
|
return true, restore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearScreen() {
|
func clearScreen() { fmt.Print("\x1b[2J\x1b[H") }
|
||||||
fmt.Print("\x1b[2J\x1b[H")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Main ----------------
|
// ---------------- Orchestrator ----------------
|
||||||
|
|
||||||
func isInTmux() bool { return os.Getenv("TMUX") != "" }
|
func buildItems(ctx context.Context, cfg Config) []Item {
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
flagCfg string
|
|
||||||
flagPrint bool
|
|
||||||
flagInitCfg bool
|
|
||||||
)
|
|
||||||
flag.StringVar(&flagCfg, "config", "", "Explicit config file path")
|
|
||||||
flag.BoolVar(&flagPrint, "print", false, "Print candidates and exit")
|
|
||||||
flag.BoolVar(&flagInitCfg, "init-config", false, "Write default config to XDG path and exit")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if flagInitCfg {
|
|
||||||
if err := writeDefaultConfig(os.Stdout); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := loadConfig(flagCfg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s: config error: %v\n", appName, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Build candidates
|
|
||||||
var items []Item
|
var items []Item
|
||||||
|
|
||||||
// Sessions
|
|
||||||
for _, s := range listTmuxSessions(ctx) {
|
for _, s := range listTmuxSessions(ctx) {
|
||||||
items = append(items, Item{Kind: KindSession, Name: s})
|
items = append(items, Item{Kind: KindSession, Name: s})
|
||||||
}
|
}
|
||||||
// Scan repos concurrently
|
for _, r := range scanGitReposConcurrent(cfg) {
|
||||||
repos := scanGitReposConcurrent(cfg)
|
|
||||||
for _, r := range repos {
|
|
||||||
items = append(items, Item{Kind: KindGitRepo, Name: sessionNameFromPath(r), Path: r})
|
items = append(items, Item{Kind: KindGitRepo, Name: sessionNameFromPath(r), Path: r})
|
||||||
}
|
}
|
||||||
// Bookmarks
|
|
||||||
for _, b := range cfg.Bookmarks {
|
for _, b := range cfg.Bookmarks {
|
||||||
if p, ok := expandPath(b); ok {
|
if p, ok := expandPath(b); ok {
|
||||||
items = append(items, Item{Kind: KindBookmark, Name: sessionNameFromPath(p), Path: p})
|
items = append(items, Item{Kind: KindBookmark, Name: sessionNameFromPath(p), Path: p})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dedup: prefer sessions by name; for G/B use (name,path)
|
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
var uniq []Item
|
var uniq []Item
|
||||||
for _, it := range items {
|
for _, it := range items {
|
||||||
@@ -638,28 +599,80 @@ func main() {
|
|||||||
seen[key] = struct{}{}
|
seen[key] = struct{}{}
|
||||||
uniq = append(uniq, it)
|
uniq = append(uniq, it)
|
||||||
}
|
}
|
||||||
items = uniq
|
return uniq
|
||||||
|
}
|
||||||
|
|
||||||
if flagPrint {
|
func Run(opts Options) error {
|
||||||
|
if opts.Print && opts.ConfigPath == "__init__" {
|
||||||
|
return writeDefaultConfig(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := loadConfig(opts.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: config error: %w", appName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
items := buildItems(ctx, cfg)
|
||||||
|
if opts.Print {
|
||||||
for _, it := range items {
|
for _, it := range items {
|
||||||
fmt.Printf("%s\t%s\t%s\n", it.Kind, it.Name, it.Path)
|
fmt.Printf("%s\t%s\t%s\n", it.Kind, it.Name, it.Path)
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
fmt.Fprintln(os.Stderr, "no candidates")
|
return errors.New("no candidates")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selected, err := interactiveSelect(items)
|
selected, err := interactiveSelect(items)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
inTmux := isInTmux()
|
inTmux := isInTmux()
|
||||||
switch selected.Kind {
|
switch selected.Kind {
|
||||||
case KindSession:
|
case KindSession:
|
||||||
_ = switchToSession(ctx, selected.Name, inTmux)
|
return switchToSession(ctx, selected.Name, inTmux)
|
||||||
case KindGitRepo, KindBookmark:
|
case KindGitRepo, KindBookmark:
|
||||||
_ = createOrSwitchForDir(ctx, selected.Name, selected.Path, inTmux)
|
return createOrSwitchForDir(ctx, selected.Name, selected.Path, inTmux)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- main() ----------------
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
flagCfg string
|
||||||
|
flagPrint bool
|
||||||
|
flagInitCfg bool
|
||||||
|
flagVersion bool
|
||||||
|
)
|
||||||
|
flag.StringVar(&flagCfg, "config", "", "Explicit config file path")
|
||||||
|
flag.BoolVar(&flagPrint, "print", false, "Print candidates and exit")
|
||||||
|
flag.BoolVar(&flagInitCfg, "init-config", false, "Write default config to XDG path and exit")
|
||||||
|
flag.BoolVar(&flagVersion, "version", false, "Print version and exit")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flagVersion {
|
||||||
|
fmt.Printf("tsm %s (commit %s, built %s)\n", version, commit, date)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagInitCfg {
|
||||||
|
if err := writeDefaultConfig(os.Stdout); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Run(Options{
|
||||||
|
ConfigPath: flagCfg,
|
||||||
|
Print: flagPrint,
|
||||||
|
}); err != nil && err.Error() != "cancelled" {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user