feat(cmd): add unified diff output option for formatting
* Implemented `-d`/`--diff` flag to print unified diffs. * Added `unifiedDiff` function for generating diffs. * Config discovery now merges fields from `.pgtidy.yaml`.
This commit is contained in:
+108
-2
@@ -1,9 +1,17 @@
|
||||
// Package config defines PgTidy's formatter (and, later, linter) configuration.
|
||||
//
|
||||
// Defaults encode the project house style reverse-engineered from the corpus.
|
||||
// A future change will load/merge these from a discovered .pgtidy.yaml file.
|
||||
// Defaults encode the project house style. A .pgtidy.yaml file discovered by
|
||||
// walking up from the target directory overrides individual fields.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Case controls keyword/identifier casing.
|
||||
type Case string
|
||||
|
||||
@@ -53,3 +61,101 @@ func Default() Style {
|
||||
Commas: CommaLeading,
|
||||
}
|
||||
}
|
||||
|
||||
// yamlFile is the on-disk representation of .pgtidy.yaml.
|
||||
// All fields are pointers so we can distinguish "not set" from "set to zero value".
|
||||
type yamlFile struct {
|
||||
Indent *string `yaml:"indent"`
|
||||
Newline *string `yaml:"newline"`
|
||||
KeywordCase *string `yaml:"keyword_case"`
|
||||
IdentCase *string `yaml:"ident_case"`
|
||||
TypeCase *string `yaml:"type_case"`
|
||||
Commas *string `yaml:"commas"`
|
||||
}
|
||||
|
||||
// Load discovers and parses the nearest .pgtidy.yaml by walking up from
|
||||
// startDir. Fields present in the file override the house-style defaults;
|
||||
// missing fields keep the default value. Returns Default() when no config
|
||||
// file is found.
|
||||
func Load(startDir string) (Style, error) {
|
||||
st := Default()
|
||||
path, err := findConfig(startDir)
|
||||
if err != nil || path == "" {
|
||||
return st, err
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return st, fmt.Errorf("pgtidy: read %s: %w", path, err)
|
||||
}
|
||||
|
||||
var yf yamlFile
|
||||
if err := yaml.Unmarshal(data, &yf); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: parse %s: %w", path, err)
|
||||
}
|
||||
|
||||
if yf.Indent != nil {
|
||||
st.Indent = *yf.Indent
|
||||
}
|
||||
if yf.Newline != nil {
|
||||
st.Newline = *yf.Newline
|
||||
}
|
||||
if yf.KeywordCase != nil {
|
||||
c := Case(*yf.KeywordCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: keyword_case: %w", path, err)
|
||||
}
|
||||
st.KeywordCase = c
|
||||
}
|
||||
if yf.IdentCase != nil {
|
||||
c := Case(*yf.IdentCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: ident_case: %w", path, err)
|
||||
}
|
||||
st.IdentCase = c
|
||||
}
|
||||
if yf.TypeCase != nil {
|
||||
c := Case(*yf.TypeCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: type_case: %w", path, err)
|
||||
}
|
||||
st.TypeCase = c
|
||||
}
|
||||
if yf.Commas != nil {
|
||||
cs := CommaStyle(*yf.Commas)
|
||||
if cs != CommaLeading && cs != CommaTrailing {
|
||||
return st, fmt.Errorf("pgtidy: %s: commas: must be \"leading\" or \"trailing\"", path)
|
||||
}
|
||||
st.Commas = cs
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// findConfig walks parent directories from startDir looking for .pgtidy.yaml.
|
||||
// Returns ("", nil) when no file is found before reaching the filesystem root.
|
||||
func findConfig(startDir string) (string, error) {
|
||||
dir, err := filepath.Abs(startDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
candidate := filepath.Join(dir, ".pgtidy.yaml")
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate, nil
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return "", nil
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
func validCase(c Case) error {
|
||||
switch c {
|
||||
case CaseUpper, CaseLower, CasePreserve:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("must be \"upper\", \"lower\", or \"preserve\"")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user