diff --git a/README.md b/README.md index d8feed2..e309eb5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ exclude: - excluded/**/*.yaml ``` +### Line Ending + +The default line ending is `lf` (Unix style, Mac/Linux). The line ending can be changed to `crlf` (Windows style) with the `line_ending` setting: +```yaml +line_ending: crlf +``` +This setting will be sent to any formatter as a config field called `line_ending`. If a `line_ending` is specified in the formatter, this will overwrite it. New formatters are free to ignore this setting if they don't need it, but any formatter provided by this repo will handle it accordingly. + ### Formatter In your `.yamlfmt` file you can also specify configuration for the formatter if that formatter supports it. To change the indentation level of the basic formatter for example: diff --git a/command/command.go b/command/command.go index 9ab6fb3..06000ad 100644 --- a/command/command.go +++ b/command/command.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "os" + "runtime" "github.com/google/yamlfmt" "github.com/google/yamlfmt/engine" @@ -40,9 +41,10 @@ type formatterConfig struct { } type commandConfig struct { - Include []string `mapstructure:"include"` - Exclude []string `mapstructure:"exclude"` - FormatterConfig *formatterConfig `mapstructure:"formatter,omitempty"` + Include []string `mapstructure:"include"` + Exclude []string `mapstructure:"exclude"` + LineEnding yamlfmt.LineBreakStyle `mapstructure:"line_ending"` + FormatterConfig *formatterConfig `mapstructure:"formatter,omitempty"` } func RunCommand( @@ -58,6 +60,12 @@ func RunCommand( if len(config.Include) == 0 { config.Include = []string{"**/*.{yaml,yml}"} } + if config.LineEnding == "" { + config.LineEnding = yamlfmt.LineBreakStyleLF + if runtime.GOOS == "windows" { + config.LineEnding = yamlfmt.LineBreakStyleCRLF + } + } var formatter yamlfmt.Formatter if config.FormatterConfig == nil { @@ -65,7 +73,10 @@ func RunCommand( if err != nil { return err } - formatter = factory.NewDefault() + formatter, err = factory.NewFormatter(nil) + if err != nil { + return err + } } else { var ( factory yamlfmt.Factory @@ -80,20 +91,22 @@ func RunCommand( return err } - if len(config.FormatterConfig.FormatterSettings) > 0 { - formatter, err = factory.NewWithConfig(config.FormatterConfig.FormatterSettings) - if err != nil { - return err - } - } else { - formatter = factory.NewDefault() + config.FormatterConfig.FormatterSettings["line_ending"] = config.LineEnding + formatter, err = factory.NewFormatter(config.FormatterConfig.FormatterSettings) + if err != nil { + return err } } + lineSepChar, err := config.LineEnding.Separator() + if err != nil { + return err + } engine := &engine.Engine{ - Include: config.Include, - Exclude: config.Exclude, - Formatter: formatter, + Include: config.Include, + Exclude: config.Exclude, + LineSepCharacter: lineSepChar, + Formatter: formatter, } switch operation { diff --git a/engine/engine.go b/engine/engine.go index d4a0722..f98d2fc 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -24,9 +24,10 @@ import ( ) type Engine struct { - Include []string - Exclude []string - Formatter yamlfmt.Formatter + Include []string + Exclude []string + LineSepCharacter string + Formatter yamlfmt.Formatter } func (e *Engine) FormatAllFiles() error { @@ -91,9 +92,9 @@ func (e *Engine) LintFile(path string) error { if err != nil { return err } - diffContent := multilinediff.MultilineDiff(string(yamlBytes), string(formatted), "\n") - if diffContent != "" { - return fmt.Errorf(diffContent) + diff, diffCount := multilinediff.Diff(string(yamlBytes), string(formatted), e.LineSepCharacter) + if diffCount > 0 { + return fmt.Errorf(diff) } return nil } @@ -107,10 +108,10 @@ func (e *Engine) DryRunAllFiles() (string, error) { formatErrors := NewFormatFileErrors() dryRunDiffs := NewDryRunDiffs() for _, path := range paths { - diff, err := e.DryRunFile(path) + diff, diffCount, err := e.DryRunFile(path) if err != nil { formatErrors.Add(path, err) - } else if diff != "" { + } else if diffCount > 0 { dryRunDiffs.Add(path, diff) } } @@ -121,15 +122,15 @@ func (e *Engine) DryRunAllFiles() (string, error) { return dryRunDiffs.CombineOutput(), nil } -func (e *Engine) DryRunFile(path string) (string, error) { +func (e *Engine) DryRunFile(path string) (string, int, error) { yamlBytes, err := os.ReadFile(path) if err != nil { - return "", err + return "", 0, err } formatted, err := e.Formatter.Format(yamlBytes) if err != nil { - return "", err + return "", 0, err } - diffContent := multilinediff.MultilineDiff(string(yamlBytes), string(formatted), "\n") - return diffContent, nil + diff, diffCount := multilinediff.Diff(string(yamlBytes), string(formatted), e.LineSepCharacter) + return diff, diffCount, nil } diff --git a/formatters/basic/README.md b/formatters/basic/README.md index 647cb22..f911524 100644 --- a/formatters/basic/README.md +++ b/formatters/basic/README.md @@ -8,6 +8,6 @@ The basic formatter is a barebones formatter that simply takes the data provided |:-------------------------|:---------------|:--------|:------------| | `indent` | int | 2 | The indentation level in spaces to use for the formatted yaml| | `include_document_start` | bool | false | Include `---` at document start | -| `line_ending` | `lf` or `crlf` | `crlf` on Windows, `lf` otherwise | Parse and write the file with "lf" or "crlf" line endings | +| `line_ending` | `lf` or `crlf` | `crlf` on Windows, `lf` otherwise | Parse and write the file with "lf" or "crlf" line endings. This setting will be overwritten by the global `line_ending`. | | `emoji_support` | bool | false | Support encoding utf-8 emojis | | `retain_line_breaks` | bool | false | Retain line breaks in formatted yaml | diff --git a/formatters/basic/config.go b/formatters/basic/config.go index 3bcc602..6e3775b 100644 --- a/formatters/basic/config.go +++ b/formatters/basic/config.go @@ -21,11 +21,11 @@ import ( ) type Config struct { - Indent int `mapstructure:"indent"` - IncludeDocumentStart bool `mapstructure:"include_document_start"` - EmojiSupport bool `mapstructure:"emoji_support"` - LineEnding string `mapstructure:"line_ending"` - RetainLineBreaks bool `mapstructure:"retain_line_breaks"` + Indent int `mapstructure:"indent"` + IncludeDocumentStart bool `mapstructure:"include_document_start"` + EmojiSupport bool `mapstructure:"emoji_support"` + LineEnding yamlfmt.LineBreakStyle `mapstructure:"line_ending"` + RetainLineBreaks bool `mapstructure:"retain_line_breaks"` } func DefaultConfig() *Config { diff --git a/formatters/basic/errors.go b/formatters/basic/errors.go new file mode 100644 index 0000000..35f5bb8 --- /dev/null +++ b/formatters/basic/errors.go @@ -0,0 +1,19 @@ +package basic + +import "fmt" + +type BasicFormatterError struct { + err error +} + +func (e BasicFormatterError) Error() string { + return fmt.Sprintf("basic formatter error: %v", e.err) +} + +func (e BasicFormatterError) Unwrap() error { + return e.err +} + +func wrapBasicFormatterError(err error) error { + return BasicFormatterError{err: err} +} diff --git a/formatters/basic/factory.go b/formatters/basic/factory.go index c43248a..6655b77 100644 --- a/formatters/basic/factory.go +++ b/formatters/basic/factory.go @@ -25,15 +25,13 @@ func (f *BasicFormatterFactory) Type() string { return BasicFormatterType } -func (f *BasicFormatterFactory) NewDefault() yamlfmt.Formatter { - return newFormatter(DefaultConfig()) -} - -func (f *BasicFormatterFactory) NewWithConfig(configData map[string]interface{}) (yamlfmt.Formatter, error) { +func (f *BasicFormatterFactory) NewFormatter(configData map[string]interface{}) (yamlfmt.Formatter, error) { config := DefaultConfig() - err := mapstructure.Decode(configData, &config) - if err != nil { - return nil, err + if configData != nil { + err := mapstructure.Decode(configData, &config) + if err != nil { + return nil, err + } } return newFormatter(config), nil } diff --git a/formatters/basic/factory_test.go b/formatters/basic/factory_test.go index 79db8bc..c8123cc 100644 --- a/formatters/basic/factory_test.go +++ b/formatters/basic/factory_test.go @@ -78,7 +78,7 @@ func TestNewWithConfigRetainsDefaultValues(t *testing.T) { factory := basic.BasicFormatterFactory{} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - formatter, err := factory.NewWithConfig(tc.configMap) + formatter, err := factory.NewFormatter(tc.configMap) if err != nil { t.Fatalf("expected factory to create config, got error: %v", err) } diff --git a/formatters/basic/features.go b/formatters/basic/features.go index 252591b..3edf0d8 100644 --- a/formatters/basic/features.go +++ b/formatters/basic/features.go @@ -50,11 +50,11 @@ func ConfigureFeaturesFromConfig(config *Config) yamlfmt.FeatureList { features = append(features, featCRLFSupport) } if config.RetainLineBreaks { - linebreakStr := "\n" - if config.LineEnding == yamlfmt.LineBreakStyleCRLF { - linebreakStr = "\r\n" + lineSep, err := config.LineEnding.Separator() + if err != nil { + lineSep = "\n" } - featLineBreak := hotfix.MakeFeatureRetainLineBreak(linebreakStr) + featLineBreak := hotfix.MakeFeatureRetainLineBreak(lineSep) features = append(features, featLineBreak) } return features diff --git a/go.mod b/go.mod index c65b65c..0cfe33b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/RageCage64/go-utf8-codepoint-converter v0.1.0 - github.com/RageCage64/multilinediff v0.1.0 + github.com/RageCage64/multilinediff v0.2.0 github.com/bmatcuk/doublestar/v4 v4.2.0 github.com/mitchellh/mapstructure v1.5.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index a52aca2..2b59ccc 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/RageCage64/go-utf8-codepoint-converter v0.1.0 h1:6GreQRSQApXW1sgeFXMB github.com/RageCage64/go-utf8-codepoint-converter v0.1.0/go.mod h1:asNWDxR7n0QIQyZNYTlpNk6Dg7GkUnxtCXho987uen8= github.com/RageCage64/multilinediff v0.1.0 h1:P9Iht5Vj4SHSmTJhQPJnySzTlX/cpL99kN3RJxejipQ= github.com/RageCage64/multilinediff v0.1.0/go.mod h1:pKr+KLgP0gvRzA+yv0/IUaYQuBYN1ucWysvsL58aMP0= +github.com/RageCage64/multilinediff v0.2.0 h1:yNSpSF5NXIrmo6bRIgO4Q0g7SXqFD4j/WEcBE+BdCFY= +github.com/RageCage64/multilinediff v0.2.0/go.mod h1:pKr+KLgP0gvRzA+yv0/IUaYQuBYN1ucWysvsL58aMP0= github.com/bmatcuk/doublestar/v4 v4.2.0 h1:Qu+u9wR3Vd89LnlLMHvnZ5coJMWKQamqdz9/p5GNthA= github.com/bmatcuk/doublestar/v4 v4.2.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= diff --git a/internal/hotfix/unicode.go b/internal/hotfix/unicode.go index f219dda..49acdd2 100644 --- a/internal/hotfix/unicode.go +++ b/internal/hotfix/unicode.go @@ -24,7 +24,7 @@ import ( // yamlfmt.FeatureFunc func ParseUnicodePoints(content []byte) ([]byte, error) { if len(content) == 0 { - return []byte{}, errors.New("no content") + return []byte{}, nil } p := unicodeParser{ diff --git a/yamlfmt.go b/yamlfmt.go index 75135ff..386e731 100644 --- a/yamlfmt.go +++ b/yamlfmt.go @@ -25,8 +25,7 @@ type Formatter interface { type Factory interface { Type() string - NewDefault() Formatter - NewWithConfig(config map[string]interface{}) (Formatter, error) + NewFormatter(config map[string]interface{}) (Formatter, error) } type Registry struct { @@ -63,11 +62,31 @@ func (r *Registry) GetDefaultFactory() (Factory, error) { return factory, nil } +type LineBreakStyle string + const ( - LineBreakStyleLF = "lf" - LineBreakStyleCRLF = "crlf" + LineBreakStyleLF LineBreakStyle = "lf" + LineBreakStyleCRLF LineBreakStyle = "crlf" ) +type UnsupportedLineBreakError struct { + style LineBreakStyle +} + +func (e UnsupportedLineBreakError) Error() string { + return fmt.Sprintf("unsupported line break style %s, see package documentation for supported styles", e.style) +} + +func (s LineBreakStyle) Separator() (string, error) { + switch s { + case LineBreakStyleLF: + return "\n", nil + case LineBreakStyleCRLF: + return "\r\n", nil + } + return "", UnsupportedLineBreakError{style: s} +} + type FeatureFunc func([]byte) ([]byte, error) type Feature struct {