Convert BKL methods to standalone functions and remove Ext wrapper

This commit is contained in:
Ian Gulliver
2025-06-26 18:03:34 -07:00
parent a14cffdab8
commit e475e1eb59
13 changed files with 66 additions and 107 deletions
+6 -6
View File
@@ -632,7 +632,7 @@ func evaluateHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
switch {
case required:
// Use RequiredFile helper
requiredResult, err := p.RequiredFile(testFS, files[0])
requiredResult, err := bkl.RequiredFile(testFS, files[0])
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Required operation failed: %v", err)), nil
}
@@ -641,14 +641,14 @@ func evaluateHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(requiredResult, format)
output, err = bkl.FormatOutput(requiredResult, format)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal required result: %v", err)), nil
}
case intersect:
// Use IntersectFiles helper
intersectResult, err := p.IntersectFiles(testFS, files)
intersectResult, err := bkl.IntersectFiles(testFS, files)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Intersect operation failed: %v", err)), nil
}
@@ -657,14 +657,14 @@ func evaluateHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(intersectResult, format)
output, err = bkl.FormatOutput(intersectResult, format)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal intersect result: %v", err)), nil
}
case diff:
// Use DiffFiles helper
diffResult, err := p.DiffFiles(testFS, files[0], files[1])
diffResult, err := bkl.DiffFiles(testFS, files[0], files[1])
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Diff operation failed: %v", err)), nil
}
@@ -673,7 +673,7 @@ func evaluateHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(diffResult, format)
output, err = bkl.FormatOutput(diffResult, format)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal diff result: %v", err)), nil
}
+2 -2
View File
@@ -83,7 +83,7 @@ Related tools:
if opts.OutputFormat != nil {
format = *opts.OutputFormat
} else if opts.OutputPath != nil {
format = p.Ext(string(*opts.OutputPath))
format = bkl.Ext(string(*opts.OutputPath))
}
files := make([]string, len(opts.Positional.InputPaths))
@@ -92,7 +92,7 @@ Related tools:
}
fsys := os.DirFS(opts.RootPath)
output, err := p.Evaluate(fsys, files, format, opts.RootPath, wd, p.GetOSEnv())
output, err := p.Evaluate(fsys, files, format, opts.RootPath, wd, bkl.GetOSEnv())
if err != nil {
fatal(err)
}
+5 -12
View File
@@ -3,9 +3,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
"github.com/gopatchy/bkl"
"github.com/jessevdk/go-flags"
@@ -52,38 +50,33 @@ See https://bkl.gopatchy.io/#bkld for detailed documentation.`
}
if format == "" && opts.OutputPath != nil {
format = strings.TrimPrefix(filepath.Ext(string(*opts.OutputPath)), ".")
}
p, err := bkl.New()
if err != nil {
fatal(err)
format = bkl.Ext(string(*opts.OutputPath))
}
// Prepare paths from current working directory
paths := []string{string(opts.Positional.BasePath), string(opts.Positional.TargetPath)}
preparedPaths, err := p.PreparePathsFromCwd(paths, "/")
preparedPaths, err := bkl.PreparePathsFromCwd(paths, "/")
if err != nil {
fatal(err)
}
// Use DiffFiles helper which handles loading and validation
fsys := os.DirFS("/")
doc, err := p.DiffFiles(fsys, preparedPaths[0], preparedPaths[1])
doc, err := bkl.DiffFiles(fsys, preparedPaths[0], preparedPaths[1])
if err != nil {
fatal(err)
}
// Get format from first file if not specified
if format == "" {
_, f, err := p.FileMatch(fsys, preparedPaths[0])
_, f, err := bkl.FileMatch(fsys, preparedPaths[0])
if err != nil {
fatal(err)
}
format = f
}
enc, err := p.FormatOutput(doc, format)
enc, err := bkl.FormatOutput(doc, format)
if err != nil {
fatal(err)
}
+5 -12
View File
@@ -3,9 +3,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
"github.com/gopatchy/bkl"
"github.com/jessevdk/go-flags"
@@ -51,12 +49,7 @@ See https://bkl.gopatchy.io/#bkli for detailed documentation.`
}
if format == "" && opts.OutputPath != nil {
format = strings.TrimPrefix(filepath.Ext(string(*opts.OutputPath)), ".")
}
p, err := bkl.New()
if err != nil {
fatal(err)
format = bkl.Ext(string(*opts.OutputPath))
}
// Convert paths to strings
@@ -66,28 +59,28 @@ See https://bkl.gopatchy.io/#bkli for detailed documentation.`
}
// Prepare paths from current working directory
preparedPaths, err := p.PreparePathsFromCwd(paths, "/")
preparedPaths, err := bkl.PreparePathsFromCwd(paths, "/")
if err != nil {
fatal(err)
}
// Use IntersectFiles helper which handles loading and validation
fsys := os.DirFS("/")
doc, err := p.IntersectFiles(fsys, preparedPaths)
doc, err := bkl.IntersectFiles(fsys, preparedPaths)
if err != nil {
fatal(err)
}
// Get format from first file if not specified
if format == "" {
_, f, err := p.FileMatch(fsys, preparedPaths[0])
_, f, err := bkl.FileMatch(fsys, preparedPaths[0])
if err != nil {
fatal(err)
}
format = f
}
enc, err := p.FormatOutput(doc, format)
enc, err := bkl.FormatOutput(doc, format)
if err != nil {
fatal(err)
}
+5 -12
View File
@@ -3,9 +3,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
"github.com/gopatchy/bkl"
"github.com/jessevdk/go-flags"
@@ -44,20 +42,15 @@ See https://bkl.gopatchy.io/#bklr for detailed documentation.`
os.Exit(1)
}
p, err := bkl.New()
if err != nil {
fatal(err)
}
// Prepare path from current working directory
preparedPaths, err := p.PreparePathsFromCwd([]string{string(opts.Positional.InputPath)}, "/")
preparedPaths, err := bkl.PreparePathsFromCwd([]string{string(opts.Positional.InputPath)}, "/")
if err != nil {
fatal(err)
}
// Use RequiredFile helper which handles loading and validation
fsys := os.DirFS("/")
out, err := p.RequiredFile(fsys, preparedPaths[0])
out, err := bkl.RequiredFile(fsys, preparedPaths[0])
if err != nil {
fatal(err)
}
@@ -65,7 +58,7 @@ See https://bkl.gopatchy.io/#bklr for detailed documentation.`
// Get format from file if not specified
format := ""
if opts.OutputPath != nil {
format = strings.TrimPrefix(filepath.Ext(string(*opts.OutputPath)), ".")
format = bkl.Ext(string(*opts.OutputPath))
}
if opts.OutputFormat != nil {
@@ -73,14 +66,14 @@ See https://bkl.gopatchy.io/#bklr for detailed documentation.`
}
if format == "" {
_, f, err := p.FileMatch(fsys, preparedPaths[0])
_, f, err := bkl.FileMatch(fsys, preparedPaths[0])
if err != nil {
fatal(err)
}
format = f
}
enc, err := p.FormatOutput(out, format)
enc, err := bkl.FormatOutput(out, format)
if err != nil {
fatal(err)
}
+4 -10
View File
@@ -11,14 +11,14 @@ import (
// DiffFiles loads two files and returns the diff between them.
// It expects each file to contain exactly one document.
// The files are loaded directly without processing, matching bkld behavior.
func (b *BKL) DiffFiles(fsys fs.FS, srcPath, dstPath string) (any, error) {
func DiffFiles(fsys fs.FS, srcPath, dstPath string) (any, error) {
// Load source file
p1, err := New()
if err != nil {
return nil, err
}
realSrcPath, _, err := p1.FileMatch(fsys, srcPath)
realSrcPath, _, err := FileMatch(fsys, srcPath)
if err != nil {
return nil, fmt.Errorf("source file %s: %w", srcPath, err)
}
@@ -48,7 +48,7 @@ func (b *BKL) DiffFiles(fsys fs.FS, srcPath, dstPath string) (any, error) {
return nil, err
}
realDstPath, _, err := p2.FileMatch(fsys, dstPath)
realDstPath, _, err := FileMatch(fsys, dstPath)
if err != nil {
return nil, fmt.Errorf("destination file %s: %w", dstPath, err)
}
@@ -73,13 +73,7 @@ func (b *BKL) DiffFiles(fsys fs.FS, srcPath, dstPath string) (any, error) {
}
// Perform diff
return b.diff(srcDocs[0].Data, dstDocs[0].Data, nil)
}
// diff generates the minimal intermediate layer needed to transform src into dst.
// It returns a document that, when merged with src, produces dst.
func (b *BKL) diff(src, dst any, env map[string]string) (any, error) {
result, err := diff(dst, src)
result, err := diff(dstDocs[0].Data, srcDocs[0].Data)
if err != nil {
return nil, err
}
+3 -3
View File
@@ -9,8 +9,8 @@ import (
"strings"
)
// ext returns the file extension without the leading dot.
func ext(path string) string {
// Ext returns the file extension without the leading dot.
func Ext(path string) string {
return strings.TrimPrefix(filepath.Ext(path), ".")
}
@@ -34,7 +34,7 @@ func loadFile(fsys *fileSystem, path string, child *file) (*file, error) {
debugLog("[%s] loading", f)
format, err := getFormat(ext(path))
format, err := getFormat(Ext(path))
if err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
+1 -1
View File
@@ -94,7 +94,7 @@ func (f *fileSystem) globFiles(path string) ([]string, error) {
ret := []string{}
for _, match := range matches {
if _, found := formatByExtension[ext(match)]; !found {
if _, found := formatByExtension[Ext(match)]; !found {
continue
}
+3 -9
View File
@@ -9,7 +9,7 @@ import (
// IntersectFiles loads multiple files and returns their intersection.
// It expects each file to contain exactly one document.
// The files are loaded directly without processing, matching bkli behavior.
func (b *BKL) IntersectFiles(fsys fs.FS, paths []string) (any, error) {
func IntersectFiles(fsys fs.FS, paths []string) (any, error) {
if len(paths) < 2 {
return nil, fmt.Errorf("intersect requires at least 2 files, got %d", len(paths))
}
@@ -23,7 +23,7 @@ func (b *BKL) IntersectFiles(fsys fs.FS, paths []string) (any, error) {
return nil, err
}
realPath, _, err := parser.FileMatch(fsys, path)
realPath, _, err := FileMatch(fsys, path)
if err != nil {
return nil, fmt.Errorf("file %s: %w", path, err)
}
@@ -52,7 +52,7 @@ func (b *BKL) IntersectFiles(fsys fs.FS, paths []string) (any, error) {
continue
}
result, err = b.intersect(result, docs[0].Data)
result, err = intersect(result, docs[0].Data)
if err != nil {
return nil, err
}
@@ -61,12 +61,6 @@ func (b *BKL) IntersectFiles(fsys fs.FS, paths []string) (any, error) {
return result, nil
}
// intersect returns the intersection of two values.
// For lists, it returns elements present in both lists.
func (b *BKL) intersect(a, bVal any) (any, error) {
return intersect(a, bVal)
}
func intersect(a, b any) (any, error) {
if b == nil {
return nil, nil
+6 -6
View File
@@ -76,7 +76,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
}
// Use the RequiredFile helper which matches bklr behavior
requiredResult, err := p.RequiredFile(testFS, testCase.Eval[0])
requiredResult, err := bkl.RequiredFile(testFS, testCase.Eval[0])
if err != nil {
return nil, err
}
@@ -86,7 +86,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(requiredResult, format)
output, err = bkl.FormatOutput(requiredResult, format)
if err != nil {
return nil, err
}
@@ -98,7 +98,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
}
// Use the IntersectFiles helper which matches bkli behavior
intersectResult, err := p.IntersectFiles(testFS, testCase.Eval)
intersectResult, err := bkl.IntersectFiles(testFS, testCase.Eval)
if err != nil {
return nil, err
}
@@ -108,7 +108,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(intersectResult, format)
output, err = bkl.FormatOutput(intersectResult, format)
if err != nil {
return nil, err
}
@@ -120,7 +120,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
}
// Use the DiffFiles helper which matches bkld behavior
diffResult, err := p.DiffFiles(testFS, testCase.Eval[0], testCase.Eval[1])
diffResult, err := bkl.DiffFiles(testFS, testCase.Eval[0], testCase.Eval[1])
if err != nil {
return nil, err
}
@@ -130,7 +130,7 @@ func runTestCase(testCase TestCase) ([]byte, error) {
if format == "" {
format = "yaml"
}
output, err = p.FormatOutput(diffResult, format)
output, err = bkl.FormatOutput(diffResult, format)
if err != nil {
return nil, err
}
+19 -21
View File
@@ -303,7 +303,7 @@ func (b *BKL) output(format string, env map[string]string) ([]byte, error) {
// If format is "", it is inferred from path's file extension.
func (b *BKL) OutputToFile(path, format string, env map[string]string) error {
if format == "" {
format = ext(path)
format = Ext(path)
}
fh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
@@ -343,7 +343,8 @@ func (b *BKL) outputToWriter(fh io.Writer, format string, env map[string]string)
return nil
}
func (b *BKL) makePathsAbsolute(paths []string, workingDir string) ([]string, error) {
// makePathsAbsolute converts relative paths to absolute paths using the provided working directory.
func makePathsAbsolute(paths []string, workingDir string) ([]string, error) {
result := make([]string, len(paths))
for i, path := range paths {
if filepath.IsAbs(path) {
@@ -355,7 +356,8 @@ func (b *BKL) makePathsAbsolute(paths []string, workingDir string) ([]string, er
return result, nil
}
func (b *BKL) rebasePathsToRoot(absPaths []string, rootPath string, workingDir string) ([]string, error) {
// rebasePathsToRoot rebases absolute paths to be relative to the root path.
func rebasePathsToRoot(absPaths []string, rootPath string, workingDir string) ([]string, error) {
absRootPath := rootPath
if !filepath.IsAbs(rootPath) {
absRootPath = filepath.Join(workingDir, rootPath)
@@ -378,28 +380,29 @@ func (b *BKL) rebasePathsToRoot(absPaths []string, rootPath string, workingDir s
return result, nil
}
func (b *BKL) preparePathsForParser(paths []string, rootPath string, workingDir string) ([]string, error) {
absPaths, err := b.makePathsAbsolute(paths, workingDir)
// preparePathsForParser prepares paths by making them absolute and rebasing to root.
func preparePathsForParser(paths []string, rootPath string, workingDir string) ([]string, error) {
absPaths, err := makePathsAbsolute(paths, workingDir)
if err != nil {
return nil, err
}
return b.rebasePathsToRoot(absPaths, rootPath, workingDir)
return rebasePathsToRoot(absPaths, rootPath, workingDir)
}
// PreparePathsFromCwd prepares file paths relative to the current working directory
// and rebases them to the given root path.
func (b *BKL) PreparePathsFromCwd(paths []string, rootPath string) ([]string, error) {
func PreparePathsFromCwd(paths []string, rootPath string) ([]string, error) {
wd, err := os.Getwd()
if err != nil {
return nil, err
}
return b.preparePathsForParser(paths, rootPath, wd)
return preparePathsForParser(paths, rootPath, wd)
}
// GetOSEnv returns the current OS environment as a map.
func (b *BKL) GetOSEnv() map[string]string {
func GetOSEnv() map[string]string {
env := make(map[string]string)
for _, e := range os.Environ() {
parts := strings.SplitN(e, "=", 2)
@@ -410,14 +413,9 @@ func (b *BKL) GetOSEnv() map[string]string {
return env
}
// Ext returns the file extension without the leading dot.
func (b *BKL) Ext(path string) string {
return ext(path)
}
// FormatOutput marshals the given data to the specified format.
// Returns the marshaled bytes or an error if the format is unknown or marshaling fails.
func (b *BKL) FormatOutput(data any, format string) ([]byte, error) {
func FormatOutput(data any, format string) ([]byte, error) {
f, found := formatByExtension[format]
if !found {
return nil, fmt.Errorf("%s: %w", format, ErrUnknownFormat)
@@ -428,14 +426,14 @@ func (b *BKL) FormatOutput(data any, format string) ([]byte, error) {
}
func (b *BKL) Evaluate(fsys fs.FS, files []string, format string, rootPath string, workingDir string, env map[string]string) ([]byte, error) {
evalFiles, err := b.preparePathsForParser(files, rootPath, workingDir)
evalFiles, err := preparePathsForParser(files, rootPath, workingDir)
if err != nil {
return nil, err
}
realFiles := make([]string, len(evalFiles))
for i, path := range evalFiles {
realPath, fileFormat, err := b.FileMatch(fsys, path)
realPath, fileFormat, err := FileMatch(fsys, path)
if err != nil {
return nil, fmt.Errorf("file %s: %w", path, err)
}
@@ -451,14 +449,14 @@ func (b *BKL) Evaluate(fsys fs.FS, files []string, format string, rootPath strin
// EvaluateToData is like Evaluate but returns the raw data instead of marshaled output
func (b *BKL) EvaluateToData(fsys fs.FS, files []string, format string, rootPath string, workingDir string, env map[string]string) (any, error) {
evalFiles, err := b.preparePathsForParser(files, rootPath, workingDir)
evalFiles, err := preparePathsForParser(files, rootPath, workingDir)
if err != nil {
return nil, err
}
realFiles := make([]string, len(evalFiles))
for i, path := range evalFiles {
realPath, fileFormat, err := b.FileMatch(fsys, path)
realPath, fileFormat, err := FileMatch(fsys, path)
if err != nil {
return nil, fmt.Errorf("file %s: %w", path, err)
}
@@ -495,8 +493,8 @@ func (b *BKL) EvaluateToData(fsys fs.FS, files []string, format string, rootPath
//
// Returns the real filename and the requested output format, or
// ("", "", error).
func (b *BKL) FileMatch(fsys fs.FS, path string) (string, string, error) {
format := ext(path)
func FileMatch(fsys fs.FS, path string) (string, string, error) {
format := Ext(path)
if _, found := formatByExtension[format]; !found {
return "", "", fmt.Errorf("%s: %w", format, ErrUnknownFormat)
}
+3 -9
View File
@@ -8,14 +8,14 @@ import (
// RequiredFile loads a file and returns only the required fields and their ancestors.
// It expects the file to contain exactly one document.
// The file is loaded directly without processing, matching bklr behavior.
func (b *BKL) RequiredFile(fsys fs.FS, path string) (any, error) {
func RequiredFile(fsys fs.FS, path string) (any, error) {
// Create new parser for the file
parser, err := New()
if err != nil {
return nil, err
}
realPath, _, err := parser.FileMatch(fsys, path)
realPath, _, err := FileMatch(fsys, path)
if err != nil {
return nil, fmt.Errorf("file %s: %w", path, err)
}
@@ -39,13 +39,7 @@ func (b *BKL) RequiredFile(fsys fs.FS, path string) (any, error) {
return nil, fmt.Errorf("required operates on exactly 1 document, got %d in %s", len(docs), path)
}
return b.required(docs[0].Data)
}
// required returns only the required fields and their ancestors from the given value.
// It recursively traverses the value and keeps only paths that lead to "$required" markers.
func (b *BKL) required(obj any) (any, error) {
return required(obj)
return required(docs[0].Data)
}
func required(obj any) (any, error) {
+4 -4
View File
@@ -37,18 +37,18 @@ func WrapOrDie(cmd string) {
}
// Prepare the path for FileMatch
preparedPaths, err := b.PreparePathsFromCwd([]string{arg}, "/")
preparedPaths, err := bkl.PreparePathsFromCwd([]string{arg}, "/")
if err != nil {
continue
}
fsys := os.DirFS("/")
realPath, f, err := b.FileMatch(fsys, preparedPaths[0])
realPath, f, err := bkl.FileMatch(fsys, preparedPaths[0])
if err != nil {
continue
}
_, err = b.MergeFiles(fsys, []string{realPath}, f, b.GetOSEnv())
_, err = b.MergeFiles(fsys, []string{realPath}, f, bkl.GetOSEnv())
if err != nil {
fatal(err)
}
@@ -64,7 +64,7 @@ func WrapOrDie(cmd string) {
fatal(err)
}
err = b.OutputToFile(tmp.Name(), f, b.GetOSEnv())
err = b.OutputToFile(tmp.Name(), f, bkl.GetOSEnv())
if err != nil {
fatal(err)
}