github.com/phsym/gomarkdoc@v0.5.4/cmd/gomarkdoc/output.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 12 "github.com/phsym/gomarkdoc" 13 "github.com/phsym/gomarkdoc/lang" 14 "github.com/phsym/gomarkdoc/logger" 15 ) 16 17 func writeOutput(specs []*PackageSpec, opts commandOptions) error { 18 log := logger.New(getLogLevel(opts.verbosity)) 19 20 overrides, err := resolveOverrides(opts) 21 if err != nil { 22 return err 23 } 24 25 out, err := gomarkdoc.NewRenderer(overrides...) 26 if err != nil { 27 return err 28 } 29 30 header, err := resolveHeader(opts) 31 if err != nil { 32 return err 33 } 34 35 footer, err := resolveFooter(opts) 36 if err != nil { 37 return err 38 } 39 40 filePkgs := make(map[string][]*lang.Package) 41 42 for _, spec := range specs { 43 if spec.pkg == nil { 44 continue 45 } 46 47 filePkgs[spec.outputFile] = append(filePkgs[spec.outputFile], spec.pkg) 48 } 49 50 for fileName, pkgs := range filePkgs { 51 file := lang.NewFile(header, footer, pkgs) 52 53 text, err := out.File(file) 54 if err != nil { 55 return err 56 } 57 58 if opts.embed && fileName != "" { 59 text = embedContents(log, fileName, text) 60 } 61 62 switch { 63 case fileName == "": 64 fmt.Fprint(os.Stdout, text) 65 case opts.check: 66 var b bytes.Buffer 67 fmt.Fprint(&b, text) 68 if err := checkFile(&b, fileName); err != nil { 69 return err 70 } 71 default: 72 if err := writeFile(fileName, text); err != nil { 73 return fmt.Errorf("failed to write output file %s: %w", fileName, err) 74 } 75 } 76 } 77 78 return nil 79 } 80 81 func writeFile(fileName string, text string) error { 82 folder := filepath.Dir(fileName) 83 84 if folder != "" { 85 if err := os.MkdirAll(folder, 0755); err != nil { 86 return fmt.Errorf("failed to create folder %s: %w", folder, err) 87 } 88 } 89 90 if err := ioutil.WriteFile(fileName, []byte(text), 0664); err != nil { 91 return fmt.Errorf("failed to write file %s: %w", fileName, err) 92 } 93 94 return nil 95 } 96 97 func checkFile(b *bytes.Buffer, path string) error { 98 checkErr := errors.New("output does not match current files. Did you forget to run gomarkdoc?") 99 100 f, err := os.Open(path) 101 if err != nil { 102 if err == os.ErrNotExist { 103 return checkErr 104 } 105 106 return fmt.Errorf("failed to open file %s for checking: %w", path, err) 107 } 108 109 defer f.Close() 110 111 match, err := compare(b, f) 112 if err != nil { 113 return fmt.Errorf("failure while attempting to check contents of %s: %w", path, err) 114 } 115 116 if !match { 117 return checkErr 118 } 119 120 return nil 121 } 122 123 var ( 124 embedStandaloneRegex = regexp.MustCompile(`(?m:^ *)<!--\s*gomarkdoc:embed\s*-->(?m:\s*?$)`) 125 embedStartRegex = regexp.MustCompile( 126 `(?m:^ *)<!--\s*gomarkdoc:embed:start\s*-->(?s:.*?)<!--\s*gomarkdoc:embed:end\s*-->(?m:\s*?$)`, 127 ) 128 ) 129 130 func embedContents(log logger.Logger, fileName string, text string) string { 131 embedText := fmt.Sprintf("<!-- gomarkdoc:embed:start -->\n\n%s\n\n<!-- gomarkdoc:embed:end -->", text) 132 133 data, err := os.ReadFile(fileName) 134 if err != nil { 135 log.Debugf("unable to find output file %s for embedding. Creating a new file instead", fileName) 136 return embedText 137 } 138 139 var replacements int 140 data = embedStandaloneRegex.ReplaceAllFunc(data, func(_ []byte) []byte { 141 replacements++ 142 return []byte(embedText) 143 }) 144 145 data = embedStartRegex.ReplaceAllFunc(data, func(_ []byte) []byte { 146 replacements++ 147 return []byte(embedText) 148 }) 149 150 if replacements == 0 { 151 log.Debugf("no embed markers found. Appending documentation to the end of the file instead") 152 return fmt.Sprintf("%s\n\n%s", string(data), text) 153 } 154 155 return string(data) 156 }