Minor fixup for path:temp-{dir file}.

* Remove `MatchesRegexp` from eval/vals since it is not part of the
  Elvish value protocol.

* Simplify implementation of value matching in `eval/evaltest`.

* Documentation wording tweaks.
This commit is contained in:
Qi Xiao 2021-04-25 22:57:51 +01:00
parent bc37099c92
commit 90acb4a242
6 changed files with 75 additions and 97 deletions

View File

@ -49,8 +49,8 @@ New features in the standard library:
- A new `file:` module contains utilities for manipulating files.
- Commands for creating, what are usually meant to be temporary, unique
directories, path:temp-dir, and regular files, path:temp-file
- Commands for creating temporary files and directories, `path:temp-file`
and `path:temp-dir`
([#1255](https://b.elv.sh/1255)).
New features in the interactive editor:

View File

@ -43,10 +43,6 @@ type Result struct {
Exception error
}
// MatchingRegexp is used in a `Puts()` call when the value being matched should be a string
// matching a regexp rather than a literal string.
type MatchingRegexp struct{ Pattern string }
// The following functions and methods are used to build Test structs. They are
// supposed to read like English, so a test that "put x" should put "x" reads:
//
@ -212,45 +208,37 @@ func capturePort() (*eval.Port, func() ([]interface{}, []byte)) {
}
func matchOut(want, got []interface{}) bool {
if len(got) == 0 && len(want) == 0 {
return true
}
if len(got) != len(want) {
return false
}
for i := range got {
switch g := got[i].(type) {
case float64:
// Special-case float64 to correctly handle NaN and support
// approximate comparison.
switch w := want[i].(type) {
case float64:
if !matchFloat64(g, w, 0) {
return false
}
case Approximately:
if !matchFloat64(g, w.F, ApproximatelyThreshold) {
return false
}
default:
return false
}
default:
switch w := want[i].(type) {
case MatchingRegexp:
if !vals.MatchesRegexp(got[i], w.Pattern) {
return false
}
default:
if !vals.Equal(got[i], want[i]) {
return false
}
}
if !match(got[i], want[i]) {
return false
}
}
return true
}
func match(got, want interface{}) bool {
switch got := got.(type) {
case float64:
// Special-case float64 to correctly handle NaN and support
// approximate comparison.
switch want := want.(type) {
case float64:
return matchFloat64(got, want, 0)
case Approximately:
return matchFloat64(got, want.F, ApproximatelyThreshold)
}
case string:
switch want := want.(type) {
case MatchingRegexp:
return matchRegexp(want.Pattern, got)
}
}
return vals.Equal(got, want)
}
func reprs(values []interface{}) []string {
s := make([]string, len(values))
for i, v := range values {

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"reflect"
"regexp"
"src.elv.sh/pkg/eval"
)
@ -27,6 +28,19 @@ func matchFloat64(a, b, threshold float64) bool {
return math.Abs(a-b) <= threshold
}
// MatchingRegexp can be passed to TestCase.Puts to match a any string that
// matches a regexp pattern. If the pattern is not a valid regexp, the test will
// panic.
type MatchingRegexp struct{ Pattern string }
func matchRegexp(p, s string) bool {
matched, err := regexp.MatchString(p, s)
if err != nil {
panic(err)
}
return matched
}
type errorMatcher interface{ matchError(error) bool }
// AnyError is an error that can be passed to TestCase.Throws to match any

View File

@ -184,13 +184,19 @@ func isRegular(path string) bool {
//elvdoc:fn temp-dir
//
// ```elvish
// temp-dir &dir=$dir $pattern?
// temp-dir &dir='' $pattern?
// ```
//
// Create a unique directory and output its name. The &dir option determines where the directory
// will be created, and its default value is appropriate for your system. The `$pattern` value is
// optional. If omitted it defaults to `elvish-*`. The last star in the pattern is replaced by a
// random string. It is your responsibility to remove the (presumably) temporary directory.
// Creates a new directory and outputs its name.
//
// The &dir option determines where the directory will be created; if it is an
// empty string (the default), a system-dependent directory suitable for storing
// temporary files will be used. The `$pattern` argument determins the name of
// the directory, where the last star will be replaced by a random string; it
// defaults to `elvish-*`.
//
// It is the caller's responsibility to remove the directory if it is intended
// to be temporary.
//
// ```elvish-transcript
// ~> path:temp-dir
@ -228,17 +234,21 @@ func tempDir(opts mktempOpt, args ...string) (string, error) {
//elvdoc:fn temp-file
//
// ```elvish
// temp-file [&dir=$dir] [$pattern]
// temp-file &dir='' $pattern?
// ```
//
// Create a unique file and output a [file](language.html#file) object opened for reading and
// writing. The &dir option determines where the directory will be created, and its default value is
// appropriate for your system. The `$pattern` value is optional. If omitted it defaults to
// `elvish-*`. The last star in the pattern is replaced by a random string. It is your
// responsibility to remove the (presumably) temporary file.
// Creates a new file and outputs a [file](language.html#file) object opened
// for reading and writing.
//
// You can use [`fclose`](builtin.html#fclose) to close the file. You can use `$f[name]` to extract
// the name of the file so it can be used as an argument for another command; e.g., `rm`.
// The &dir option determines where the file will be created; if it is an
// empty string (the default), a system-dependent directory suitable for storing
// temporary files will be used. The `$pattern` argument determins the name of
// the file, where the last star will be replaced by a random string; it
// defaults to `elvish-*`.
//
// It is the caller's responsibility to close the file with
// [`file:close`](file.html#close). The caller should also remove the file if it
// is intended to be temporary (with `rm $f[name]`).
//
// ```elvish-transcript
// ~> f = path:temp-file

View File

@ -35,36 +35,16 @@ func (err noSuchKeyError) Error() string {
return "no such key: " + Repr(err.key, NoPretty)
}
// TODO: Replace this with a a generalized introspection mechanism based on PseudoStructMap for
// *os.File objects so that commands like `keys` also work on those objects.
var errInvalidOsFileIndex = errors.New("invalid index for a File object")
func indexOsFile(f *os.File, k interface{}) (interface{}, error) {
switch k := k.(type) {
case string:
switch {
case k == "fd":
return int(f.Fd()), nil
case k == "name":
return f.Name(), nil
default:
return nil, errInvalidOsFileIndex
}
default:
return nil, errInvalidOsFileIndex
}
}
// Index indexes a value with the given key. It is implemented for the builtin
// type string, the List type, StructMap types, and types satisfying the
// ErrIndexer or Indexer interface (the Map type satisfies Indexer). For other
// types, it returns a nil value and a non-nil error.
// type string, *os.File, List, StructMap and PseudoStructMap types, and types
// satisfying the ErrIndexer or Indexer interface (the Map type satisfies
// Indexer). For other types, it returns a nil value and a non-nil error.
func Index(a, k interface{}) (interface{}, error) {
switch a := a.(type) {
case string:
return indexString(a, k)
case *os.File:
return indexOsFile(a, k)
return indexFile(a, k)
case ErrIndexer:
return a.Index(k)
case Indexer:
@ -84,6 +64,16 @@ func Index(a, k interface{}) (interface{}, error) {
}
}
func indexFile(f *os.File, k interface{}) (interface{}, error) {
switch k {
case "fd":
return int(f.Fd()), nil
case "name":
return f.Name(), nil
}
return nil, NoSuchKey(k)
}
func indexStructMap(a StructMap, k interface{}) (interface{}, error) {
fieldName, ok := k.(string)
if !ok || fieldName == "" {

View File

@ -1,24 +0,0 @@
package vals
import (
"regexp"
)
// MatchesRegexp returns whether The first value matches the second value interpreted as a regexp.
// Both bytes must be a string.
func MatchesRegexp(x, y interface{}) bool {
val, ok := x.(string)
if !ok {
return false
}
pat, ok := y.(string)
if !ok {
return false
}
matched, err := regexp.MatchString(pat, val)
if err != nil {
return false
}
return matched
}