// snippet-extract --begin=#_BEGIN_WRAP_TAG_ --end=#_END_WRAP_TAG_ -output_dir=./docs/ [./file|./directory/]... // Extracts markdown snippets from relative files into output_dir, keeping the // directory layout. // It is not mandatory to terminate a snippet, the extractor will simply add // line until EOF. // Lines matching begin or end tags will not be put in the resulting file. // When a directory is passed, all files from directory will be parsed. package main import ( "bufio" "bytes" "flag" "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" ) type options struct { filenames []string begin, end string outputDir string extension string } func (o *options) AddFlagSets(fs *flag.FlagSet) { fs.StringVar(&o.begin, "begin", "#_BEGIN_WRAP_TAG_", "flag to mark beginning of a snippet") fs.StringVar(&o.end, "end", "#_END_WRAP_TAG_", "flag to mark ending of a snippet") fs.StringVar(&o.outputDir, "output_dir", "./docs/", "directory in which the files will be generated, note that the directory layout is kept") fs.StringVar(&o.extension, "extension", ".mdx", "extension for generated files") } func main() { fs := flag.NewFlagSet("snippet-extract", flag.ContinueOnError) opts := options{} opts.AddFlagSets(fs) if err := fs.Parse(os.Args[1:]); err != nil { fs.Usage() os.Exit(1) } wd, _ := os.Getwd() fmt.Printf("working from %s\n", wd) opts.filenames = extactFilenames(fs.Args()) for _, filename := range opts.filenames { ext := filepath.Ext(filename) snippets := extractSnippets(opts.begin, opts.end, filename) for _, snippet := range snippets { outputFile := filepath.Join(opts.outputDir, filepath.Base(filename), snippet.Identifier+opts.extension) folder := filepath.Dir(outputFile) err := os.MkdirAll(folder, os.ModePerm) if err != nil { log.Printf("cannot mkdir %s: %s", folder, err) } f := bytes.NewBuffer(nil) fmt.Fprintf(f, ``, strings.Join(os.Args[1:], " ")) fmt.Fprintf(f, "\n\n```%s\n%s```\n", ext, snippet.Text) err = ioutil.WriteFile(outputFile, f.Bytes(), 0600) if err != nil { log.Printf("cannot write %s in %s: %s", filepath.Base(outputFile), folder, err) } } } } type snippet struct { Identifier string Text string Closed bool } func extractSnippets(beginPattern, endPattern, filename string) []snippet { file, err := os.Open(filename) if err != nil { log.Printf("could not open file: %s", err) os.Exit(1) } defer file.Close() scanner := bufio.NewScanner(file) snippets := []snippet{} for scanner.Scan() { line := scanner.Text() if identifier := matches(line, beginPattern); identifier != "" { snippets = append(snippets, snippet{ Identifier: identifier, }) continue } if identifier := matches(line, endPattern); identifier != "" { for i := range snippets { snippet := &snippets[i] if snippet.Identifier == identifier { snippet.Closed = true } } continue } for i := range snippets { snippet := &snippets[i] if snippet.Closed { continue } snippet.Text = snippet.Text + line + "\n" } } return snippets } func matches(s, prefix string) string { trimmed := strings.TrimSpace(s) lenDiff := len(s) - len(trimmed) if strings.HasPrefix(trimmed, prefix) { return s[len(prefix)+lenDiff:] } return "" } // if an entry is a directory all files from directory will be listed. func extactFilenames(in []string) []string { out := []string{} for _, path := range in { fi, err := os.Stat(path) if err != nil { log.Fatalf("%s: %s", path, err) } if !fi.IsDir() { out = append(out, path) continue } files, err := ioutil.ReadDir(path) if err != nil { log.Fatalf("could not read directory %s: %s", path, err) } for _, file := range files { if file.IsDir() { continue } out = append(out, file.Name()) } } return in }