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