Skip to content
Snippets Groups Projects
main.go 4.32 KiB
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// doc2hugo converts godoc formatted package documentation into Markdown format with a hugo header.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"go.science.ru.nl/doc2md"
)

var (
	verbose = flag.Bool("v", false, "verbose mode")
	outdir  = flag.String("o", ".", "directory where to output generated files")

	// layout control
	showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
	declLinks      = flag.Bool("links", true, "link identifiers to their declarations")

	// The hash format for Github is the default `#L%d`; but other source control platforms do not
	// use the same format. For example Bitbucket Enterprise uses `#%d`. This option provides the
	// user the option to switch the format as needed and still remain backwards compatible.
	srcLinkHashFormat = flag.String("hashformat", "#L%d", "source link URL hash format")

	flgImport = flag.String("import", "", "import path for the package")
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage: doc2hugo [options] package\n")
	flag.PrintDefaults()
	os.Exit(2)
}

func main() {
	flag.Usage = usage
	flag.Parse()

	if flag.NArg() != 1 {
		usage()
	}
	pkgName := flag.Arg(0) // actually path

	config := &doc2md.Config{
		ShowTimestamps:    *showTimestamps,
		DeclLinks:         *declLinks,
		SrcLinkHashFormat: *srcLinkHashFormat,
		Verbose:           *verbose,
		Import:            *flgImport,
		GitRef:            "main",
	}
	doc2md.Funcs["subdir_format"] = func(s string) string {
		// up to first slash is domain, chop off to get relative link
		sl := strings.Index(s, "/")
		return `[` + s[sl+1:] + `](` + path.Base(s) + `)`
	}

	err := filepath.Walk(pkgName,
		func(p string, info os.FileInfo, err error) error {
			if !info.IsDir() {
				return nil
			}

			rel, _ := filepath.Rel(pkgName, p)
			if len(rel) > 2 && strings.HasPrefix(rel, ".") {
				return nil
			}
			imp := config.Import
			defer func() { config.Import = imp; config.SubPackage = "" }()

			if rel != "" && rel != "." {
				// chekc for dir
				config.Import += "/" + rel
				config.SubPackage = rel
			}
			out := path.Join(*outdir, pkgName)
			if config.SubPackage != "" {
				out = path.Join(out, config.SubPackage)
			}

			if err := os.MkdirAll(out, 0777); err != nil {
				return err
			}
			outfile := path.Join(out, "index.md")

			if err != nil {
				return err
			}

			buf := &bytes.Buffer{}
			if err := doc2md.Transform(buf, p, config); err != nil {
				return err
			}

			log.Printf("Writing %s", outfile)
			if rbuf, err := readme(pkgName, rel, buf); err == nil {
				rbuf = hugo(pkgName, rel, rbuf)
				if err := os.WriteFile(outfile, rbuf, 0666); err != nil {
					return err
				}
			} else {
				rbuf := hugo(pkgName, rel, buf.Bytes())
				if err = os.WriteFile(outfile, rbuf, 0666); err != nil {
					return err
				}
			}

			return nil
		})
	if err != nil {
		log.Fatal(err)
	}
}

// if there is a README return the contents.
func readme(p, rel string, godoc *bytes.Buffer) ([]byte, error) {
	if rel != "." {
		p = path.Join(p, rel)
	}

	buf, err := os.ReadFile(path.Join(p, "README.md"))
	if err != nil {
		return nil, err
	}
	rbuf := &bytes.Buffer{}
	rbuf.WriteString("# README\n\n")
	rbuf.Write(buf)

	if godoc.Len() > 10 { // there is go code docs, link to that.
		rbuf.WriteString("\n---------------------\n")
		rbuf.WriteString("# Package documentation {#documentation}\n\n")
		rbuf.Write(godoc.Bytes())
	}
	return rbuf.Bytes(), nil
}

// add hugo meta data.
func hugo(p, rel string, buf []byte) []byte {
	p = path.Clean(p)
	if rel != "." {
		p = path.Join(p, rel)
	}

	now := time.Now().UTC()

	header := &bytes.Buffer{}
	header.WriteString("+++\ntitle = \"")
	header.WriteString(p)
	header.WriteString("\"\n")
	header.WriteString(fmt.Sprintf("%s = \"Package %s.\"\n", "description", p))
	header.WriteString(fmt.Sprintf("%s = \"Package %s.\"\n", "summary", p))
	header.WriteString(fmt.Sprintf("%s = \"/%s\"\n", "url", p))
	header.WriteString(fmt.Sprintf("%s = %s\n", "date", now.Format("2006-01-02")))
	header.WriteString(fmt.Sprintf("%s = \"%s\"\n", "repo", "https://gitlab.science.ru.nl/cncz/go/-/tree/main/"+p))
	// date??
	header.WriteString("+++\n\n")

	header.Write(buf)
	return header.Bytes()
}