// Copyright 2020 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.
// The checkmd command parses Markdown documents and returns an error if any
// relative links or invalid URLs are found.
// Other tests may be added later.
package main
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/russross/blackfriday/v2"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "usage %s filenames\n", os.Args[0])
os.Exit(1)
}
var hasErrs bool
for _, root := range os.Args[1:] {
if isMD(root) {
ok, err := checkFile(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
hasErrs = hasErrs || ok
continue
}
// If the path doesn't appear to be a markdown file assume it's a directory
// and try to walk it.
err := filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error {
if !isMD(fpath) {
return nil
}
ok, err := checkFile(fpath)
if err != nil {
return err
}
hasErrs = hasErrs || ok
return nil
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
if hasErrs {
os.Exit(1)
}
}
func isMD(fpath string) bool {
return strings.HasSuffix(fpath, ".md") || strings.HasSuffix(fpath, ".markdown")
}
func checkFile(fpath string) (bool, error) {
/* #nosec */
md, err := ioutil.ReadFile(fpath)
if err != nil {
return false, err
}
node := blackfriday.New().Parse(md)
var hasErrs bool
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if node.Type != blackfriday.Link || !entering {
return blackfriday.GoToNext
}
u, err := url.Parse(string(node.LinkData.Destination))
if err != nil {
hasErrs = true
fmt.Fprintf(os.Stderr, "link %q is not a valid URL: %v\n", node.LinkData.Destination, err)
return blackfriday.GoToNext
}
if u.Scheme == "" {
hasErrs = true
fmt.Fprintf(os.Stderr, "%s: link is missing the URL scheme: %s\n", fpath, u)
}
return blackfriday.GoToNext
})
return hasErrs, nil
}