410a014daf
Signed-off-by: Lucas Servén Marín <lserven@gmail.com>
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
// Copyright 2016 Google Inc. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to writing, software distributed
|
|
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
// CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package embedmd provides a single function, Process, that parses markdown
|
|
// searching for markdown comments.
|
|
//
|
|
// The format of an embedmd command is:
|
|
//
|
|
// [embedmd]:# (pathOrURL language /start regexp/ /end regexp/)
|
|
//
|
|
// The embedded code will be extracted from the file at pathOrURL,
|
|
// which can either be a relative path to a file in the local file
|
|
// system (using always forward slashes as directory separator) or
|
|
// a url starting with http:// or https://.
|
|
// If the pathOrURL is a url the tool will fetch the content in that url.
|
|
// The embedded content starts at the first line that matches /start regexp/
|
|
// and finishes at the first line matching /end regexp/.
|
|
//
|
|
// Omitting the the second regular expression will embed only the piece of
|
|
// text that matches /regexp/:
|
|
//
|
|
// [embedmd]:# (pathOrURL language /regexp/)
|
|
//
|
|
// To embed the whole line matching a regular expression you can use:
|
|
//
|
|
// [embedmd]:# (pathOrURL language /.*regexp.*\n/)
|
|
//
|
|
// If you want to embed from a point to the end you should use:
|
|
//
|
|
// [embedmd]:# (pathOrURL language /start regexp/ $)
|
|
//
|
|
// Finally you can embed a whole file by omitting both regular expressions:
|
|
//
|
|
// [embedmd]:# (pathOrURL language)
|
|
//
|
|
// You can ommit the language in any of the previous commands, and the extension
|
|
// of the file will be used for the snippet syntax highlighting. Note that while
|
|
// this works Go files, since the file extension .go matches the name of the language
|
|
// go, this will fail with other files like .md whose language name is markdown.
|
|
//
|
|
// [embedmd]:# (file.ext)
|
|
//
|
|
package embedmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
)
|
|
|
|
// Process reads markdown from the given io.Reader searching for an embedmd
|
|
// command. When a command is found, it is executed and the output is written
|
|
// into the given io.Writer with the rest of standard markdown.
|
|
func Process(out io.Writer, in io.Reader, opts ...Option) error {
|
|
e := embedder{Fetcher: fetcher{}}
|
|
for _, opt := range opts {
|
|
opt.f(&e)
|
|
}
|
|
return process(out, in, e.runCommand)
|
|
}
|
|
|
|
// An Option provides a way to adapt the Process function to your needs.
|
|
type Option struct{ f func(*embedder) }
|
|
|
|
// WithBaseDir indicates that the given path should be used to resolve relative
|
|
// paths.
|
|
func WithBaseDir(path string) Option {
|
|
return Option{func(e *embedder) { e.baseDir = path }}
|
|
}
|
|
|
|
// WithFetcher provides a custom Fetcher to be used whenever a path or url needs
|
|
// to be fetched.
|
|
func WithFetcher(c Fetcher) Option {
|
|
return Option{func(e *embedder) { e.Fetcher = c }}
|
|
}
|
|
|
|
type embedder struct {
|
|
Fetcher
|
|
baseDir string
|
|
}
|
|
|
|
func (e *embedder) runCommand(w io.Writer, cmd *command) error {
|
|
b, err := e.Fetch(e.baseDir, cmd.path)
|
|
if err != nil {
|
|
return fmt.Errorf("could not read %s: %v", cmd.path, err)
|
|
}
|
|
|
|
b, err = extract(b, cmd.start, cmd.end)
|
|
if err != nil {
|
|
return fmt.Errorf("could not extract content from %s: %v", cmd.path, err)
|
|
}
|
|
|
|
if len(b) > 0 && b[len(b)-1] != '\n' {
|
|
b = append(b, '\n')
|
|
}
|
|
|
|
fmt.Fprintln(w, "```"+cmd.lang)
|
|
w.Write(b)
|
|
fmt.Fprintln(w, "```")
|
|
return nil
|
|
}
|
|
|
|
func extract(b []byte, start, end *string) ([]byte, error) {
|
|
if start == nil && end == nil {
|
|
return b, nil
|
|
}
|
|
|
|
match := func(s string) ([]int, error) {
|
|
if len(s) <= 2 || s[0] != '/' || s[len(s)-1] != '/' {
|
|
return nil, fmt.Errorf("missing slashes (/) around %q", s)
|
|
}
|
|
re, err := regexp.CompilePOSIX(s[1 : len(s)-1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
loc := re.FindIndex(b)
|
|
if loc == nil {
|
|
return nil, fmt.Errorf("could not match %q", s)
|
|
}
|
|
return loc, nil
|
|
}
|
|
|
|
if *start != "" {
|
|
loc, err := match(*start)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if end == nil {
|
|
return b[loc[0]:loc[1]], nil
|
|
}
|
|
b = b[loc[0]:]
|
|
}
|
|
|
|
if *end != "$" {
|
|
loc, err := match(*end)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b = b[:loc[1]]
|
|
}
|
|
|
|
return b, nil
|
|
}
|