package main import ( "bufio" "bytes" "flag" "os" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) func main() { sourceFlag := flag.Bool("source", false, "include location of linter code that failed assertion") quiet := flag.Bool("quiet", false, "only print failed assertion but no pass information") flag.Parse() var err error pass := true for _, filename := range flag.Args() { var filePass bool filePass, err = checkFile(filename, guessPluginType(filename), *sourceFlag, *quiet) if err != nil { panic(err) } pass = pass && filePass } if !pass { os.Exit(1) } } type ruleFunc func(*T, ast.Node) error type rulesMap map[plugin][]ruleFunc var rules rulesMap func init() { rules = make(rulesMap) // Rules for all plugin types all := []ruleFunc{ firstSection, metadata, configSection, relativeTelegrafLinks, noLongLinesInParagraphs(80), } for i := pluginInput; i <= pluginParser; i++ { rules[i] = all } // Rules for input plugins rules[pluginInput] = append(rules[pluginInput], []ruleFunc{ requiredSectionsClose([]string{ "Example Output", "Metrics", "Global configuration options", }), }...) // Rules for output plugins rules[pluginOutput] = append(rules[pluginOutput], []ruleFunc{ requiredSectionsClose([]string{ "Global configuration options", }), }...) // Rules for processor pluings rules[pluginProcessor] = append(rules[pluginProcessor], []ruleFunc{ requiredSectionsClose([]string{ "Global configuration options", }), }...) // Rules for aggregator pluings rules[pluginAggregator] = append(rules[pluginAggregator], []ruleFunc{ requiredSectionsClose([]string{ "Global configuration options", }), }...) } func checkFile(filename string, pluginType plugin, sourceFlag, quiet bool) (bool, error) { md, err := os.ReadFile(filename) if err != nil { return false, err } // Goldmark returns locations as offsets. We want line // numbers. Find the newlines in the file so we can translate // later. scanner := bufio.NewScanner(bytes.NewReader(md)) scanner.Split(bufio.ScanRunes) offset := 0 newlineOffsets := make([]int, 0) for scanner.Scan() { if scanner.Text() == "\n" { newlineOffsets = append(newlineOffsets, offset) } offset++ } p := goldmark.DefaultParser() // We need goldmark to parse tables, otherwise they show up as // paragraphs. Since tables often have long lines and we check for long // lines in paragraphs, without table parsing there are false positive long // lines in tables. // // The tableParagraphTransformer is an extension and not part of the default // parser so we add it. There may be an easier way to do it, but this works: p.AddOptions( parser.WithParagraphTransformers( util.Prioritized(extension.NewTableParagraphTransformer(), 99), ), ) r := text.NewReader(md) root := p.Parse(r) rules := rules[pluginType] tester := T{ filename: filename, markdown: md, newlineOffsets: newlineOffsets, sourceFlag: sourceFlag, pluginType: pluginType, } for _, rule := range rules { err = rule(&tester, root) if err != nil { return false, err } } if !quiet { tester.printPassFail() } return tester.pass(), nil }