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  }