Add 'environment' package

This commit adds a new environment package that manages all calls to the
underlying operating system.
This commit is contained in:
Tom Wiesing 2022-09-18 14:24:22 +02:00
parent 822c70cd69
commit f19619ef9f
No known key found for this signature in database
60 changed files with 539 additions and 308 deletions

View file

@ -0,0 +1,58 @@
package environment
import (
"io"
"io/fs"
"net"
"time"
"github.com/tkw1536/goprogram/stream"
)
// Environment represents an environment that a program can run it.
// It mostly mimics the interfaces of the [os] package.
type Environment interface {
isEnv()
GetEnv(name string) string
Stat(path string) (fs.FileInfo, error)
Lstat(path string) (fs.FileInfo, error)
Readlink(path string) (string, error)
Symlink(oldname, newname string) error
ReadDir(name string) ([]fs.DirEntry, error)
Open(path string) (fs.File, error)
Chtimes(name string, atime time.Time, mtime time.Time) error
SameFile(f1, f2 fs.FileInfo) bool
Create(path string, mode fs.FileMode) (WritableFile, error)
Mkdir(path string, mode fs.FileMode) error
MkdirAll(path string, mode fs.FileMode) error
Remove(path string) error
RemoveAll(path string) error
WalkDir(root string, fn fs.WalkDirFunc) error
Abs(path string) (string, error)
Listen(network, address string) (net.Listener, error)
Dial(network, address string) (net.Conn, error)
Executable() (string, error)
Exec(io stream.IOStream, workdir string, exe string, argv ...string) int
LookPathAbs(name string) (string, error)
}
type WritableFile interface {
fs.File
io.Writer
}
func init() {
var _ Environment = Native{}
}

View file

@ -0,0 +1,69 @@
package environment
import (
"bytes"
"io"
"io/fs"
"os"
"github.com/tkw1536/goprogram/stream"
)
// ExecCommandError is returned by Exec when a command could not be executed.
// This typically hints that the executable cannot be found, but may have other causes.
const ExecCommandError = 127
// DefaultFilePerm is the default mode to use for files
const DefaultFilePerm fs.FileMode = 0666
// DefaultDirPerm is the default mode to use for directories
const DefaultDirPerm fs.FileMode = os.ModeDir & fs.ModePerm
// IsExist checks if the provided error represents a 'does not exist' errror
func IsExist(err error) bool {
return os.IsExist(err)
}
// IsNotExist checks if the provided error represents a 'does exist' error
func IsNotExist(err error) bool {
return os.IsNotExist(err)
}
// WriteFile is like [os.WriteFile].
func WriteFile(env Environment, path string, data []byte, mode fs.FileMode) error {
handle, err := env.Create(path, mode)
if err != nil {
return err
}
defer handle.Close()
if _, err := handle.Write(data); err != nil {
return err
}
return nil
}
// ReadFile is like [os.ReadFile]
func ReadFile(env Environment, path string) ([]byte, error) {
// open the file!
file, err := env.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// copy everything into a buffer!
var buffer bytes.Buffer
if _, err := io.Copy(&buffer, file); err != nil {
return nil, err
}
// return the buffer contents!
return buffer.Bytes(), nil
}
// MustExec is like Exec, except that it returns true if the command exited successfully, and else false.
func MustExec(env Environment, io stream.IOStream, workdir string, exe string, argv ...string) bool {
return env.Exec(io, workdir, exe, argv...) == 0
}

View file

@ -0,0 +1,44 @@
package environment
import (
"os/exec"
"github.com/tkw1536/goprogram/stream"
)
// Exec executes a system command with the specified input/output streams, working directory, and arguments.
//
// If the command executes, it's exit code will be returned.
// If the command can not be executed, returns [ExecCommandError].
func (Native) Exec(io stream.IOStream, workdir string, exe string, argv ...string) int {
// setup the command
cmd := exec.Command(exe, argv...)
cmd.Dir = workdir
cmd.Stdin = io.Stdin
cmd.Stdout = io.Stdout
cmd.Stderr = io.Stderr
// run it
err := cmd.Run()
// non-zero exit
if err, ok := err.(*exec.ExitError); ok {
return err.ExitCode()
}
// unknown error
if err != nil {
return ExecCommandError
}
// everything is fine!
return 0
}
func (n Native) LookPathAbs(file string) (string, error) {
path, err := exec.LookPath(file)
if err != nil {
return "", err
}
return n.Abs(path)
}

View file

@ -0,0 +1,80 @@
package environment
import (
"io/fs"
"os"
"path/filepath"
"time"
)
type Native struct{}
func (Native) isEnv() {}
func (Native) GetEnv(name string) string {
return os.Getenv(name)
}
func (Native) Stat(path string) (fs.FileInfo, error) {
return os.Stat(path)
}
func (Native) Lstat(path string) (fs.FileInfo, error) {
return os.Lstat(path)
}
func (Native) Readlink(path string) (string, error) {
return os.Readlink(path)
}
func (Native) Symlink(oldname, newname string) error {
return os.Symlink(oldname, newname)
}
func (Native) ReadDir(name string) ([]fs.DirEntry, error) {
return os.ReadDir(name)
}
func (Native) SameFile(f1, f2 fs.FileInfo) bool {
return os.SameFile(f1, f2)
}
func (Native) WalkDir(path string, f fs.WalkDirFunc) error {
return filepath.WalkDir(path, f)
}
func (Native) Executable() (string, error) {
return os.Executable() // TODO: not sure this works with the remote concepts
}
func (Native) Open(path string) (fs.File, error) {
return os.Open(path)
}
func (Native) Create(path string, mode fs.FileMode) (WritableFile, error) {
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
}
func (Native) Chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
func (Native) Mkdir(path string, mode fs.FileMode) error {
return os.Mkdir(path, mode)
}
func (Native) MkdirAll(path string, mode fs.FileMode) error {
return os.MkdirAll(path, mode)
}
func (Native) Remove(path string) error {
return os.Remove(path)
}
func (Native) RemoveAll(path string) error {
return os.RemoveAll(path)
}
func (Native) Abs(path string) (string, error) {
return filepath.Abs(path)
}

View file

@ -0,0 +1,11 @@
package environment
import "net"
func (Native) Listen(network, address string) (net.Listener, error) {
return net.Listen(network, address)
}
func (Native) Dial(network, address string) (net.Conn, error) {
return net.Dial(network, address)
}