Merge pull request #81 from ripienaar/80

add file.stat()
This commit is contained in:
Daniel 2019-02-06 10:25:52 -08:00 committed by GitHub
commit a9224a3593
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 0 deletions

View file

@ -77,6 +77,40 @@ var osModule = map[string]objects.Object{
"start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
"exec_look_path": FuncASRSE(exec.LookPath), // exec_look_path(file) => string/error
"exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
"stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error
}
func osStat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
fname, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
stat, err := os.Stat(fname)
if err != nil {
return wrapError(err), nil
}
fstat := &objects.ImmutableMap{
Value: map[string]objects.Object{
"name": &objects.String{Value: stat.Name()},
"mtime": &objects.Time{Value: stat.ModTime()},
"size": &objects.Int{Value: stat.Size()},
"mode": &objects.Int{Value: int64(stat.Mode())},
},
}
if stat.IsDir() {
fstat.Value["directory"] = objects.TrueValue
} else {
fstat.Value["directory"] = objects.FalseValue
}
return fstat, nil
}
func osCreate(args ...objects.Object) (objects.Object, error) {

View file

@ -66,6 +66,16 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.Int{Value: res}, nil
},
},
// stat() => imap(fileinfo)/error
"stat": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
return osStat(&objects.String{Value: file.Name()})
},
},
},
}
}

View file

@ -0,0 +1,72 @@
package stdlib_test
import (
"io/ioutil"
"os"
"testing"
"github.com/d5/tengo/objects"
)
func TestFileStatArgs(t *testing.T) {
module(t, "os").call("stat").expectError()
}
func TestFileStatFile(t *testing.T) {
content := []byte("the quick brown fox jumps over the lazy dog")
tf, err := ioutil.TempFile("", "test")
if err != nil {
t.Logf("could not open tempfile: %s", err)
return
}
defer os.Remove(tf.Name())
_, err = tf.Write(content)
if err != nil {
t.Logf("could not write temp content: %s", err)
return
}
tf.Close()
stat, err := os.Stat(tf.Name())
if err != nil {
t.Logf("could not get tmp file stat: %s", err)
return
}
module(t, "os").call("stat", tf.Name()).expect(&objects.ImmutableMap{
Value: map[string]objects.Object{
"name": &objects.String{Value: stat.Name()},
"mtime": &objects.Time{Value: stat.ModTime()},
"size": &objects.Int{Value: stat.Size()},
"mode": &objects.Int{Value: int64(stat.Mode())},
"directory": objects.FalseValue,
},
})
}
func TestFileStatDir(t *testing.T) {
td, err := ioutil.TempDir("", "test")
if err != nil {
t.Logf("could not open tempdir: %s", err)
return
}
defer os.RemoveAll(td)
stat, err := os.Stat(td)
if err != nil {
t.Logf("could not get tmp dir stat: %s", err)
return
}
module(t, "os").call("stat", td).expect(&objects.ImmutableMap{
Value: map[string]objects.Object{
"name": &objects.String{Value: stat.Name()},
"mtime": &objects.Time{Value: stat.ModTime()},
"size": &objects.Int{Value: stat.Size()},
"mode": &objects.Int{Value: int64(stat.Mode())},
"directory": objects.TrueValue,
},
})
}

View file

@ -68,6 +68,7 @@ os := import("os")
- `remove_all(name string) => error `: removes path and any children it contains.
- `rename(oldpath string, newpath string) => error `: renames (moves) oldpath to newpath.
- `setenv(key string, value string) => error `: sets the value of the environment variable named by the key.
- `stat(filename string) => FileInfo/error`: returns a file info structure describing the file
- `symlink(oldname string newname string) => error `: creates newname as a symbolic link to oldname.
- `temp_dir() => string `: returns the default directory to use for temporary files.
- `truncate(name string, size int) => error `: changes the size of the named file.
@ -98,6 +99,7 @@ file.close()
- `write(bytes) => int/error`: writes len(b) bytes to the File.
- `write_string(string) => int/error`: is like 'write', but writes the contents of string s rather than a slice of bytes.
- `read(bytes) => int/error`: reads up to len(b) bytes from the File.
- `stat() => FileInfo/error`: returns a file info structure describing the file
- `chmod(mode int) => error`: changes the mode of the file to mode.
- `seek(offset int, whence int) => int/error`: sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end.
@ -131,6 +133,14 @@ cmd := exec.command("echo", ["foo", "bar"])
output := cmd.output()
```
## FileInfo
- `name`: name of the file the info describes
- `mtime`: time the file was last modified
- `size`: file size in bytes
- `mode`: file permissions as in int, comparable to octal permissions
- `directory`: boolean indicating if the file is a directory
## Command
- `combined_output() => bytes/error`: runs the command and returns its combined standard output and standard error.