diff options
Diffstat (limited to 'source.go')
| -rw-r--r-- | source.go | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/source.go b/source.go new file mode 100644 index 0000000..577d00a --- /dev/null +++ b/source.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +// Spec is a parsed source as the user wrote it: a git URL and an optional ref. +type Spec struct { + URL string + Ref string +} + +var ( + ghShorthand = regexp.MustCompile(`^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$`) + scpLike = regexp.MustCompile(`^([A-Za-z0-9_.-]+@)?([A-Za-z0-9_.-]+):([^/].*)$`) + fullRev = regexp.MustCompile(`^[0-9a-f]{40}$`) +) + +// parseSpec turns what a user typed into a git URL. It accepts: +// +// owner/repo, github:owner/repo GitHub +// https://…, ssh://…, git://…, file://… +// user@host:path scp-like, as git understands it +// ./path, /path, ~/path a local repository +// +// each optionally followed by #ref (a branch, tag or commit). +func parseSpec(s string) (Spec, error) { + raw, ref, _ := strings.Cut(s, "#") + if raw == "" { + return Spec{}, fmt.Errorf("empty source %q", s) + } + var url string + switch { + case strings.HasPrefix(raw, "github:"): + url = "https://github.com/" + strings.TrimPrefix(raw, "github:") + case strings.Contains(raw, "://"): + // Accept Nix's git+https:// spelling too. + url = strings.TrimPrefix(raw, "git+") + case strings.HasPrefix(raw, "/"), strings.HasPrefix(raw, "."), strings.HasPrefix(raw, "~"): + p := raw + if strings.HasPrefix(p, "~") { + home, err := os.UserHomeDir() + if err != nil { + return Spec{}, err + } + p = filepath.Join(home, strings.TrimPrefix(p, "~")) + } + abs, err := filepath.Abs(p) + if err != nil { + return Spec{}, err + } + url = "file://" + abs + case scpLike.MatchString(raw): + // Nix only takes URLs, and the lock is shared with it. + m := scpLike.FindStringSubmatch(raw) + url = "ssh://" + m[1] + m[2] + "/" + m[3] + case ghShorthand.MatchString(raw): + url = "https://github.com/" + raw + default: + return Spec{}, fmt.Errorf("%q is not a source; use owner/repo, a git URL or a path", s) + } + return Spec{URL: strings.TrimSuffix(url, "/"), Ref: ref}, nil +} + +// defaultSourceName names a source after its only skill, or else after its +// repository. +func defaultSourceName(spec Spec, selected []Skill) string { + if len(selected) == 1 { + return selected[0].Name + } + u := spec.URL + if i := strings.LastIndexAny(u, "/:"); i >= 0 { + u = u[i+1:] + } + return strings.TrimSuffix(u, ".git") +} |
