~sbinet/star-tex-patches

[PATCH star-tex 2/3] kpath: introduce New{,FromFS} contexts

Details
Message ID
<HbGhfDjP4Z5Uehbtsbb6BQY0OlnOJHyr1R7SKU7Y@cp3-web-029.plabs.ch>
DKIM signature
missing
Download raw message
Patch: +177 -7
Signed-off-by: Sebastien Binet <s@sbinet.org>
---
 kpath/kpath.go      |  60 ++++++++++++++++++---
 kpath/kpath_test.go | 124 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 177 insertions(+), 7 deletions(-)

diff --git a/kpath/kpath.go b/kpath/kpath.go
index a92aec8..afafffe 100644
--- a/kpath/kpath.go
+++ b/kpath/kpath.go
@@ -11,10 +11,28 @@ package kpath // import "star-tex.org/x/tex/kpath"
import (
	"fmt"
	"io"
	"io/fs"
	stdpath "path"
	"strings"
	"sync"

	"star-tex.org/x/tex/internal/tds"
)

var (
	once   sync.Once
	tdsCtx Context
)

// New returns a minimal kpath context initialized with the content of
// a minimal TeX Directory Structure.
func New() Context {
	once.Do(func() {
		tdsCtx, _ = NewFromFS(tds.FS)
	})
	return tdsCtx
}

// Context holds state to efficiently search for files in a TDS
// (TeX Directory Structure), as described in:
//  - http://tug.org/tds/tds.pdf
@@ -32,12 +50,6 @@ func (ctx *Context) init() {
	}
}

// func New(root string) Context {
// 	ctx := Context{}
// 	ctx.init()
// 	return ctx
// }
//
// // NewFromDB creates a kpath search from a TeX .cnf configuration file.
// func NewFromConfig(cfg io.Reader) (Context, error) {
// 	ctx, err := parseConfig(cfg)
@@ -64,6 +76,41 @@ func NewFromDB(r io.Reader) (Context, error) {
	return ctx, nil
}

// NewFromFS creates a kpath search context from the provided filesystem.
//
// NewFromFS checks first whether an ls-R database exists at the root of the
// provided filesystem, and otherwise walks the whole fs.
func NewFromFS(fsys fs.FS) (Context, error) {
	var ctx Context
	ctx.init()

	if _, err := fs.Stat(fsys, "ls-R"); err == nil {
		db, err := fsys.Open("ls-R")
		if err != nil {
			return ctx, fmt.Errorf("kpath: could not open db file: %w", err)
		}
		defer db.Close()
		return NewFromDB(db)
	}

	err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		fname := stdpath.Base(path)
		ctx.db[fname] = append(ctx.db[fname], path)
		return nil
	})
	if err != nil {
		return ctx, fmt.Errorf("kpath: could not walk fs: %w", err)
	}

	return ctx, nil
}

// Find returns the full path to the named file if it could be found within the
// TeXMF distribution system.
// Find returns an error if no file or more than one file were found.
@@ -89,7 +136,6 @@ func (ctx Context) Find(name string) (string, error) {
func (ctx Context) FindAll(name string) ([]string, error) {
	// TODO(sbinet): handle (all) standard exts.
	// TODO(sbinet): handle multi-root TEXMFs
	// FIXME(sbinet): normalize all paths to use UNIX path separator ?

	orig := name
	name = strings.Replace(name, "\\", "/", -1)
diff --git a/kpath/kpath_test.go b/kpath/kpath_test.go
index 02994d2..939db07 100644
--- a/kpath/kpath_test.go
+++ b/kpath/kpath_test.go
@@ -7,6 +7,9 @@ package kpath
import (
	"fmt"
	"io"
	"os"
	stdpath "path"
	"path/filepath"
	"strings"
	"testing"
)
@@ -227,3 +230,124 @@ TEXPICTS.XDvi  = .;$TEXMF/%q{dvips,tex}//

	_ = ctx // FIXME(sbinet): test Find/FindAll
}

func TestFindFromFS(t *testing.T) {
	ctx := New()
	for _, tc := range []struct {
		name string
		want string
		err  error
	}{
		{
			name: "plain.tex",
			want: "tex/plain/base/plain.tex",
		},
		{
			name: "hyphen.tex",
			want: "tex/generic/hyphen/hyphen.tex",
		},
		{
			name: "cmr10.tfm",
			want: "fonts/tfm/public/cm/cmr10.tfm",
		},
		{
			name: "not-there.tex",
			err:  fmt.Errorf(`kpath: could not find file "not-there.tex"`),
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := ctx.Find(tc.name)
			switch {
			case err == nil && tc.err == nil:
				// ok.
			case err != nil && tc.err != nil:
				if got, want := err.Error(), tc.err.Error(); got != want {
					t.Fatalf("invalid error:\ngot= %s\nwant=%s\n", got, want)
				}
				return
			case err != nil && tc.err == nil:
				t.Fatalf("could not run kpath-find: %+v", err)
			case err == nil && tc.err != nil:
				t.Fatalf("missing error. expected: %+v", tc.err)
			}

			if got != tc.want {
				t.Fatalf("invalid file named:\ngot= %s\nwant=%s", got, tc.want)
			}
		})
	}
}

func TestNewFromFS(t *testing.T) {
	dir, err := os.MkdirTemp("", "star-tex-kpath-")
	if err != nil {
		t.Fatalf("could not create tmp dir: %+v", err)
	}
	defer os.RemoveAll(dir)

	dbname := stdpath.Join(dir, "ls-R")
	err = os.WriteFile(dbname, []byte(`%%
./:
./dir1:
file1.tex

./dir2:
file2.tex
`), 0644)
	if err != nil {
		t.Fatalf("could not create texmf db: %+v", err)
	}
	db := os.DirFS(dir)

	err = os.MkdirAll(filepath.Join(dir, "dir1"), 0755)
	if err != nil {
		t.Fatalf("could not create dir1: %+v", err)
	}

	err = os.WriteFile(filepath.Join(dir, "dir1", "file1.tex"), []byte(""), 0644)
	if err != nil {
		t.Fatalf("could not create dir1/file1.tex: %+v", err)
	}

	ctx, err := NewFromFS(db)
	if err != nil {
		t.Fatalf("could not create kpath context: %+v", err)
	}

	for _, tc := range []struct {
		name string
		want string
		err  error
	}{
		{
			name: "file1.tex",
			want: stdpath.Join(dir, "dir1", "file1.tex"),
		},
		{
			// test NewFromFS only considered ls-R informations.
			name: "file2.tex",
			want: stdpath.Join(dir, "dir2", "file2.tex"),
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := ctx.Find(tc.name)
			switch {
			case err == nil && tc.err == nil:
				// ok.
			case err != nil && tc.err != nil:
				if got, want := err.Error(), tc.err.Error(); got != want {
					t.Fatalf("invalid error:\ngot= %s\nwant=%s\n", got, want)
				}
				return
			case err != nil && tc.err == nil:
				t.Fatalf("could not run kpath-find: %+v", err)
			case err == nil && tc.err != nil:
				t.Fatalf("missing error. expected: %+v", tc.err)
			}

			if got != tc.want {
				t.Fatalf("invalid file named:\ngot= %s\nwant=%s", got, tc.want)
			}
		})
	}
}
-- 
2.30.1
Reply to thread Export thread (mbox)