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  }