186 lines
4.5 KiB
Go
186 lines
4.5 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.
|
||
|
|
||
|
// embedmd
|
||
|
//
|
||
|
// embedmd embeds files or fractions of files into markdown files.
|
||
|
// It does so by searching embedmd commands, which are a subset of the
|
||
|
// markdown syntax for comments. This means they are invisible when
|
||
|
// markdown is rendered, so they can be kept in the file as pointers
|
||
|
// to the origin of the embedded text.
|
||
|
//
|
||
|
// The command receives a list of markdown files, if none is given it
|
||
|
// reads from the standard input.
|
||
|
//
|
||
|
// embedmd supports two flags:
|
||
|
// -d: will print the difference of the input file with what the output
|
||
|
// would have been if executed.
|
||
|
// -w: rewrites the given files rather than writing the output to the standard
|
||
|
// output.
|
||
|
//
|
||
|
// For more information on the format of the commands, read the documentation
|
||
|
// of the github.com/campoy/embedmd/embedmd package.
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
|
||
|
"github.com/campoy/embedmd/embedmd"
|
||
|
"github.com/pmezard/go-difflib/difflib"
|
||
|
)
|
||
|
|
||
|
// modified while building by -ldflags.
|
||
|
var version = "unkown"
|
||
|
|
||
|
func usage() {
|
||
|
fmt.Fprintf(os.Stderr, "usage: embedmd [flags] [path ...]\n")
|
||
|
flag.PrintDefaults()
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
rewrite := flag.Bool("w", false, "write result to (markdown) file instead of stdout")
|
||
|
doDiff := flag.Bool("d", false, "display diffs instead of rewriting files")
|
||
|
printVersion := flag.Bool("v", false, "display embedmd version")
|
||
|
flag.Usage = usage
|
||
|
flag.Parse()
|
||
|
|
||
|
if *printVersion {
|
||
|
fmt.Println("embedmd version: " + version)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
diff, err := embed(flag.Args(), *rewrite, *doDiff)
|
||
|
if err != nil {
|
||
|
fmt.Fprintln(os.Stderr, err)
|
||
|
os.Exit(2)
|
||
|
}
|
||
|
if diff && *doDiff {
|
||
|
os.Exit(2)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
stdout io.Writer = os.Stdout
|
||
|
stdin io.Reader = os.Stdin
|
||
|
)
|
||
|
|
||
|
func embed(paths []string, rewrite, doDiff bool) (foundDiff bool, err error) {
|
||
|
if rewrite && doDiff {
|
||
|
return false, fmt.Errorf("error: cannot use -w and -d simultaneously")
|
||
|
}
|
||
|
|
||
|
if len(paths) == 0 {
|
||
|
if rewrite {
|
||
|
return false, fmt.Errorf("error: cannot use -w with standard input")
|
||
|
}
|
||
|
if !doDiff {
|
||
|
return false, embedmd.Process(stdout, stdin)
|
||
|
}
|
||
|
|
||
|
var out, in bytes.Buffer
|
||
|
if err := embedmd.Process(&out, io.TeeReader(stdin, &in)); err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
d, err := diff(in.String(), out.String())
|
||
|
if err != nil || len(d) == 0 {
|
||
|
return false, err
|
||
|
}
|
||
|
fmt.Fprintf(stdout, "%s", d)
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
for _, path := range paths {
|
||
|
d, err := processFile(path, rewrite, doDiff)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("%s:%v", path, err)
|
||
|
}
|
||
|
foundDiff = foundDiff || d
|
||
|
}
|
||
|
return foundDiff, nil
|
||
|
}
|
||
|
|
||
|
type file interface {
|
||
|
io.ReadCloser
|
||
|
io.WriterAt
|
||
|
Truncate(int64) error
|
||
|
}
|
||
|
|
||
|
// replaced by testing functions.
|
||
|
var openFile = func(name string) (file, error) {
|
||
|
return os.OpenFile(name, os.O_RDWR, 0666)
|
||
|
}
|
||
|
|
||
|
func readFile(path string) ([]byte, error) {
|
||
|
f, err := openFile(path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
return ioutil.ReadAll(f)
|
||
|
}
|
||
|
|
||
|
func processFile(path string, rewrite, doDiff bool) (foundDiff bool, err error) {
|
||
|
if filepath.Ext(path) != ".md" {
|
||
|
return false, fmt.Errorf("not a markdown file")
|
||
|
}
|
||
|
|
||
|
f, err := openFile(path)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
buf := new(bytes.Buffer)
|
||
|
if err := embedmd.Process(buf, f, embedmd.WithBaseDir(filepath.Dir(path))); err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
if doDiff {
|
||
|
f, err := readFile(path)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("could not read %s for diff: %v", path, err)
|
||
|
}
|
||
|
data, err := diff(string(f), buf.String())
|
||
|
if err != nil || len(data) == 0 {
|
||
|
return false, err
|
||
|
}
|
||
|
fmt.Fprintf(stdout, "%s", data)
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
if rewrite {
|
||
|
n, err := f.WriteAt(buf.Bytes(), 0)
|
||
|
if err != nil {
|
||
|
return false, fmt.Errorf("could not write: %v", err)
|
||
|
}
|
||
|
return false, f.Truncate(int64(n))
|
||
|
}
|
||
|
|
||
|
io.Copy(stdout, buf)
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
func diff(a, b string) (string, error) {
|
||
|
return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||
|
A: difflib.SplitLines(a),
|
||
|
B: difflib.SplitLines(b),
|
||
|
Context: 3,
|
||
|
})
|
||
|
}
|