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") }