github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/test_helpers/docs.go (about) 1 package test_helpers 2 3 import ( 4 "os" 5 "path/filepath" 6 "regexp" 7 "strings" 8 ) 9 10 var punctuationRE = regexp.MustCompile(`[^\w\-\s]`) 11 var linkRE = regexp.MustCompile(`\]\(([\w:/#\-\.]*)\)`) 12 13 type Doc struct { 14 Name string 15 URLs []string 16 path string 17 } 18 19 func (doc Doc) Path(root string) string { 20 return filepath.Join(root, doc.path) 21 } 22 23 type Docs []Doc 24 25 func (d Docs) DocWithName(name string) Doc { 26 for _, doc := range d { 27 if doc.Name == name { 28 return doc 29 } 30 } 31 return Doc{} 32 } 33 34 func (d Docs) DocWithURL(url string) Doc { 35 for _, doc := range d { 36 for _, u := range doc.URLs { 37 if u == url { 38 return doc 39 } 40 } 41 } 42 return Doc{} 43 } 44 45 var DOCS = Docs{ 46 {"index.md", []string{"https://onsi.github.io/ginkgo/"}, "docs/index.md"}, 47 {"MIGRATING_TO_V2.md", []string{"https://onsi.github.io/ginkgo/MIGRATING_TO_V2"}, "docs/MIGRATING_TO_V2.md"}, 48 {"README.md", []string{"https://github.com/onsi/ginkgo", "https://github.com/onsi/ginkgo/blob/master/README.md"}, "README.md"}, 49 } 50 51 type Anchors struct { 52 Docs Docs 53 DocAnchors map[string][]string 54 } 55 56 func (a Anchors) IsResolvable(docName string, link string) bool { 57 var anchorSet []string 58 var expectedAnchor string 59 if strings.HasPrefix(link, "#") { 60 anchorSet = a.DocAnchors[docName] 61 expectedAnchor = strings.TrimPrefix(link, "#") 62 } else { 63 components := strings.Split(link, "#") 64 doc := a.Docs.DocWithURL(components[0]) 65 if doc.Name == "" { 66 //allow external links 67 return true 68 } 69 if len(components) == 1 { 70 //allow links to the doc with no anchor 71 return true 72 } 73 expectedAnchor = components[1] 74 anchorSet = a.DocAnchors[doc.Name] 75 } 76 77 for _, anchor := range anchorSet { 78 if anchor == expectedAnchor { 79 return true 80 } 81 } 82 83 return false 84 } 85 86 func LoadAnchors(docs Docs, rootPath string) (Anchors, error) { 87 out := Anchors{ 88 Docs: docs, 89 DocAnchors: map[string][]string{}, 90 } 91 for _, doc := range docs { 92 anchors, err := loadMarkdownHeadingAnchors(doc.Path(rootPath)) 93 if err != nil { 94 return Anchors{}, err 95 } 96 out.DocAnchors[doc.Name] = anchors 97 } 98 return out, nil 99 } 100 101 func loadMarkdownHeadingAnchors(filename string) ([]string, error) { 102 headings, err := LoadMarkdownHeadings(filename) 103 if err != nil { 104 return nil, err 105 } 106 107 var anchors []string 108 for _, heading := range headings { 109 heading = punctuationRE.ReplaceAllString(heading, "") 110 heading = strings.ToLower(heading) 111 heading = strings.ReplaceAll(heading, " ", "-") 112 anchors = append(anchors, heading) 113 } 114 115 return anchors, nil 116 } 117 118 func LoadMarkdownHeadings(filename string) ([]string, error) { 119 b, err := os.ReadFile(filename) 120 if err != nil { 121 return nil, err 122 } 123 headings := []string{} 124 lines := strings.Split(string(b), "\n") 125 for _, line := range lines { 126 line = strings.TrimLeft(line, " ") 127 line = strings.TrimRight(line, " ") 128 if !strings.HasPrefix(line, "#") { 129 continue 130 } 131 line = strings.TrimLeft(line, "# ") 132 headings = append(headings, line) 133 } 134 return headings, nil 135 } 136 137 func LoadMarkdownLinks(filename string) ([]string, error) { 138 b, err := os.ReadFile(filename) 139 if err != nil { 140 return nil, err 141 } 142 links := []string{} 143 lines := strings.Split(string(b), "\n") 144 145 for _, line := range lines { 146 matches := linkRE.FindAllStringSubmatch(line, -1) 147 for _, match := range matches { 148 if match[1] != "" { 149 links = append(links, match[1]) 150 } 151 } 152 } 153 154 return links, nil 155 }