//go:build mage package main import ( "fmt" "io" "log/slog" "os" "path/filepath" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/goccy/go-yaml" ) type projectsConfig struct { Projects []project `yaml:"projects"` } type project struct { Name string `yaml:"name"` RepositoryURL string `yaml:"repository_url"` Commit string `yaml:"commit"` Tag string `yaml:"tag"` Branch string `yaml:"branch"` GitReference string `yaml:"git_reference"` DocumentationRoot string `yaml:"documentation_root"` } var ( configFile = "./flow/nanoc.yaml" projectsDirectory = "./flow/content/projects" ) func Projects() error { logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) slog.SetDefault(logger) projects, err := parseProjectsConfig() if err != nil { return fmt.Errorf("unable to parse the projects configuration; %w", err) } for _, project := range projects.Projects { clonedDirectory, err := cloneProject(project) if err != nil { return fmt.Errorf("unable to clone %q; %w", project.Name, err) } documentationDir := filepath.Join(clonedDirectory, project.DocumentationRoot) if err := copyDocumentation(documentationDir, project.Name); err != nil { return fmt.Errorf("unable to copy the documentation from %q; %w", project.Name, err) } } return nil } func parseProjectsConfig() (projectsConfig, error) { slog.Info("Parsing configuration.", "config file", configFile) var config projectsConfig data, err := os.ReadFile(configFile) if err != nil { return config, fmt.Errorf("unable to read %q; %w", configFile, err) } if err := yaml.Unmarshal(data, &config); err != nil { return config, fmt.Errorf("unable to Unmarshal the configuration; %w", err) } slog.Info("Configuration parsed successfully.") return config, nil } func cloneProject(p project) (string, error) { slog.Info("Cloning the project repository.", "repository", p.RepositoryURL) var ( reference plumbing.ReferenceName singleBranch bool depth int ) if p.Tag != "" { reference = plumbing.NewTagReferenceName(p.Tag) singleBranch = false depth = 1 } else if p.Branch != "" { reference = plumbing.NewBranchReferenceName(p.Branch) singleBranch = true depth = 1 } else { reference = plumbing.Main singleBranch = false depth = 0 } cloneDir, err := os.MkdirTemp("", p.Name) if err != nil { return "", fmt.Errorf("unable to create the temporary clone directory; %w", err) } options := git.CloneOptions{ URL: p.RepositoryURL, Progress: nil, ReferenceName: reference, SingleBranch: singleBranch, Depth: depth, } repository, err := git.PlainClone(cloneDir, false, &options) if err != nil { return "", fmt.Errorf("unable to clone the repository; %w", err) } if p.Commit == "" { return cloneDir, nil } worktree, err := repository.Worktree() if err != nil { return "", fmt.Errorf("unable to retrieve the work tree; %w", err) } checkoutOptions := git.CheckoutOptions{ Hash: plumbing.NewHash(p.Commit), Create: false, } if err := worktree.Checkout(&checkoutOptions); err != nil { return "", fmt.Errorf("unable to checkout %q; %w", p.Commit, err) } slog.Info("Project cloned successfully.") return cloneDir, nil } func copyDocumentation(inputDirectory, projectName string) error { outputDirectory := filepath.Join(projectsDirectory, projectName) slog.Info("Copying documentation.", "project", projectName, "source", inputDirectory, "destination", outputDirectory) defer os.RemoveAll(inputDirectory) files, err := os.ReadDir(inputDirectory) if err != nil { return fmt.Errorf("unable to read files from %q; %w", inputDirectory, err) } if len(files) == 0 { return nil } if err := os.MkdirAll(outputDirectory, 0o750); err != nil { return fmt.Errorf("unable to create %q; %w", outputDirectory, err) } for _, file := range files { if file.IsDir() || !strings.HasSuffix(file.Name(), ".asciidoc") { continue } inputPath := filepath.Join(inputDirectory, file.Name()) outputPath := filepath.Join(projectsDirectory, projectName, file.Name()) err := func() error { inputFile, err := os.Open(inputPath) if err != nil { return fmt.Errorf("unable to open %q; %w", inputPath, err) } defer inputFile.Close() outputFile, err := os.Create(outputPath) if err != nil { return fmt.Errorf("unable to create %q; %w", outputPath, err) } defer outputFile.Close() if _, err := io.Copy(outputFile, inputFile); err != nil { return fmt.Errorf( "unable to copy the contents from %q to %q; %w", inputPath, outputPath, err, ) } return nil }() if err != nil { return err } } slog.Info("Documentation copied successfully.") return nil }