github.com/errata-ai/vale/v3@v3.4.2/internal/lint/adoc.go (about) 1 package lint 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os/exec" 8 "regexp" 9 "strings" 10 11 "github.com/errata-ai/vale/v3/internal/core" 12 "github.com/errata-ai/vale/v3/internal/nlp" 13 ) 14 15 // NOTE: Asciidoctor converts "'" to "’". 16 // 17 // See #206. 18 var adocSanitizer = strings.NewReplacer( 19 "\u2018", "'", 20 "\u2019", "'", 21 "\u201C", "“", 22 "\u201D", "”", 23 "’", "'", 24 "’", "'") 25 26 // Convert listing blocks of the form `[source,.+]` to `[source]` 27 var reSource = regexp.MustCompile(`\[source,.+\]`) 28 var reComment = regexp.MustCompile(`// .+`) 29 30 func (l *Linter) lintADoc(f *core.File) error { 31 var html string 32 var err error 33 34 exe := core.Which([]string{"asciidoctor"}) 35 if exe == "" { 36 return core.NewE100("lintAdoc", errors.New("asciidoctor not found")) 37 } 38 39 s, err := l.Transform(f) 40 if err != nil { 41 return err 42 } 43 s = adocSanitizer.Replace(s) 44 45 html, err = callAdoc(f, s, exe, l.Manager.Config.Asciidoctor) 46 if err != nil { 47 return core.NewE100(f.Path, err) 48 } 49 50 html = adocSanitizer.Replace(html) 51 body := reSource.ReplaceAllStringFunc(f.Content, func(m string) string { 52 offset := 0 53 if strings.HasSuffix(m, ",]") { 54 offset = 1 55 m = strings.Replace(m, ",]", "]", 1) 56 } 57 // NOTE: This is required to avoid finding matches in block attributes. 58 // 59 // See https://github.com/errata-ai/vale/issues/296. 60 parts := strings.Split(m, ",") 61 size := nlp.StrLen(parts[len(parts)-1]) 62 63 span := strings.Repeat("*", size-2+offset) 64 return "[source, " + span + "]" 65 }) 66 67 body = reComment.ReplaceAllStringFunc(body, func(m string) string { 68 // NOTE: This is required to avoid finding matches in line comments. 69 // 70 // See https://github.com/errata-ai/vale/issues/414. 71 // 72 // TODO: Multiple line comments are not handled correctly. 73 // 74 // https://docs.asciidoctor.org/asciidoc/latest/comments/ 75 parts := strings.Split(m, "//") 76 span := strings.Repeat("*", nlp.StrLen(parts[1])-1) 77 return "// " + span 78 }) 79 80 f.Content = body 81 return l.lintHTMLTokens(f, []byte(html), 0) 82 } 83 84 func callAdoc(_ *core.File, text, exe string, attrs map[string]string) (string, error) { 85 var out bytes.Buffer 86 var eut bytes.Buffer 87 88 var adocArgs = []string{ 89 "-s", 90 "-a", 91 "notitle!", 92 "-a", 93 "attribute-missing=drop", 94 } 95 96 adocArgs = append(adocArgs, parseAttributes(attrs)...) 97 adocArgs = append(adocArgs, []string{"--safe-mode", "secure", "-"}...) 98 99 cmd := exec.Command(exe, adocArgs...) 100 cmd.Stdin = strings.NewReader(text) 101 cmd.Stdout = &out 102 cmd.Stderr = &eut 103 104 if err := cmd.Run(); err != nil { 105 return "", errors.New(eut.String()) 106 } 107 108 return out.String(), nil 109 } 110 111 func parseAttributes(attrs map[string]string) []string { 112 var adocArgs []string 113 114 for k, v := range attrs { 115 entry := fmt.Sprintf("%s=%s", k, v) 116 if v == "YES" { 117 entry = k 118 } else if v == "NO" { 119 entry = k + "!" 120 } 121 adocArgs = append(adocArgs, []string{"-a", entry}...) 122 } 123 124 return adocArgs 125 }