feat: initial plan
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/hein/pgtidy/pkg/config"
|
||||
"github.com/hein/pgtidy/pkg/format"
|
||||
"github.com/hein/pgtidy/pkg/parser"
|
||||
)
|
||||
|
||||
// cmdFmt implements `pgtidy fmt`. It follows the gofmt model: with no flags it
|
||||
// prints the formatted result to stdout; -w rewrites in place; -l lists files
|
||||
// that differ; --check exits non-zero if any input is unformatted.
|
||||
func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
var (
|
||||
write bool
|
||||
list bool
|
||||
check bool
|
||||
files []string
|
||||
)
|
||||
for _, a := range args {
|
||||
switch a {
|
||||
case "-w", "--write":
|
||||
write = true
|
||||
case "-l", "--list":
|
||||
list = true
|
||||
case "--check":
|
||||
check = true
|
||||
case "-h", "--help":
|
||||
usage(stdout)
|
||||
return 0
|
||||
default:
|
||||
if len(a) > 1 && a[0] == '-' {
|
||||
fmt.Fprintf(stderr, "pgtidy fmt: unknown flag %q\n", a)
|
||||
return 2
|
||||
}
|
||||
files = append(files, a)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: discover and parse .pgtidy.yaml; for now use the house-style default.
|
||||
st := config.Default()
|
||||
|
||||
// stdin → stdout when no files are given.
|
||||
if len(files) == 0 {
|
||||
src, err := io.ReadAll(stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "pgtidy: reading stdin: %v\n", err)
|
||||
return 2
|
||||
}
|
||||
out := format.File(parser.Parse(string(src)), st)
|
||||
if check {
|
||||
if out != string(src) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
io.WriteString(stdout, out)
|
||||
return 0
|
||||
}
|
||||
|
||||
exit := 0
|
||||
anyDiff := false
|
||||
for _, path := range files {
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "pgtidy: %v\n", err)
|
||||
exit = 2
|
||||
continue
|
||||
}
|
||||
out := format.File(parser.Parse(string(src)), st)
|
||||
changed := out != string(src)
|
||||
if changed {
|
||||
anyDiff = true
|
||||
}
|
||||
switch {
|
||||
case write:
|
||||
if changed {
|
||||
if err := os.WriteFile(path, []byte(out), 0o644); err != nil {
|
||||
fmt.Fprintf(stderr, "pgtidy: writing %s: %v\n", path, err)
|
||||
exit = 2
|
||||
}
|
||||
}
|
||||
case list:
|
||||
if changed {
|
||||
fmt.Fprintln(stdout, path)
|
||||
}
|
||||
case check:
|
||||
// handled after loop via anyDiff
|
||||
default:
|
||||
io.WriteString(stdout, out)
|
||||
}
|
||||
}
|
||||
if check && anyDiff && exit == 0 {
|
||||
return 1
|
||||
}
|
||||
return exit
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFmtStdin(t *testing.T) {
|
||||
in := strings.NewReader("create function f() returns int language sql as $$ select 1 $$;\n")
|
||||
var out, errb bytes.Buffer
|
||||
rc := run([]string{"fmt"}, in, &out, &errb)
|
||||
if rc != 0 {
|
||||
t.Fatalf("rc=%d stderr=%s", rc, errb.String())
|
||||
}
|
||||
if !strings.HasPrefix(out.String(), "CREATE FUNCTION f(") {
|
||||
t.Errorf("unexpected output:\n%s", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFmtCheckStdin(t *testing.T) {
|
||||
unformatted := "create function f() returns int language sql as $$ select 1 $$;"
|
||||
|
||||
// Unformatted input → rc 1.
|
||||
var out, errb bytes.Buffer
|
||||
if rc := run([]string{"fmt", "--check"}, strings.NewReader(unformatted), &out, &errb); rc != 1 {
|
||||
t.Errorf("unformatted input: rc=%d, want 1", rc)
|
||||
}
|
||||
|
||||
// Its own formatted output → rc 0 (idempotent + check agree).
|
||||
out.Reset()
|
||||
errb.Reset()
|
||||
run([]string{"fmt"}, strings.NewReader(unformatted), &out, &errb)
|
||||
formatted := out.String()
|
||||
var out2, errb2 bytes.Buffer
|
||||
if rc := run([]string{"fmt", "--check"}, strings.NewReader(formatted), &out2, &errb2); rc != 0 {
|
||||
t.Errorf("formatted input: rc=%d, want 0\noutput was:\n%s", rc, formatted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFmtWriteInPlace(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "f.pgsql")
|
||||
orig := "create function f() returns int language sql as $$ select 1 $$;\n"
|
||||
if err := os.WriteFile(path, []byte(orig), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var out, errb bytes.Buffer
|
||||
if rc := run([]string{"fmt", "-w", path}, nil, &out, &errb); rc != 0 {
|
||||
t.Fatalf("rc=%d stderr=%s", rc, errb.String())
|
||||
}
|
||||
got, _ := os.ReadFile(path)
|
||||
if string(got) == orig {
|
||||
t.Error("file was not rewritten")
|
||||
}
|
||||
// Second write is a no-op (idempotent).
|
||||
if rc := run([]string{"fmt", "--check", path}, nil, &out, &errb); rc != 0 {
|
||||
t.Errorf("after -w, --check rc=%d, want 0", rc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownCommand(t *testing.T) {
|
||||
var out, errb bytes.Buffer
|
||||
if rc := run([]string{"frobnicate"}, nil, &out, &errb); rc != 2 {
|
||||
t.Errorf("rc=%d, want 2", rc)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Command pgtidy is the PgTidy CLI: a PostgreSQL formatter (and, later, linter)
|
||||
// and LSP server. Today it provides the `fmt` subcommand.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// version is overridden at build time via -ldflags "-X main.version=...".
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
usage(stderr)
|
||||
return 2
|
||||
}
|
||||
switch args[0] {
|
||||
case "fmt", "format":
|
||||
return cmdFmt(args[1:], stdin, stdout, stderr)
|
||||
case "version", "--version", "-v":
|
||||
fmt.Fprintf(stdout, "pgtidy %s\n", version)
|
||||
return 0
|
||||
case "help", "-h", "--help":
|
||||
usage(stdout)
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintf(stderr, "pgtidy: unknown command %q\n", args[0])
|
||||
usage(stderr)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func usage(w io.Writer) {
|
||||
fmt.Fprint(w, `pgtidy — PostgreSQL formatter and linter
|
||||
|
||||
Usage:
|
||||
pgtidy fmt [flags] [files...] Format SQL/PL-pgSQL (stdin if no files)
|
||||
pgtidy version Print version
|
||||
pgtidy help Show this help
|
||||
|
||||
fmt flags:
|
||||
-w, --write Rewrite files in place
|
||||
-l, --list List files whose formatting differs (no writes)
|
||||
--check Exit non-zero if any input is not already formatted (CI)
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user