Implement module file search mechanism for "use"

This resolves issue #44.
This commit is contained in:
Cheer Xiao 2015-02-23 18:36:46 +01:00
parent 5fda0bc0a4
commit 1d59566aaf
7 changed files with 56 additions and 35 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/elves/elvish/parse"
)
@ -313,8 +314,16 @@ func compileUse(cp *Compiler, fn *parse.FormNode) exitusOp {
default:
cp.errorf(fn.Args.Nodes[2].Pos, "superfluous argument")
}
// TODO(xiaq): File name should be relative to the current source when it
// starts with . or .. and relative to ~/.elvish otherwise
switch {
case strings.HasPrefix(fname, "/"):
// Absolute file name, do nothing
case strings.HasPrefix(fname, "./") || strings.HasPrefix(fname, "../"):
// File name relative to current source
fname = path.Clean(path.Join(cp.dir, fname))
default:
// File name relative to data dir
fname = path.Clean(path.Join(cp.dataDir, fname))
}
src, err := readFileUTF8(fname)
if err != nil {
cp.errorf(fnameNode.Pos, "cannot read module: %s", err.Error())
@ -326,8 +335,8 @@ func compileUse(cp *Compiler, fn *parse.FormNode) exitusOp {
cp.errorf(fnameNode.Pos, "cannot parse module: %s", err.Error())
}
newCp := NewCompiler(cp.builtin)
op, err := newCp.Compile(fname, src, cn)
newCp := NewCompiler(cp.builtin, cp.dataDir)
op, err := newCp.Compile(fname, src, path.Dir(fname), cn)
if err != nil {
// TODO(xiaq): Pretty print
cp.errorf(fnameNode.Pos, "cannot compile module: %s", err.Error())
@ -337,7 +346,7 @@ func compileUse(cp *Compiler, fn *parse.FormNode) exitusOp {
return func(ev *Evaluator) exitus {
// XXX(xiaq): Should use some part of ev
newEv := NewEvaluator(ev.store)
newEv := NewEvaluator(ev.store, cp.dataDir)
op(newEv)
ev.mod[modname] = newEv.local
return success

View File

@ -18,25 +18,26 @@ type Compiler struct {
scopes []staticNS
up staticNS
mod map[string]staticNS
dataDir string
compilerEphemeral
}
// compilerEphemeral wraps the ephemeral parts of a Compiler, namely the parts
// only valid through one startCompile-stopCompile cycle.
type compilerEphemeral struct {
name, text string
name, text, dir string
}
// NewCompiler returns a new compiler.
func NewCompiler(bi staticNS) *Compiler {
func NewCompiler(bi staticNS, dataDir string) *Compiler {
return &Compiler{
bi, []staticNS{staticNS{}}, staticNS{}, map[string]staticNS{},
bi, []staticNS{staticNS{}}, staticNS{}, map[string]staticNS{}, dataDir,
compilerEphemeral{},
}
}
func (cp *Compiler) startCompile(name, text string) {
cp.compilerEphemeral = compilerEphemeral{name, text}
func (cp *Compiler) startCompile(name, text, dir string) {
cp.compilerEphemeral = compilerEphemeral{name, text, dir}
}
func (cp *Compiler) stopCompile() {
@ -45,8 +46,8 @@ func (cp *Compiler) stopCompile() {
// Compile compiles a ChunkNode into an Op. The supplied name and text are used
// in diagnostic messages.
func (cp *Compiler) Compile(name, text string, n *parse.ChunkNode) (op Op, err error) {
cp.startCompile(name, text)
func (cp *Compiler) Compile(name, text, dir string, n *parse.ChunkNode) (op Op, err error) {
cp.startCompile(name, text, dir)
defer cp.stopCompile()
defer errutil.Catch(&err)
return cp.chunk(n), nil

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"syscall"
@ -55,7 +56,7 @@ func statusOk(vs []Value) bool {
}
// NewEvaluator creates a new top-level Evaluator.
func NewEvaluator(st *store.Store) *Evaluator {
func NewEvaluator(st *store.Store, dataDir string) *Evaluator {
pid := str(strconv.Itoa(syscall.Getpid()))
bi := ns{
"pid": newInternalVariableWithType(pid),
@ -67,7 +68,7 @@ func NewEvaluator(st *store.Store) *Evaluator {
bi["fn-"+b.Name] = newInternalVariableWithType(b)
}
ev := &Evaluator{
Compiler: NewCompiler(makeCompilerScope(bi)),
Compiler: NewCompiler(makeCompilerScope(bi), dataDir),
local: ns{},
up: ns{},
builtin: bi,
@ -151,8 +152,8 @@ func makeCompilerScope(s ns) staticNS {
// Eval evaluates a chunk node n. The supplied name and text are used in
// diagnostic messages.
func (ev *Evaluator) Eval(name, text string, n *parse.ChunkNode) error {
op, err := ev.Compiler.Compile(name, text, n)
func (ev *Evaluator) Eval(name, text, dir string, n *parse.ChunkNode) error {
op, err := ev.Compiler.Compile(name, text, dir, n)
if err != nil {
return err
}
@ -210,12 +211,12 @@ func (ev *Evaluator) applyPortOps(ports []portOp) {
}
}
func (ev *Evaluator) SourceText(name, src string) error {
func (ev *Evaluator) SourceText(name, src, dir string) error {
n, err := parse.Parse(name, src)
if err != nil {
return err
}
return ev.Eval(name, src, n)
return ev.Eval(name, src, dir, n)
}
func readFileUTF8(fname string) (string, error) {
@ -235,7 +236,7 @@ func (ev *Evaluator) Source(fname string) error {
if err != nil {
return err
}
return ev.SourceText(fname, src)
return ev.SourceText(fname, src, path.Dir(fname))
}
// ResolveVar resolves a variable. When the variable cannot be found, nil is

View File

@ -10,7 +10,7 @@ import (
)
func TestNewEvaluator(t *testing.T) {
ev := NewEvaluator(nil)
ev := NewEvaluator(nil, "")
pid := strconv.Itoa(syscall.Getpid())
if toString(ev.builtin["pid"].Get()) != pid {
t.Errorf(`ev.builtin["pid"] = %v, want %v`, ev.builtin["pid"], pid)
@ -132,7 +132,7 @@ func TestEval(t *testing.T) {
for _, tt := range evalTests {
n := mustParse(name, tt.text)
ev := NewEvaluator(nil)
ev := NewEvaluator(nil, ".")
out := make(chan Value, len(tt.wanted))
outs := []Value{}
exhausted := make(chan struct{})
@ -145,7 +145,7 @@ func TestEval(t *testing.T) {
ev.ports[1].ch = out
e := ev.Eval(name, tt.text, n)
e := ev.Eval(name, tt.text, ".", n)
close(out)
<-exhausted
if tt.wantError {

16
main.go
View File

@ -30,12 +30,20 @@ func newEvaluator() *eval.Evaluator {
}
}()
st, err := store.NewStore()
dataDir, err := store.EnsureDataDir()
if err != nil {
fmt.Fprintln(os.Stderr, "Warning: cannot connect to store:", err)
fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish")
}
ev := eval.NewEvaluator(st)
var st *store.Store
if err == nil {
st, err = store.NewStore(dataDir)
if err != nil {
fmt.Fprintln(os.Stderr, "Warning: cannot connect to store:", err)
}
}
ev := eval.NewEvaluator(st, dataDir)
ev.SetChanOut(ch)
return ev
}
@ -105,7 +113,7 @@ func interact() {
printError(err)
if err == nil {
err := ev.Eval(name, lr.Line, n)
err := ev.Eval(name, lr.Line, ".", n)
printError(err)
}
}

View File

@ -15,19 +15,15 @@ type Store struct {
var createTable = map[string]string{}
// DefaultDB returns the default database for storage.
func DefaultDB() (*sql.DB, error) {
ddir, err := EnsureDataDir()
if err != nil {
return nil, err
}
uri := "file:" + url.QueryEscape(ddir+"/db") +
func DefaultDB(dataDir string) (*sql.DB, error) {
uri := "file:" + url.QueryEscape(dataDir+"/db") +
"?mode=rwc&cache=shared&vfs=unix-dotfile"
return sql.Open("sqlite3", uri)
}
// NewStore creates a new Store with the default database.
func NewStore() (*Store, error) {
db, err := DefaultDB()
func NewStore(dataDir string) (*Store, error) {
db, err := DefaultDB(dataDir)
if err != nil {
return nil, err
}

View File

@ -24,7 +24,13 @@ func init() {
}
func TestNewStore(t *testing.T) {
_, err := NewStore()
// XXX(xiaq): Also tests EnsureDataDir
dataDir, err := EnsureDataDir()
if err != nil {
t.Errorf("EnsureDataDir() -> (*, %v), want (*, <nil>)", err)
}
_, err = NewStore(dataDir)
if err != nil {
t.Errorf("NewStore() -> (*, %v), want (*, <nil>)", err)
}