1// Copyright 2019 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build ignore
16
17package main
18
19// print-markdown-links.go prints a sorted list of the link targets in the
20// given .md files (or directories containing .md files).
21//
22// Usage: go run print-markdown-links.go dirname0 filename1 etc
23//
24// For link targets that look like "/file/names" (as opposed to starting with
25// web URLs starting with "http:" or "https:"), they are preceded by a '+' or a
26// '-' depending on whether or not that file or directory exists, assuming that
27// this script is run in the root directory to resolve filename links that
28// start with a slash.
29//
30// Printing web URLs can be skipped entirely by passing -skiphttp
31//
32// For example, running:
33//   go run script/print-markdown-links.go -skiphttp doc | grep "^-"
34// can find broken Markdown links in the documentation.
35
36import (
37	"flag"
38	"fmt"
39	"io"
40	"io/ioutil"
41	"os"
42	"path/filepath"
43	"sort"
44	"strings"
45
46	"gopkg.in/russross/blackfriday.v2"
47)
48
49var skiphttp = flag.Bool("skiphttp", false, `skip "http:" or "https:" links`)
50
51func main() {
52	if err := main1(); err != nil {
53		os.Stderr.WriteString(err.Error() + "\n")
54		os.Exit(1)
55	}
56}
57
58func main1() error {
59	flag.Parse()
60	r := &renderer{
61		links: map[string]struct{}{},
62	}
63
64	for _, arg := range flag.Args() {
65		err := filepath.Walk(arg, func(path string, info os.FileInfo, walkErr error) error {
66			if walkErr != nil {
67				return walkErr
68			}
69			if info.IsDir() || !strings.HasSuffix(path, ".md") {
70				return nil
71			}
72			return visit(r, path)
73		})
74		if err != nil {
75			return err
76		}
77	}
78
79	sorted := make([]string, 0, len(r.links))
80	for k := range r.links {
81		sorted = append(sorted, k)
82	}
83	sort.Strings(sorted)
84
85	for _, s := range sorted {
86		mark := '-'
87		switch {
88		case strings.HasPrefix(s, "http:"), strings.HasPrefix(s, "https:"):
89			if *skiphttp {
90				continue
91			}
92			mark = ':'
93		case (len(s) > 0) && (s[0] == '/'):
94			filename := s[1:]
95			if i := strings.IndexByte(filename, '#'); i >= 0 {
96				filename = filename[:i]
97			}
98			if _, err := os.Stat(filename); err == nil {
99				mark = '+'
100			}
101		}
102		fmt.Printf("%c %s\n", mark, s)
103	}
104	return nil
105}
106
107func visit(r *renderer, filename string) error {
108	src, err := ioutil.ReadFile(filename)
109	if err != nil {
110		return err
111	}
112	blackfriday.Run(src, blackfriday.WithRenderer(r))
113	return nil
114}
115
116type renderer struct {
117	links map[string]struct{}
118}
119
120func (r *renderer) RenderHeader(w io.Writer, n *blackfriday.Node) {}
121func (r *renderer) RenderFooter(w io.Writer, n *blackfriday.Node) {}
122
123func (r *renderer) RenderNode(w io.Writer, n *blackfriday.Node, entering bool) blackfriday.WalkStatus {
124	if entering {
125		if dst := n.LinkData.Destination; len(dst) != 0 {
126			r.links[string(dst)] = struct{}{}
127		}
128	}
129	return blackfriday.GoToNext
130}
131