mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
Add use
unit tests and tweak documentation
This explicitly tests a common error case not currently verified by the existing unit tests. Thus improving test coverage by one line. :-)
This commit is contained in:
parent
7350a3351b
commit
8faf8930b3
|
@ -36,9 +36,11 @@ var builtinSpecials map[string]compileBuiltin
|
||||||
// intended for external consumption, e.g. the syntax highlighter.
|
// intended for external consumption, e.g. the syntax highlighter.
|
||||||
var IsBuiltinSpecial = map[string]bool{}
|
var IsBuiltinSpecial = map[string]bool{}
|
||||||
|
|
||||||
type noSuchModule struct{ spec string }
|
// NoSuchModule encodes an error where a module spec cannot be resolved.
|
||||||
|
type NoSuchModule struct{ spec string }
|
||||||
|
|
||||||
func (err noSuchModule) Error() string { return "no such module: " + err.spec }
|
// Error implements the error interface.
|
||||||
|
func (err NoSuchModule) Error() string { return "no such module: " + err.spec }
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Needed to avoid initialization loop
|
// Needed to avoid initialization loop
|
||||||
|
@ -316,7 +318,12 @@ func (op useOp) exec(fm *Frame) Exception {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for module specs relative to a package/workspace.
|
||||||
|
// See https://github.com/elves/elvish/issues/1421.
|
||||||
func use(fm *Frame, spec string, r diag.Ranger) (*Ns, error) {
|
func use(fm *Frame, spec string, r diag.Ranger) (*Ns, error) {
|
||||||
|
// Handle relative imports. Note that this deliberately does not support Windows backslash as a
|
||||||
|
// path separator because module specs are meant to be platform independent. If necessary, we
|
||||||
|
// translate a module spec to an appropriate path for the platform.
|
||||||
if strings.HasPrefix(spec, "./") || strings.HasPrefix(spec, "../") {
|
if strings.HasPrefix(spec, "./") || strings.HasPrefix(spec, "../") {
|
||||||
var dir string
|
var dir string
|
||||||
if fm.srcMeta.IsFile {
|
if fm.srcMeta.IsFile {
|
||||||
|
@ -331,6 +338,8 @@ func use(fm *Frame, spec string, r diag.Ranger) (*Ns, error) {
|
||||||
path := filepath.Clean(dir + "/" + spec)
|
path := filepath.Clean(dir + "/" + spec)
|
||||||
return useFromFile(fm, spec, path, r)
|
return useFromFile(fm, spec, path, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle imports of pre-defined modules like `builtin` and `str`.
|
||||||
if ns, ok := fm.Evaler.modules[spec]; ok {
|
if ns, ok := fm.Evaler.modules[spec]; ok {
|
||||||
return ns, nil
|
return ns, nil
|
||||||
}
|
}
|
||||||
|
@ -338,16 +347,21 @@ func use(fm *Frame, spec string, r diag.Ranger) (*Ns, error) {
|
||||||
return evalModule(fm, spec,
|
return evalModule(fm, spec,
|
||||||
parse.Source{Name: "[bundled " + spec + "]", Code: code}, r)
|
parse.Source{Name: "[bundled " + spec + "]", Code: code}, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle imports relative to the Elvish module search directories.
|
||||||
|
//
|
||||||
// TODO: For non-relative imports, use the spec (instead of the full path)
|
// TODO: For non-relative imports, use the spec (instead of the full path)
|
||||||
// as the module key instead to avoid searching every time.
|
// as the module key instead to avoid searching every time.
|
||||||
for _, dir := range fm.Evaler.LibDirs {
|
for _, dir := range fm.Evaler.LibDirs {
|
||||||
ns, err := useFromFile(fm, spec, filepath.Join(dir, spec), r)
|
ns, err := useFromFile(fm, spec, filepath.Join(dir, spec), r)
|
||||||
if _, isNoSuchModule := err.(noSuchModule); isNoSuchModule {
|
if _, isNoSuchModule := err.(NoSuchModule); isNoSuchModule {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return ns, err
|
return ns, err
|
||||||
}
|
}
|
||||||
return nil, noSuchModule{spec}
|
|
||||||
|
// Sadly, we couldn't resolve the module spec.
|
||||||
|
return nil, NoSuchModule{spec}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make access to fm.Evaler.modules concurrency-safe.
|
// TODO: Make access to fm.Evaler.modules concurrency-safe.
|
||||||
|
@ -360,7 +374,7 @@ func useFromFile(fm *Frame, spec, path string, r diag.Ranger) (*Ns, error) {
|
||||||
code, err := readFileUTF8(path + ".elv")
|
code, err := readFileUTF8(path + ".elv")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, noSuchModule{spec}
|
return nil, NoSuchModule{spec}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -370,7 +384,7 @@ func useFromFile(fm *Frame, spec, path string, r diag.Ranger) (*Ns, error) {
|
||||||
|
|
||||||
plug, err := pluginOpen(path + ".so")
|
plug, err := pluginOpen(path + ".so")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, noSuchModule{spec}
|
return nil, NoSuchModule{spec}
|
||||||
}
|
}
|
||||||
sym, err := plug.Lookup("Ns")
|
sym, err := plug.Lookup("Ns")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -378,7 +392,7 @@ func useFromFile(fm *Frame, spec, path string, r diag.Ranger) (*Ns, error) {
|
||||||
}
|
}
|
||||||
ns, ok := sym.(**Ns)
|
ns, ok := sym.(**Ns)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, noSuchModule{spec}
|
return nil, NoSuchModule{spec}
|
||||||
}
|
}
|
||||||
fm.Evaler.modules[path] = *ns
|
fm.Evaler.modules[path] = *ns
|
||||||
return *ns, nil
|
return *ns, nil
|
||||||
|
|
|
@ -349,13 +349,11 @@ func TestUse_SupportsCircularDependency(t *testing.T) {
|
||||||
|
|
||||||
func TestUse(t *testing.T) {
|
func TestUse(t *testing.T) {
|
||||||
libdir1 := InTempDir(t)
|
libdir1 := InTempDir(t)
|
||||||
|
|
||||||
ApplyDir(Dir{
|
ApplyDir(Dir{
|
||||||
"shadow.elv": "put lib1",
|
"shadow.elv": "put lib1",
|
||||||
})
|
})
|
||||||
|
|
||||||
libdir2 := InTempDir(t)
|
libdir2 := InTempDir(t)
|
||||||
|
|
||||||
ApplyDir(Dir{
|
ApplyDir(Dir{
|
||||||
"has-init.elv": "put has-init",
|
"has-init.elv": "put has-init",
|
||||||
"put-x.elv": "put $x",
|
"put-x.elv": "put $x",
|
||||||
|
@ -410,7 +408,10 @@ func TestUse(t *testing.T) {
|
||||||
// modules
|
// modules
|
||||||
That("x = foo; use put-x").Throws(AnyError),
|
That("x = foo; use put-x").Throws(AnyError),
|
||||||
|
|
||||||
// TODO: Test module namespace
|
// Using an unknown module spec fails.
|
||||||
|
That("use unknown").Throws(ErrorWithType(NoSuchModule{})),
|
||||||
|
That("use ./unknown").Throws(ErrorWithType(NoSuchModule{})),
|
||||||
|
That("use ../unknown").Throws(ErrorWithType(NoSuchModule{})),
|
||||||
|
|
||||||
// Nonexistent module
|
// Nonexistent module
|
||||||
That("use non-existent").Throws(ErrorWithMessage("no such module: non-existent")),
|
That("use non-existent").Throws(ErrorWithMessage("no such module: non-existent")),
|
||||||
|
|
|
@ -169,10 +169,11 @@ writes out `\*`.
|
||||||
A string is a (possibly empty) sequence of bytes.
|
A string is a (possibly empty) sequence of bytes.
|
||||||
|
|
||||||
[Single-quoted string literals](#single-quoted-string),
|
[Single-quoted string literals](#single-quoted-string),
|
||||||
[double-quoted string literals](#double-quoted-string)and [barewords](#bareword)
|
[double-quoted string literals](#double-quoted-string) and
|
||||||
all evaluate to string values. Unless otherwise noted, different syntaxes of
|
[barewords](#bareword) all evaluate to string values. Unless otherwise noted,
|
||||||
string literals are equivalent in the code. For instance, `xyz`, `'xyz'` and
|
different syntaxes of string literals are equivalent in the code. For instance,
|
||||||
`"xyz"` are different syntaxes for the same string with content `xyz`.
|
`xyz`, `'xyz'` and `"xyz"` are different syntaxes for the same string with
|
||||||
|
content `xyz`.
|
||||||
|
|
||||||
Strings that contain UTF-8 encoded text can be [indexed](#indexing) with a
|
Strings that contain UTF-8 encoded text can be [indexed](#indexing) with a
|
||||||
**byte index** where a codepoint starts, which results in the codepoint that
|
**byte index** where a codepoint starts, which results in the codepoint that
|
||||||
|
@ -2392,18 +2393,34 @@ itself or defined by the user.
|
||||||
|
|
||||||
### Importing modules with `use`
|
### Importing modules with `use`
|
||||||
|
|
||||||
Modules are imported using the `use` special command, which accepts a **module
|
Modules are imported using the `use` special command. It requires a **module
|
||||||
spec** and an optional alias:
|
spec** and allows a namespace alias:
|
||||||
|
|
||||||
```elvish
|
```elvish
|
||||||
use $spec $alias?
|
use $spec $alias?
|
||||||
```
|
```
|
||||||
|
|
||||||
Both the module spec and the alias must appear as a single string literal.
|
The module spec and the alias must both be a simple [string literal](#string).
|
||||||
|
[Compound strings](#compounding) such as `'a'/b` are not allowed.
|
||||||
|
|
||||||
The module spec specifies which module to import, and the alias, if given,
|
The module spec specifies which module to import. The alias, if given, specifies
|
||||||
specifies the namespace to import the module under. By default, the namespace is
|
the namespace to import the module under. By default, the namespace is derived
|
||||||
derived from the module spec, by taking the part after the last slash.
|
from the module spec by taking the part after the last slash.
|
||||||
|
|
||||||
|
Module specs fall into three categories that are resolved in the following
|
||||||
|
order:
|
||||||
|
|
||||||
|
1. **Relative**: These are [relative](#relative-imports) to the file containing
|
||||||
|
the `use` command.
|
||||||
|
|
||||||
|
1. **User defined**: These match a [user defined module](#user-defined-modules)
|
||||||
|
in a [module search directory](#module-search-directories).
|
||||||
|
|
||||||
|
1. **Pre-defined**: These match the name of a
|
||||||
|
[pre-defined module](#pre-defined-modules), such as `math` or `str`.
|
||||||
|
|
||||||
|
If a module spec doesn't match any of the above a "no such module"
|
||||||
|
[exception](#exception) is raised.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
|
@ -2525,10 +2542,10 @@ functions.
|
||||||
|
|
||||||
### Relative imports
|
### Relative imports
|
||||||
|
|
||||||
The module spec may being with `./` or `../`, which introduce **relative
|
The module spec may begin with `./` or `../` to introduce a **relative import**.
|
||||||
imports**. When `use` is invoked from a file, this will import the file relative
|
When `use` is invoked from a file this will import the file relative to the
|
||||||
to the location of the file. When `use` is invoked at the interactive prompt,
|
location of the file. When `use` is invoked from an interactive prompt, this
|
||||||
this will import the file relative to the current working directory.
|
will import the file relative to the current working directory.
|
||||||
|
|
||||||
### Scoping of imports
|
### Scoping of imports
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user