Skip to content

Commit

Permalink
Merge pull request #81 from ripienaar/80
Browse files Browse the repository at this point in the history
add file.stat()
  • Loading branch information
d5 authored Feb 6, 2019
2 parents edfb765 + 69961de commit a9224a3
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
34 changes: 34 additions & 0 deletions compiler/stdlib/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions compiler/stdlib/os_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()})
},
},
},
}
}
72 changes: 72 additions & 0 deletions compiler/stdlib/os_test.go
Original file line number Diff line number Diff line change
@@ -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,
},
})
}
10 changes: 10 additions & 0 deletions docs/stdlib-os.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit a9224a3

Please sign in to comment.