feat(docs): add --target hugo sync mode for core.help
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
349647d4aa
commit
2e24bb59f6
1 changed files with 170 additions and 3 deletions
|
|
@ -1,12 +1,15 @@
|
|||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
"forge.lthn.ai/core/go/pkg/io"
|
||||
"forge.lthn.ai/core/go/pkg/repos"
|
||||
)
|
||||
|
||||
// Flag variables for sync command
|
||||
|
|
@ -14,6 +17,7 @@ var (
|
|||
docsSyncRegistryPath string
|
||||
docsSyncDryRun bool
|
||||
docsSyncOutputDir string
|
||||
docsSyncTarget string
|
||||
)
|
||||
|
||||
var docsSyncCmd = &cli.Command{
|
||||
|
|
@ -21,7 +25,7 @@ var docsSyncCmd = &cli.Command{
|
|||
Short: i18n.T("cmd.docs.sync.short"),
|
||||
Long: i18n.T("cmd.docs.sync.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun)
|
||||
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun, docsSyncTarget)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +33,7 @@ func init() {
|
|||
docsSyncCmd.Flags().StringVar(&docsSyncRegistryPath, "registry", "", i18n.T("common.flag.registry"))
|
||||
docsSyncCmd.Flags().BoolVar(&docsSyncDryRun, "dry-run", false, i18n.T("cmd.docs.sync.flag.dry_run"))
|
||||
docsSyncCmd.Flags().StringVar(&docsSyncOutputDir, "output", "", i18n.T("cmd.docs.sync.flag.output"))
|
||||
docsSyncCmd.Flags().StringVar(&docsSyncTarget, "target", "php", "Target format: php (default) or hugo")
|
||||
}
|
||||
|
||||
// packageOutputName maps repo name to output folder name
|
||||
|
|
@ -57,13 +62,21 @@ func shouldSyncRepo(repoName string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
||||
// Find or use provided registry
|
||||
func runDocsSync(registryPath string, outputDir string, dryRun bool, target string) error {
|
||||
reg, basePath, err := loadRegistry(registryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch target {
|
||||
case "hugo":
|
||||
return runHugoSync(reg, basePath, outputDir, dryRun)
|
||||
default:
|
||||
return runPHPSync(reg, basePath, outputDir, dryRun)
|
||||
}
|
||||
}
|
||||
|
||||
func runPHPSync(reg *repos.Registry, basePath string, outputDir string, dryRun bool) error {
|
||||
// Default output to core-php/docs/packages relative to registry
|
||||
if outputDir == "" {
|
||||
outputDir = filepath.Join(basePath, "core-php", "docs", "packages")
|
||||
|
|
@ -158,3 +171,157 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hugoOutputName maps repo name to Hugo content section and folder.
|
||||
func hugoOutputName(repoName string) (string, string) {
|
||||
if repoName == "cli" {
|
||||
return "getting-started", ""
|
||||
}
|
||||
if repoName == "core" {
|
||||
return "cli", ""
|
||||
}
|
||||
if strings.HasPrefix(repoName, "go-") {
|
||||
return "go", repoName
|
||||
}
|
||||
if strings.HasPrefix(repoName, "core-") {
|
||||
return "php", strings.TrimPrefix(repoName, "core-")
|
||||
}
|
||||
return "go", repoName
|
||||
}
|
||||
|
||||
// injectFrontMatter prepends Hugo front matter to markdown content if missing.
|
||||
func injectFrontMatter(content []byte, title string, weight int) []byte {
|
||||
if bytes.HasPrefix(bytes.TrimSpace(content), []byte("---")) {
|
||||
return content
|
||||
}
|
||||
fm := fmt.Sprintf("---\ntitle: %q\nweight: %d\n---\n\n", title, weight)
|
||||
return append([]byte(fm), content...)
|
||||
}
|
||||
|
||||
// titleFromFilename derives a human-readable title from a filename.
|
||||
func titleFromFilename(filename string) string {
|
||||
name := strings.TrimSuffix(filepath.Base(filename), ".md")
|
||||
name = strings.ReplaceAll(name, "-", " ")
|
||||
name = strings.ReplaceAll(name, "_", " ")
|
||||
words := strings.Fields(name)
|
||||
for i, w := range words {
|
||||
if len(w) > 0 {
|
||||
words[i] = strings.ToUpper(w[:1]) + w[1:]
|
||||
}
|
||||
}
|
||||
return strings.Join(words, " ")
|
||||
}
|
||||
|
||||
// copyWithFrontMatter copies a markdown file, injecting front matter if missing.
|
||||
func copyWithFrontMatter(src, dst string, weight int) error {
|
||||
if err := io.Local.EnsureDir(filepath.Dir(dst)); err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := io.Local.Read(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
title := titleFromFilename(src)
|
||||
result := injectFrontMatter([]byte(content), title, weight)
|
||||
return io.Local.Write(dst, string(result))
|
||||
}
|
||||
|
||||
func runHugoSync(reg *repos.Registry, basePath string, outputDir string, dryRun bool) error {
|
||||
if outputDir == "" {
|
||||
outputDir = filepath.Join(basePath, "docs-site", "content")
|
||||
}
|
||||
|
||||
var docsInfo []RepoDocInfo
|
||||
for _, repo := range reg.List() {
|
||||
if repo.Name == "core-template" || repo.Name == "core-claude" {
|
||||
continue
|
||||
}
|
||||
info := scanRepoDocs(repo)
|
||||
if info.HasDocs {
|
||||
docsInfo = append(docsInfo, info)
|
||||
}
|
||||
}
|
||||
|
||||
if len(docsInfo) == 0 {
|
||||
cli.Text("No documentation found")
|
||||
return nil
|
||||
}
|
||||
|
||||
cli.Print("\n Hugo sync: %d repos with docs → %s\n\n", len(docsInfo), outputDir)
|
||||
|
||||
for _, info := range docsInfo {
|
||||
section, folder := hugoOutputName(info.Name)
|
||||
target := section
|
||||
if folder != "" {
|
||||
target = section + "/" + folder
|
||||
}
|
||||
fileCount := len(info.DocsFiles) + len(info.KBFiles)
|
||||
if info.Readme != "" {
|
||||
fileCount++
|
||||
}
|
||||
cli.Print(" %s → %s/ (%d files)\n", repoNameStyle.Render(info.Name), target, fileCount)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
cli.Print("\n Dry run — no files written\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
cli.Blank()
|
||||
if !confirm("Sync to Hugo content directory?") {
|
||||
cli.Text("Aborted")
|
||||
return nil
|
||||
}
|
||||
|
||||
cli.Blank()
|
||||
var synced int
|
||||
for _, info := range docsInfo {
|
||||
section, folder := hugoOutputName(info.Name)
|
||||
|
||||
destDir := filepath.Join(outputDir, section)
|
||||
if folder != "" {
|
||||
destDir = filepath.Join(destDir, folder)
|
||||
}
|
||||
|
||||
weight := 10
|
||||
docsDir := filepath.Join(info.Path, "docs")
|
||||
for _, f := range info.DocsFiles {
|
||||
src := filepath.Join(docsDir, f)
|
||||
dst := filepath.Join(destDir, f)
|
||||
if err := copyWithFrontMatter(src, dst, weight); err != nil {
|
||||
cli.Print(" %s %s: %s\n", errorStyle.Render("✗"), f, err)
|
||||
continue
|
||||
}
|
||||
weight += 10
|
||||
}
|
||||
|
||||
if info.Readme != "" && folder != "" {
|
||||
dst := filepath.Join(destDir, "_index.md")
|
||||
if err := copyWithFrontMatter(info.Readme, dst, 1); err != nil {
|
||||
cli.Print(" %s README: %s\n", errorStyle.Render("✗"), err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(info.KBFiles) > 0 {
|
||||
suffix := strings.TrimPrefix(info.Name, "go-")
|
||||
kbDestDir := filepath.Join(outputDir, "kb", suffix)
|
||||
kbDir := filepath.Join(info.Path, "KB")
|
||||
kbWeight := 10
|
||||
for _, f := range info.KBFiles {
|
||||
src := filepath.Join(kbDir, f)
|
||||
dst := filepath.Join(kbDestDir, f)
|
||||
if err := copyWithFrontMatter(src, dst, kbWeight); err != nil {
|
||||
cli.Print(" %s KB/%s: %s\n", errorStyle.Render("✗"), f, err)
|
||||
continue
|
||||
}
|
||||
kbWeight += 10
|
||||
}
|
||||
}
|
||||
|
||||
cli.Print(" %s %s\n", successStyle.Render("✓"), info.Name)
|
||||
synced++
|
||||
}
|
||||
|
||||
cli.Print("\n Synced %d repos to Hugo content\n", synced)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue