2022-12-15 04:00:54 +00:00
|
|
|
// filesystem interaction and functionality library
|
2023-12-26 03:08:29 +00:00
|
|
|
/*
|
|
|
|
The fs module provides filesystem functions to Hilbish. While Lua's standard
|
|
|
|
library has some I/O functions, they're missing a lot of the basics. The `fs`
|
|
|
|
library offers more functions and will work on any operating system Hilbish does.
|
|
|
|
#field pathSep The operating system's path separator.
|
|
|
|
*/
|
2021-03-20 22:49:15 +00:00
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
2022-07-13 19:38:07 +00:00
|
|
|
"fmt"
|
2022-04-23 03:35:26 +00:00
|
|
|
"path/filepath"
|
2021-10-17 16:56:45 +00:00
|
|
|
"strconv"
|
2021-03-20 22:49:15 +00:00
|
|
|
"os"
|
2021-03-31 17:46:49 +00:00
|
|
|
"strings"
|
2021-03-20 22:49:15 +00:00
|
|
|
|
2021-10-16 16:40:53 +00:00
|
|
|
"hilbish/util"
|
2022-04-04 10:40:02 +00:00
|
|
|
|
|
|
|
rt "github.com/arnodel/golua/runtime"
|
|
|
|
"github.com/arnodel/golua/lib/packagelib"
|
2024-04-28 01:03:54 +00:00
|
|
|
"github.com/arnodel/golua/lib/iolib"
|
2024-07-20 13:31:57 +00:00
|
|
|
"mvdan.cc/sh/v3/interp"
|
2021-03-20 22:49:15 +00:00
|
|
|
)
|
|
|
|
|
2023-07-16 00:03:55 +00:00
|
|
|
var watcherMetaKey = rt.StringValue("hshwatcher")
|
2024-07-20 13:31:57 +00:00
|
|
|
type fs struct{
|
|
|
|
runner *interp.Runner
|
|
|
|
Loader packagelib.Loader
|
2022-04-04 10:40:02 +00:00
|
|
|
}
|
|
|
|
|
2024-07-20 13:31:57 +00:00
|
|
|
func New(runner *interp.Runner) *fs {
|
|
|
|
f := &fs{
|
|
|
|
runner: runner,
|
|
|
|
}
|
|
|
|
f.Loader = packagelib.Loader{
|
|
|
|
Load: f.loaderFunc,
|
|
|
|
Name: "fs",
|
|
|
|
}
|
|
|
|
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
2023-07-16 00:03:55 +00:00
|
|
|
watcherMethods := rt.NewTable()
|
|
|
|
watcherFuncs := map[string]util.LuaExport{
|
|
|
|
"start": {watcherStart, 1, false},
|
|
|
|
"stop": {watcherStop, 1, false},
|
|
|
|
}
|
|
|
|
util.SetExports(rtm, watcherMethods, watcherFuncs)
|
|
|
|
|
|
|
|
watcherMeta := rt.NewTable()
|
|
|
|
watcherIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
//ti, _ := watcherArg(c, 0)
|
|
|
|
|
|
|
|
arg := c.Arg(1)
|
|
|
|
val := watcherMethods.Get(arg)
|
|
|
|
|
|
|
|
if val != rt.NilValue {
|
|
|
|
return c.PushingNext1(t.Runtime, val), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, val), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
watcherMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(watcherIndex, "__index", 2, false)))
|
|
|
|
rtm.SetRegistry(watcherMetaKey, rt.TableValue(watcherMeta))
|
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
exports := map[string]util.LuaExport{
|
2024-07-20 13:31:57 +00:00
|
|
|
"cd": util.LuaExport{f.fcd, 1, false},
|
|
|
|
"mkdir": util.LuaExport{f.fmkdir, 2, false},
|
|
|
|
"stat": util.LuaExport{f.fstat, 1, false},
|
|
|
|
"readdir": util.LuaExport{f.freaddir, 1, false},
|
|
|
|
"abs": util.LuaExport{f.fabs, 1, false},
|
|
|
|
"basename": util.LuaExport{f.fbasename, 1, false},
|
|
|
|
"dir": util.LuaExport{f.fdir, 1, false},
|
|
|
|
"glob": util.LuaExport{f.fglob, 1, false},
|
|
|
|
"join": util.LuaExport{f.fjoin, 0, true},
|
|
|
|
"pipe": util.LuaExport{f.fpipe, 0, false},
|
2023-07-16 00:03:55 +00:00
|
|
|
"watch": util.LuaExport{fwatch, 2, false},
|
2022-04-04 10:40:02 +00:00
|
|
|
}
|
|
|
|
mod := rt.NewTable()
|
|
|
|
util.SetExports(rtm, mod, exports)
|
2022-06-20 20:47:16 +00:00
|
|
|
mod.Set(rt.StringValue("pathSep"), rt.StringValue(string(os.PathSeparator)))
|
|
|
|
mod.Set(rt.StringValue("pathListSep"), rt.StringValue(string(os.PathListSeparator)))
|
2021-03-20 22:49:15 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return rt.TableValue(mod), nil
|
2021-03-20 22:49:15 +00:00
|
|
|
}
|
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// abs(path) -> string
|
|
|
|
// Returns an absolute version of the `path`.
|
|
|
|
// This can be used to resolve short paths like `..` to `/home/user`.
|
|
|
|
// #param path string
|
|
|
|
// #returns string
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-04-04 10:40:02 +00:00
|
|
|
path, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
path = util.ExpandHome(path)
|
2021-03-20 22:49:15 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
abspath, err := filepath.Abs(path)
|
2021-03-31 03:56:37 +00:00
|
|
|
if err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return nil, err
|
2021-03-31 03:56:37 +00:00
|
|
|
}
|
2021-03-20 22:49:15 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(abspath)), nil
|
2021-03-20 22:49:15 +00:00
|
|
|
}
|
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// basename(path) -> string
|
|
|
|
// Returns the "basename," or the last part of the provided `path`. If path is empty,
|
|
|
|
// `.` will be returned.
|
|
|
|
// #param path string Path to get the base name of.
|
|
|
|
// #returns string
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2023-12-26 03:08:29 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-05-01 04:49:59 +00:00
|
|
|
path, err := c.StringArg(0)
|
2022-04-04 10:40:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
|
|
|
|
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Base(path))), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// cd(dir)
|
|
|
|
// Changes Hilbish's directory to `dir`.
|
|
|
|
// #param dir string Path to change directory to.
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2023-12-26 03:08:29 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
path, err := c.StringArg(0)
|
2022-04-04 10:40:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-05-01 04:49:59 +00:00
|
|
|
path = util.ExpandHome(strings.TrimSpace(path))
|
2021-04-09 22:10:18 +00:00
|
|
|
|
2024-07-27 18:28:09 +00:00
|
|
|
abspath, _ := filepath.Abs(path)
|
2023-12-26 03:08:29 +00:00
|
|
|
err = os.Chdir(path)
|
2021-10-16 17:49:01 +00:00
|
|
|
if err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return nil, err
|
2021-06-12 13:31:42 +00:00
|
|
|
}
|
2024-07-27 18:28:09 +00:00
|
|
|
interp.Dir(abspath)(f.runner)
|
2021-04-09 22:10:18 +00:00
|
|
|
|
2022-04-04 10:40:02 +00:00
|
|
|
return c.Next(), err
|
2021-04-09 22:10:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// dir(path) -> string
|
|
|
|
// Returns the directory part of `path`. If a file path like
|
|
|
|
// `~/Documents/doc.txt` then this function will return `~/Documents`.
|
|
|
|
// #param path string Path to get the directory for.
|
|
|
|
// #returns string
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-04-04 10:40:02 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
path, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-04-09 22:10:18 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
return c.PushingNext(t.Runtime, rt.StringValue(filepath.Dir(path))), nil
|
2021-04-09 22:10:18 +00:00
|
|
|
}
|
2021-10-16 17:47:39 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// glob(pattern) -> matches (table)
|
|
|
|
// Match all files based on the provided `pattern`.
|
|
|
|
// For the syntax' refer to Go's filepath.Match function: https://pkg.go.dev/path/filepath#Match
|
|
|
|
// #param pattern string Pattern to compare files with.
|
|
|
|
// #returns table A list of file names/paths that match.
|
|
|
|
/*
|
|
|
|
#example
|
|
|
|
--[[
|
|
|
|
Within a folder that contains the following files:
|
|
|
|
a.txt
|
|
|
|
init.lua
|
|
|
|
code.lua
|
|
|
|
doc.pdf
|
|
|
|
]]--
|
|
|
|
local matches = fs.glob './*.lua'
|
|
|
|
print(matches)
|
|
|
|
-- -> {'init.lua', 'code.lua'}
|
|
|
|
#example
|
|
|
|
*/
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-04-04 10:40:02 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
pattern, err := c.StringArg(0)
|
2022-04-04 10:40:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-10-16 17:47:39 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
matches, err := filepath.Glob(pattern)
|
2021-10-16 17:47:39 +00:00
|
|
|
if err != nil {
|
2022-04-04 10:40:02 +00:00
|
|
|
return nil, err
|
2021-10-16 17:47:39 +00:00
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
|
|
|
|
luaMatches := rt.NewTable()
|
|
|
|
|
|
|
|
for i, match := range matches {
|
|
|
|
luaMatches.Set(rt.IntValue(int64(i + 1)), rt.StringValue(match))
|
2021-10-16 17:47:39 +00:00
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
|
|
|
|
return c.PushingNext(t.Runtime, rt.TableValue(luaMatches)), nil
|
|
|
|
}
|
2021-10-16 17:47:39 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// join(...path) -> string
|
|
|
|
// Takes any list of paths and joins them based on the operating system's path separator.
|
|
|
|
// #param path ...string Paths to join together
|
|
|
|
// #returns string The joined path.
|
|
|
|
/*
|
|
|
|
#example
|
|
|
|
-- This prints the directory for Hilbish's config!
|
|
|
|
print(fs.join(hilbish.userDir.config, 'hilbish'))
|
|
|
|
-- -> '/home/user/.config/hilbish' on Linux
|
|
|
|
#example
|
|
|
|
*/
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2023-12-26 03:08:29 +00:00
|
|
|
strs := make([]string, len(c.Etc()))
|
|
|
|
for i, v := range c.Etc() {
|
|
|
|
if v.Type() != rt.StringType {
|
|
|
|
// +2; go indexes of 0 and first arg from above
|
|
|
|
return nil, fmt.Errorf("bad argument #%d to run (expected string, got %s)", i + 1, v.TypeName())
|
|
|
|
}
|
|
|
|
strs[i] = v.AsString()
|
|
|
|
}
|
|
|
|
|
|
|
|
res := filepath.Join(strs...)
|
|
|
|
|
|
|
|
return c.PushingNext(t.Runtime, rt.StringValue(res)), nil
|
2021-10-16 17:47:39 +00:00
|
|
|
}
|
2022-04-23 03:35:26 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// mkdir(name, recursive)
|
|
|
|
// Creates a new directory with the provided `name`.
|
|
|
|
// With `recursive`, mkdir will create parent directories.
|
|
|
|
// #param name string Name of the directory
|
|
|
|
// #param recursive boolean Whether to create parent directories for the provided name
|
|
|
|
/*
|
|
|
|
#example
|
|
|
|
-- This will create the directory foo, then create the directory bar in the
|
|
|
|
-- foo directory. If recursive is false in this case, it will fail.
|
|
|
|
fs.mkdir('./foo/bar', true)
|
|
|
|
#example
|
|
|
|
*/
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2023-12-26 03:08:29 +00:00
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-04-23 03:35:26 +00:00
|
|
|
path, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
recursive, err := c.BoolArg(1)
|
2022-04-23 03:35:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
path = util.ExpandHome(strings.TrimSpace(path))
|
2022-04-23 03:35:26 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
if recursive {
|
|
|
|
err = os.MkdirAll(path, 0744)
|
|
|
|
} else {
|
|
|
|
err = os.Mkdir(path, 0744)
|
2022-06-20 20:47:16 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
return c.Next(), err
|
2022-06-20 20:47:16 +00:00
|
|
|
}
|
|
|
|
|
2024-04-28 01:03:54 +00:00
|
|
|
// fpipe() -> File, File
|
|
|
|
// Returns a pair of connected files, also known as a pipe.
|
|
|
|
// The type returned is a Lua file, same as returned from `io` functions.
|
|
|
|
// #returns File
|
|
|
|
// #returns File
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2024-04-28 01:03:54 +00:00
|
|
|
rf, wf, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rfLua := iolib.NewFile(rf, 0)
|
|
|
|
wfLua := iolib.NewFile(wf, 0)
|
|
|
|
|
|
|
|
return c.PushingNext(t.Runtime, rfLua.Value(t.Runtime), wfLua.Value(t.Runtime)), nil
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
// readdir(path) -> table[string]
|
|
|
|
// Returns a list of all files and directories in the provided path.
|
|
|
|
// #param dir string
|
|
|
|
// #returns table
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-06-20 20:47:16 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
dir, err := c.StringArg(0)
|
2022-06-20 20:47:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
dir = util.ExpandHome(dir)
|
|
|
|
names := rt.NewTable()
|
2022-06-20 20:47:16 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
dirEntries, err := os.ReadDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for i, entry := range dirEntries {
|
|
|
|
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
|
2022-06-20 20:47:16 +00:00
|
|
|
}
|
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
// stat(path) -> {}
|
|
|
|
// Returns the information about a given `path`.
|
|
|
|
// The returned table contains the following values:
|
|
|
|
// name (string) - Name of the path
|
|
|
|
// size (number) - Size of the path in bytes
|
|
|
|
// mode (string) - Unix permission mode in an octal format string (with leading 0)
|
|
|
|
// isDir (boolean) - If the path is a directory
|
|
|
|
// #param path string
|
|
|
|
// #returns table
|
|
|
|
/*
|
|
|
|
#example
|
|
|
|
local inspect = require 'inspect'
|
|
|
|
|
|
|
|
local stat = fs.stat '~'
|
|
|
|
print(inspect(stat))
|
|
|
|
--[[
|
|
|
|
Would print the following:
|
|
|
|
{
|
|
|
|
isDir = true,
|
|
|
|
mode = "0755",
|
|
|
|
name = "username",
|
|
|
|
size = 12288
|
|
|
|
}
|
|
|
|
]]--
|
|
|
|
#example
|
|
|
|
*/
|
2024-07-20 13:31:57 +00:00
|
|
|
func (f *fs) fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2022-06-20 20:47:16 +00:00
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
path, err := c.StringArg(0)
|
2022-06-20 20:47:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
path = util.ExpandHome(path)
|
2022-06-20 20:47:16 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
pathinfo, err := os.Stat(path)
|
2022-06-20 20:47:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-26 03:08:29 +00:00
|
|
|
statTbl := rt.NewTable()
|
|
|
|
statTbl.Set(rt.StringValue("name"), rt.StringValue(pathinfo.Name()))
|
|
|
|
statTbl.Set(rt.StringValue("size"), rt.IntValue(pathinfo.Size()))
|
|
|
|
statTbl.Set(rt.StringValue("mode"), rt.StringValue("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
|
|
|
|
statTbl.Set(rt.StringValue("isDir"), rt.BoolValue(pathinfo.IsDir()))
|
2022-06-20 20:47:16 +00:00
|
|
|
|
2023-12-26 03:08:29 +00:00
|
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil
|
2022-07-13 19:38:07 +00:00
|
|
|
}
|
2023-07-16 00:03:55 +00:00
|
|
|
|
2024-12-22 16:39:15 +00:00
|
|
|
// watch(path, callback)
|
|
|
|
// Watches a path for changes made to it. For example, to monitor
|
|
|
|
// new files created in a folder.
|
|
|
|
// The callback passed 2 string arguments, the `event` and the absolute `path`
|
|
|
|
// #param path string
|
|
|
|
// #param callback function
|
2023-07-16 00:03:55 +00:00
|
|
|
func fwatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dir, err := c.StringArg(0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
watcher, err := c.ClosureArg(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-12-22 16:46:40 +00:00
|
|
|
dw := newWatcher(dir, watcher, t.Runtime)
|
2023-07-16 00:03:55 +00:00
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(dw.ud)), nil
|
|
|
|
}
|