github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/mdtogo/cmddocs/cmddocs.go (about) 1 // Copyright 2019 The kpt 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 // http://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 package cmddocs 16 17 import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "os" 22 "path/filepath" 23 "regexp" 24 "strings" 25 26 "golang.org/x/text/cases" 27 "golang.org/x/text/language" 28 ) 29 30 func ParseCmdDocs(files []string) []doc { 31 var docs []doc 32 for _, path := range files { 33 b, err := os.ReadFile(path) 34 if err != nil { 35 fmt.Fprintf(os.Stderr, "%v\n", err) 36 os.Exit(1) 37 } 38 parsedDocs := parse(path, string(b)) 39 40 docs = append(docs, parsedDocs...) 41 } 42 return docs 43 } 44 45 var ( 46 hugoHideDirectives = regexp.MustCompile("(?ms){{% hide %}}.+?{{% /hide %}}") 47 mdTag = regexp.MustCompile(`<!--.+-->`) 48 // Capture content between opening and closing tags like <!--mdtogo:(Variable)(Short)>(Content)<!--mdtogo--> 49 mdtogoTag = regexp.MustCompile(`<!--mdtogo:(\S*)(Short|Long|Examples)-->([\s\S]*?)<!--mdtogo-->`) 50 // Capture content within a tag like <!--mdtogo:(Variable)(Short) (Content)--> 51 mdtogoInternalTag = regexp.MustCompile(`<!--mdtogo:(\S*)(Short|Long|Examples)\s+?([\s\S]*?)-->`) 52 ) 53 54 func parse(path, value string) []doc { 55 pathDir := filepath.Dir(path) 56 _, name := filepath.Split(pathDir) 57 58 name = cleanName(name) 59 60 matches := mdtogoTag.FindAllStringSubmatch(value, -1) 61 matches = append(matches, mdtogoInternalTag.FindAllStringSubmatch(value, -1)...) 62 63 docsByName := make(map[string]doc) 64 var docBuilder doc 65 for _, match := range matches { 66 if match[1] == "" { 67 docBuilder = docsByName[name] 68 docBuilder.Name = name 69 } else { 70 cleanedMatchName := cleanName(match[1]) 71 docBuilder = docsByName[cleanedMatchName] 72 docBuilder.Name = cleanedMatchName 73 } 74 75 switch match[2] { 76 case "Short": 77 val := strings.TrimSpace(cleanUpContent(match[3])) 78 docBuilder.Short = val 79 case "Long": 80 val := cleanUpContent(match[3]) 81 docBuilder.Long = val 82 case "Examples": 83 val := cleanUpContent(match[3]) 84 docBuilder.Examples = val 85 } 86 87 docsByName[docBuilder.Name] = docBuilder 88 } 89 90 var docs []doc 91 for _, parsedDoc := range docsByName { 92 docs = append(docs, parsedDoc) 93 } 94 return docs 95 } 96 97 func cleanName(name string) string { 98 name = cases.Title(language.English).String(name) 99 name = strings.ReplaceAll(name, "-", "") 100 return name 101 } 102 103 func cleanUpContent(text string) string { 104 var lines []string 105 106 text = hugoHideDirectives.ReplaceAllString(text, "") 107 scanner := bufio.NewScanner(bytes.NewBufferString(strings.Trim(text, "\n"))) 108 109 indent := false 110 for scanner.Scan() { 111 line := scanner.Text() 112 if strings.HasPrefix(line, "```") { 113 indent = !indent 114 continue 115 } 116 117 if indent { 118 line = " " + line 119 } 120 121 line = strings.ReplaceAll(line, "`", "` + \"`\" + `") 122 123 if strings.HasPrefix(line, "####") { 124 line = strings.TrimPrefix(line, "####") 125 line = fmt.Sprintf("%s:", strings.TrimSpace(line)) 126 } 127 128 if mdTag.MatchString(line) { 129 continue 130 } 131 132 lines = append(lines, line) 133 } 134 135 return fmt.Sprintf("\n%s\n", strings.Join(lines, "\n")) 136 } 137 138 type doc struct { 139 Name string 140 Short string 141 Long string 142 Examples string 143 } 144 145 func (d doc) String() string { 146 var parts []string 147 148 if d.Short != "" { 149 parts = append(parts, 150 fmt.Sprintf("var %sShort = `%s`", d.Name, d.Short)) 151 } 152 if d.Long != "" { 153 parts = append(parts, 154 fmt.Sprintf("var %sLong = `%s`", d.Name, d.Long)) 155 } 156 if d.Examples != "" { 157 parts = append(parts, 158 fmt.Sprintf("var %sExamples = `%s`", d.Name, d.Examples)) 159 } 160 161 return strings.Join(parts, "\n") + "\n" 162 } 163 164 func Write(docs []doc, dest, license string) error { 165 out := []string{license, ` 166 // Code generated by "mdtogo"; DO NOT EDIT. 167 package ` + filepath.Base(dest) + "\n"} 168 169 for i := range docs { 170 out = append(out, docs[i].String()) 171 } 172 173 if _, err := os.Stat(dest); err != nil { 174 _ = os.Mkdir(dest, 0700) 175 } 176 177 o := strings.Join(out, "\n") 178 return os.WriteFile(filepath.Join(dest, "docs.go"), []byte(o), 0600) 179 }