Add script/print-markdown-links.go
diff --git a/go.mod b/go.mod
index 4ec9849..4671479 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,8 @@
 module github.com/google/wuffs
 
 go 1.12
+
+require (
+	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+	gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2b9acb9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
+gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
diff --git a/script/print-markdown-links.go b/script/print-markdown-links.go
new file mode 100644
index 0000000..9b40ed8
--- /dev/null
+++ b/script/print-markdown-links.go
@@ -0,0 +1,130 @@
+// Copyright 2019 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+package main
+
+// print-markdown-links.go prints a sorted list of the link targets in the
+// given .md files (or directories containing .md files).
+//
+// Usage: go run print-markdown-links.go dirname0 filename1 etc
+//
+// For link targets that look like "/file/names" (as opposed to starting with
+// web URLs starting with "http:" or "https:"), they are preceded by a '+' or a
+// '-' depending on whether or not that file or directory exists, assuming that
+// this script is run in the root directory to resolve filename links that
+// start with a slash.
+//
+// Printing web URLs can be skipped entirely by passing -skiphttp
+//
+// For example, running:
+//   go run script/print-markdown-links.go -skiphttp doc | grep "^-"
+// can find broken Markdown links in the documentation.
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"gopkg.in/russross/blackfriday.v2"
+)
+
+var skiphttp = flag.Bool("skiphttp", false, `skip "http:" or "https:" links`)
+
+func main() {
+	if err := main1(); err != nil {
+		os.Stderr.WriteString(err.Error() + "\n")
+		os.Exit(1)
+	}
+}
+
+func main1() error {
+	flag.Parse()
+	r := &renderer{
+		links: map[string]struct{}{},
+	}
+
+	for _, arg := range flag.Args() {
+		err := filepath.Walk(arg, func(path string, info os.FileInfo, walkErr error) error {
+			if walkErr != nil {
+				return walkErr
+			}
+			if info.IsDir() || !strings.HasSuffix(path, ".md") {
+				return nil
+			}
+			return visit(r, path)
+		})
+		if err != nil {
+			return err
+		}
+	}
+
+	sorted := make([]string, 0, len(r.links))
+	for k := range r.links {
+		sorted = append(sorted, k)
+	}
+	sort.Strings(sorted)
+
+	for _, s := range sorted {
+		mark := '-'
+		switch {
+		case strings.HasPrefix(s, "http:"), strings.HasPrefix(s, "https:"):
+			if *skiphttp {
+				continue
+			}
+			mark = ':'
+		case (len(s) > 0) && (s[0] == '/'):
+			filename := s[1:]
+			if i := strings.IndexByte(filename, '#'); i >= 0 {
+				filename = filename[:i]
+			}
+			if _, err := os.Stat(filename); err == nil {
+				mark = '+'
+			}
+		}
+		fmt.Printf("%c %s\n", mark, s)
+	}
+	return nil
+}
+
+func visit(r *renderer, filename string) error {
+	src, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+	blackfriday.Run(src, blackfriday.WithRenderer(r))
+	return nil
+}
+
+type renderer struct {
+	links map[string]struct{}
+}
+
+func (r *renderer) RenderHeader(w io.Writer, n *blackfriday.Node) {}
+func (r *renderer) RenderFooter(w io.Writer, n *blackfriday.Node) {}
+
+func (r *renderer) RenderNode(w io.Writer, n *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+	if entering {
+		if dst := n.LinkData.Destination; len(dst) != 0 {
+			r.links[string(dst)] = struct{}{}
+		}
+	}
+	return blackfriday.GoToNext
+}