github.com/exercism/v2-configlet@v3.9.2+incompatible/cmd/fmt.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/exercism/configlet/track"
    10  	"github.com/exercism/configlet/ui"
    11  	multierror "github.com/hashicorp/go-multierror"
    12  	"github.com/pmezard/go-difflib/difflib"
    13  	"github.com/spf13/cobra"
    14  )
    15  
    16  // verbose flag for fmt command.
    17  var fmtVerbose bool
    18  
    19  // fmtCmd defines the fmt command
    20  var fmtCmd = &cobra.Command{
    21  	Use:   "fmt " + pathExample,
    22  	Short: "Format the track configuration files",
    23  	Long: `The fmt command formats the track's configuration files.
    24  
    25  It ensures the following files have consistent JSON syntax and indentation:
    26  	config.json, maintainers.json
    27  
    28  It also normalizes and alphabetizes the exercise topics in the config.json file.
    29  `,
    30  	Example: fmt.Sprintf("  %s fmt %s --verbose", binaryName, pathExample),
    31  	Run: func(cmd *cobra.Command, args []string) {
    32  		if err := runFmt(args[0], args[0], fmtVerbose); err != nil {
    33  			ui.PrintError(err.Error())
    34  			os.Exit(1)
    35  		}
    36  	},
    37  
    38  	Args: cobra.ExactArgs(1),
    39  }
    40  
    41  func runFmt(inDir, outDir string, verbose bool) error {
    42  	if _, err := os.Stat(filepath.Join(outDir, "config")); os.IsNotExist(err) {
    43  		os.Mkdir(filepath.Join(outDir, "config"), os.ModePerm)
    44  	}
    45  
    46  	var fs = []struct {
    47  		inPath  string
    48  		outPath string
    49  		cfg     ConfigSerializer
    50  	}{
    51  		{
    52  			filepath.Join(inDir, "config.json"),
    53  			filepath.Join(outDir, "config.json"),
    54  			&track.Config{},
    55  		},
    56  		{
    57  			filepath.Join(inDir, "config", "maintainers.json"),
    58  			filepath.Join(outDir, "config", "maintainers.json"),
    59  			&track.MaintainerConfig{},
    60  		},
    61  	}
    62  
    63  	var changes string
    64  
    65  	errs := &multierror.Error{}
    66  	for _, f := range fs {
    67  		diff, err := formatFile(f.cfg, f.inPath, f.outPath)
    68  		if err != nil {
    69  			errs = multierror.Append(errs, err)
    70  			continue
    71  		}
    72  		if diff != "" {
    73  			if verbose {
    74  				ui.Print(fmt.Sprintf("%s\n\n%s", f.inPath, diff))
    75  			}
    76  			changes += fmt.Sprintf("%s\n", f.inPath)
    77  		}
    78  	}
    79  	if changes != "" {
    80  		ui.Print("changes made to:\n", changes)
    81  	}
    82  	if err := errs.ErrorOrNil(); err != nil {
    83  		return err
    84  	}
    85  	return nil
    86  }
    87  
    88  func formatFile(cfg ConfigSerializer, inPath, outPath string) (string, error) {
    89  	src, err := ioutil.ReadFile(inPath)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  	if err := cfg.LoadFromFile(inPath); err != nil {
    94  		return "", err
    95  	}
    96  	dst, err := cfg.ToJSON()
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  	dst = []byte(string(fmt.Sprintf("%s\n", dst)))
   101  
   102  	a := difflib.SplitLines(string(src))
   103  	b := difflib.SplitLines(string(dst))
   104  	diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{A: a, B: b})
   105  	if err != nil {
   106  		return "", err
   107  	}
   108  	if diff != "" {
   109  		if err := ioutil.WriteFile(outPath, dst, os.FileMode(0644)); err != nil {
   110  			return "", err
   111  		}
   112  	}
   113  	return diff, nil
   114  }
   115  
   116  func init() {
   117  	RootCmd.AddCommand(fmtCmd)
   118  	fmtCmd.Flags().BoolVarP(&fmtVerbose, "verbose", "v", false, "display the diff of the formatted changes.")
   119  }