initial commit [141.14.140.180,mike]
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Spec is one repository plus the options that belong to it - either a command
|
||||
// line invocation or one line of the config file.
|
||||
type Spec struct {
|
||||
Repo string
|
||||
Install string
|
||||
Name string
|
||||
Tag string
|
||||
Asset string
|
||||
Pattern string
|
||||
OS string
|
||||
Arch string
|
||||
Forge string
|
||||
Token string
|
||||
VersionFlag string
|
||||
Pre bool
|
||||
}
|
||||
|
||||
func (s *Spec) set(key, val string) error {
|
||||
switch key {
|
||||
case "install":
|
||||
s.Install = val
|
||||
case "name":
|
||||
s.Name = val
|
||||
case "tag":
|
||||
s.Tag = val
|
||||
case "asset":
|
||||
s.Asset = val
|
||||
case "pattern":
|
||||
s.Pattern = val
|
||||
case "os":
|
||||
s.OS = val
|
||||
case "arch":
|
||||
s.Arch = val
|
||||
case "forge":
|
||||
s.Forge = val
|
||||
case "token":
|
||||
s.Token = val
|
||||
case "version-flag":
|
||||
s.VersionFlag = val
|
||||
case "pre":
|
||||
s.Pre = val != "" && val != "0"
|
||||
default:
|
||||
return fmt.Errorf("unknown key %q (allowed: %s)", key, strings.Join(specKeys, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options typed on the command line win over the config file. Unlike the Perl
|
||||
// version this looks at what was actually given, so a "version-flag=" in the
|
||||
// config file is no longer overwritten by its own default.
|
||||
func (s *Spec) applyGlobals() {
|
||||
for _, k := range specKeys {
|
||||
if !given[k] {
|
||||
continue
|
||||
}
|
||||
var v string
|
||||
switch k {
|
||||
case "install":
|
||||
v = opt.install
|
||||
case "name":
|
||||
v = opt.name
|
||||
case "tag":
|
||||
v = opt.tag
|
||||
case "asset":
|
||||
v = opt.asset
|
||||
case "pattern":
|
||||
v = opt.pattern
|
||||
case "os":
|
||||
v = opt.os
|
||||
case "arch":
|
||||
v = opt.arch
|
||||
case "forge":
|
||||
v = opt.forge
|
||||
case "token":
|
||||
v = opt.token
|
||||
case "version-flag":
|
||||
v = opt.versionFlag
|
||||
case "pre":
|
||||
if !opt.pre {
|
||||
continue // --pre only ever switches on
|
||||
}
|
||||
v = "1"
|
||||
}
|
||||
s.set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Spec) versionFlag() string {
|
||||
if s.VersionFlag != "" {
|
||||
return s.VersionFlag
|
||||
}
|
||||
return opt.versionFlag
|
||||
}
|
||||
|
||||
func (s *Spec) label() string {
|
||||
if s.Name != "" {
|
||||
return parseNames(s.Name, "?")[0].out
|
||||
}
|
||||
r := strings.TrimRight(s.Repo, "/")
|
||||
if r == "" {
|
||||
return "?"
|
||||
}
|
||||
return filepath.Base(r)
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// Config file (--all)
|
||||
// ==============================================================================
|
||||
|
||||
func configPath() string {
|
||||
base := os.Getenv("XDG_CONFIG_HOME")
|
||||
if base == "" {
|
||||
base = filepath.Join(homeDir(), ".config")
|
||||
}
|
||||
return filepath.Join(base, "upd", "tools")
|
||||
}
|
||||
|
||||
// One entry per line: <repo-url> [key=value ...] [pre]
|
||||
func readConfig(path string) ([]*Spec, error) {
|
||||
fh, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read config %s: %w\n"+
|
||||
"Format: one line per tool, e.g.\n"+
|
||||
" https://github.com/sxyazi/yazi install=~/bin name=yazi,ya", path, err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
var specs []*Spec
|
||||
sc := bufio.NewScanner(fh)
|
||||
for ln := 1; sc.Scan(); ln++ {
|
||||
line := strings.TrimSpace(sc.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
tokens := splitTokens(line)
|
||||
s := &Spec{Repo: tokens[0]}
|
||||
for _, t := range tokens[1:] {
|
||||
key, val, ok := strings.Cut(t, "=")
|
||||
if !ok {
|
||||
val = "1" // a bare "pre"
|
||||
}
|
||||
if err := s.set(strings.ToLower(key), val); err != nil {
|
||||
return nil, fmt.Errorf("%s:%d: %w", path, ln, err)
|
||||
}
|
||||
}
|
||||
s.applyGlobals()
|
||||
specs = append(specs, s)
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, fmt.Errorf("cannot read config %s: %w", path, err)
|
||||
}
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
// Whitespace separated, with double quotes around values that contain spaces.
|
||||
func splitTokens(line string) []string {
|
||||
var out []string
|
||||
var cur strings.Builder
|
||||
quoted, escaped, started := false, false, false
|
||||
|
||||
for _, r := range line {
|
||||
switch {
|
||||
case escaped:
|
||||
cur.WriteRune(r)
|
||||
escaped = false
|
||||
case r == '\\' && quoted:
|
||||
escaped = true
|
||||
case r == '"':
|
||||
quoted = !quoted
|
||||
started = true
|
||||
case (r == ' ' || r == '\t') && !quoted:
|
||||
if started {
|
||||
out = append(out, cur.String())
|
||||
cur.Reset()
|
||||
started = false
|
||||
}
|
||||
default:
|
||||
cur.WriteRune(r)
|
||||
started = true
|
||||
}
|
||||
}
|
||||
if started {
|
||||
out = append(out, cur.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// Binary names
|
||||
// ==============================================================================
|
||||
|
||||
// name holds what to look for inside the asset (src) and what to write to disk
|
||||
// (out): "yazi,ya" are two binaries, "yazi:yazi-nightly" renames one.
|
||||
type name struct{ src, out string }
|
||||
|
||||
func parseNames(list, fallback string) []name {
|
||||
var names []name
|
||||
for _, part := range strings.Split(list, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
src, out, ok := strings.Cut(part, ":")
|
||||
if !ok || out == "" {
|
||||
out = src
|
||||
}
|
||||
names = append(names, name{src: src, out: out})
|
||||
}
|
||||
if len(names) == 0 {
|
||||
names = append(names, name{src: fallback, out: fallback})
|
||||
}
|
||||
return names
|
||||
}
|
||||
Reference in New Issue
Block a user