main.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Copyright 2016 Google Inc. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to writing, software distributed
  8. // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
  9. // CONDITIONS OF ANY KIND, either express or implied.
  10. //
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. // embedmd
  14. //
  15. // embedmd embeds files or fractions of files into markdown files.
  16. // It does so by searching embedmd commands, which are a subset of the
  17. // markdown syntax for comments. This means they are invisible when
  18. // markdown is rendered, so they can be kept in the file as pointers
  19. // to the origin of the embedded text.
  20. //
  21. // The command receives a list of markdown files, if none is given it
  22. // reads from the standard input.
  23. //
  24. // embedmd supports two flags:
  25. // -d: will print the difference of the input file with what the output
  26. // would have been if executed.
  27. // -w: rewrites the given files rather than writing the output to the standard
  28. // output.
  29. //
  30. // For more information on the format of the commands, read the documentation
  31. // of the github.com/campoy/embedmd/embedmd package.
  32. package main
  33. import (
  34. "bytes"
  35. "flag"
  36. "fmt"
  37. "io"
  38. "io/ioutil"
  39. "os"
  40. "path/filepath"
  41. "github.com/campoy/embedmd/embedmd"
  42. "github.com/pmezard/go-difflib/difflib"
  43. )
  44. // modified while building by -ldflags.
  45. var version = "unkown"
  46. func usage() {
  47. fmt.Fprintf(os.Stderr, "usage: embedmd [flags] [path ...]\n")
  48. flag.PrintDefaults()
  49. }
  50. func main() {
  51. rewrite := flag.Bool("w", false, "write result to (markdown) file instead of stdout")
  52. doDiff := flag.Bool("d", false, "display diffs instead of rewriting files")
  53. printVersion := flag.Bool("v", false, "display embedmd version")
  54. flag.Usage = usage
  55. flag.Parse()
  56. if *printVersion {
  57. fmt.Println("embedmd version: " + version)
  58. return
  59. }
  60. diff, err := embed(flag.Args(), *rewrite, *doDiff)
  61. if err != nil {
  62. fmt.Fprintln(os.Stderr, err)
  63. os.Exit(2)
  64. }
  65. if diff && *doDiff {
  66. os.Exit(2)
  67. }
  68. }
  69. var (
  70. stdout io.Writer = os.Stdout
  71. stdin io.Reader = os.Stdin
  72. )
  73. func embed(paths []string, rewrite, doDiff bool) (foundDiff bool, err error) {
  74. if rewrite && doDiff {
  75. return false, fmt.Errorf("error: cannot use -w and -d simultaneously")
  76. }
  77. if len(paths) == 0 {
  78. if rewrite {
  79. return false, fmt.Errorf("error: cannot use -w with standard input")
  80. }
  81. if !doDiff {
  82. return false, embedmd.Process(stdout, stdin)
  83. }
  84. var out, in bytes.Buffer
  85. if err := embedmd.Process(&out, io.TeeReader(stdin, &in)); err != nil {
  86. return false, err
  87. }
  88. d, err := diff(in.String(), out.String())
  89. if err != nil || len(d) == 0 {
  90. return false, err
  91. }
  92. fmt.Fprintf(stdout, "%s", d)
  93. return true, nil
  94. }
  95. for _, path := range paths {
  96. d, err := processFile(path, rewrite, doDiff)
  97. if err != nil {
  98. return false, fmt.Errorf("%s:%v", path, err)
  99. }
  100. foundDiff = foundDiff || d
  101. }
  102. return foundDiff, nil
  103. }
  104. type file interface {
  105. io.ReadCloser
  106. io.WriterAt
  107. Truncate(int64) error
  108. }
  109. // replaced by testing functions.
  110. var openFile = func(name string) (file, error) {
  111. return os.OpenFile(name, os.O_RDWR, 0666)
  112. }
  113. func readFile(path string) ([]byte, error) {
  114. f, err := openFile(path)
  115. if err != nil {
  116. return nil, err
  117. }
  118. defer f.Close()
  119. return ioutil.ReadAll(f)
  120. }
  121. func processFile(path string, rewrite, doDiff bool) (foundDiff bool, err error) {
  122. if filepath.Ext(path) != ".md" {
  123. return false, fmt.Errorf("not a markdown file")
  124. }
  125. f, err := openFile(path)
  126. if err != nil {
  127. return false, err
  128. }
  129. defer f.Close()
  130. buf := new(bytes.Buffer)
  131. if err := embedmd.Process(buf, f, embedmd.WithBaseDir(filepath.Dir(path))); err != nil {
  132. return false, err
  133. }
  134. if doDiff {
  135. f, err := readFile(path)
  136. if err != nil {
  137. return false, fmt.Errorf("could not read %s for diff: %v", path, err)
  138. }
  139. data, err := diff(string(f), buf.String())
  140. if err != nil || len(data) == 0 {
  141. return false, err
  142. }
  143. fmt.Fprintf(stdout, "%s", data)
  144. return true, nil
  145. }
  146. if rewrite {
  147. n, err := f.WriteAt(buf.Bytes(), 0)
  148. if err != nil {
  149. return false, fmt.Errorf("could not write: %v", err)
  150. }
  151. return false, f.Truncate(int64(n))
  152. }
  153. io.Copy(stdout, buf)
  154. return false, nil
  155. }
  156. func diff(a, b string) (string, error) {
  157. return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
  158. A: difflib.SplitLines(a),
  159. B: difflib.SplitLines(b),
  160. Context: 3,
  161. })
  162. }