package readme import ( "fmt" "net/url" "strings" ) // Platform represents a supported Git hosting platform type Platform string const ( PlatformGitHub Platform = "github" PlatformGitLab Platform = "gitlab" PlatformTangled Platform = "tangled" PlatformCodeberg Platform = "codeberg" ) // ParseSourceURL extracts platform, user, and repo from a source repository URL. // Returns ok=false if the URL is not a recognized pattern. func ParseSourceURL(sourceURL string) (platform Platform, user, repo string, ok bool) { if sourceURL == "" { return "", "", "", false } parsed, err := url.Parse(sourceURL) if err != nil { return "", "", "", false } // Normalize: remove trailing slash and .git suffix path := strings.TrimSuffix(parsed.Path, "/") path = strings.TrimSuffix(path, ".git") path = strings.TrimPrefix(path, "/") if path == "" { return "", "", "", false } host := strings.ToLower(parsed.Host) switch host { case "github.com": // GitHub: github.com/{user}/{repo} parts := strings.SplitN(path, "/", 3) if len(parts) < 2 || parts[0] == "" || parts[1] == "" { return "", "", "", false } return PlatformGitHub, parts[0], parts[1], true case "gitlab.com": // GitLab: gitlab.com/{user}/{repo} or gitlab.com/{group}/{subgroup}/{repo} // For nested groups, user = everything except last part, repo = last part lastSlash := strings.LastIndex(path, "/") if lastSlash == -1 || lastSlash == 0 { return "", "", "", false } user = path[:lastSlash] repo = path[lastSlash+1:] if user == "" || repo == "" { return "", "", "", false } return PlatformGitLab, user, repo, true case "tangled.org", "tangled.sh": // Tangled: tangled.org/{user}/{repo} or tangled.sh/@{user}/{repo} (legacy) // Strip leading @ from user if present path = strings.TrimPrefix(path, "@") parts := strings.SplitN(path, "/", 3) if len(parts) < 2 || parts[0] == "" || parts[1] == "" { return "", "", "", false } return PlatformTangled, parts[0], parts[1], true case "codeberg.org": // Codeberg: codeberg.org/{user}/{repo} parts := strings.SplitN(path, "/", 3) if len(parts) < 2 || parts[0] == "" || parts[1] == "" { return "", "", "", false } return PlatformCodeberg, parts[0], parts[1], true default: return "", "", "", false } } // DeriveReadmeURL converts a source repository URL to a raw README URL. // Returns empty string if platform is not supported. func DeriveReadmeURL(sourceURL, branch string) string { platform, user, repo, ok := ParseSourceURL(sourceURL) if !ok { return "" } switch platform { case PlatformGitHub: // https://raw.githubusercontent.com/{user}/{repo}/refs/heads/{branch}/README.md return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/refs/heads/%s/README.md", user, repo, branch) case PlatformGitLab: // https://gitlab.com/{user}/{repo}/-/raw/{branch}/README.md return fmt.Sprintf("https://gitlab.com/%s/%s/-/raw/%s/README.md", user, repo, branch) case PlatformTangled: // https://tangled.org/{user}/{repo}/raw/{branch}/README.md return fmt.Sprintf("https://tangled.org/%s/%s/raw/%s/README.md", user, repo, branch) case PlatformCodeberg: // https://codeberg.org/{user}/{repo}/raw/branch/{branch}/README.md return fmt.Sprintf("https://codeberg.org/%s/%s/raw/branch/%s/README.md", user, repo, branch) default: return "" } }