elvish/pkg/mods/file/file.go

142 lines
3.1 KiB
Go

package file
import (
"math/big"
"os"
"strconv"
"src.elv.sh/pkg/eval"
"src.elv.sh/pkg/eval/errs"
"src.elv.sh/pkg/eval/vals"
)
var Ns = eval.BuildNsNamed("file").
AddGoFns(map[string]any{
"close": close,
"open": open,
"pipe": pipe,
"truncate": truncate,
}).Ns()
//elvdoc:fn open
//
// ```elvish
// file:open $filename
// ```
//
// Opens a file. Currently, `open` only supports opening a file for reading.
// File must be closed with `close` explicitly. Example:
//
// ```elvish-transcript
// ~> cat a.txt
// This is
// a file.
// ~> use file
// ~> var f = (file:open a.txt)
// ~> cat < $f
// This is
// a file.
// ~> close $f
// ```
//
// @cf file:close
func open(name string) (vals.File, error) {
return os.Open(name)
}
//elvdoc:fn close
//
// ```elvish
// file:close $file
// ```
//
// Closes a file opened with `open`.
//
// @cf file:open
func close(f vals.File) error {
return f.Close()
}
//elvdoc:fn pipe
//
// ```elvish
// file:pipe
// ```
//
// Create a new pipe that can be used in redirections. A pipe contains a read-end and write-end.
// Each pipe object is a [pseudo-map](language.html#pseudo-map) with fields `r` (the read-end [file
// object](./language.html#file)) and `w` (the write-end).
//
// When redirecting command input from a pipe with `<`, the read-end is used. When redirecting
// command output to a pipe with `>`, the write-end is used. Redirecting both input and output with
// `<>` to a pipe is not supported.
//
// Pipes have an OS-dependent buffer, so writing to a pipe without an active reader
// does not necessarily block. Pipes **must** be explicitly closed with `file:close`.
//
// Putting values into pipes will cause those values to be discarded.
//
// Examples (assuming the pipe has a large enough buffer):
//
// ```elvish-transcript
// ~> var p = (file:pipe)
// ~> echo 'lorem ipsum' > $p
// ~> head -n1 < $p
// lorem ipsum
// ~> put 'lorem ipsum' > $p
// ~> file:close $p[w] # close the write-end
// ~> head -n1 < $p # blocks unless the write-end is closed
// ~> file:close $p[r] # close the read-end
// ```
//
// @cf file:close
func pipe() (vals.Pipe, error) {
r, w, err := os.Pipe()
return vals.NewPipe(r, w), err
}
//elvdoc:fn truncate
//
// ```elvish
// file:truncate $filename $size
// ```
//
// changes the size of the named file. If the file is a symbolic link, it
// changes the size of the link's target. The size must be an integer between 0
// and 2^64-1.
func truncate(name string, rawSize vals.Num) error {
var size int64
switch rawSize := rawSize.(type) {
case int:
size = int64(rawSize)
case *big.Int:
if rawSize.IsInt64() {
size = rawSize.Int64()
} else {
return truncateSizeOutOfRange(rawSize.String())
}
default:
return errs.BadValue{
What: "size argument to file:truncate",
Valid: "integer", Actual: "non-integer",
}
}
if size < 0 {
return truncateSizeOutOfRange(strconv.FormatInt(size, 10))
}
return os.Truncate(name, size)
}
func truncateSizeOutOfRange(size string) error {
return errs.OutOfRange{
What: "size argument to file:truncate",
ValidLow: "0",
ValidHigh: "2^64-1",
Actual: size,
}
}