Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/.vitepress/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function sidebar(): DefaultTheme.Sidebar {
{text: 'json', link: '/plugins/library/json'},
{text: 'strings', link: '/plugins/library/strings'},
{text: 'archiver', link: '/plugins/library/archiver'},
{text: 'fs', link: '/plugins/library/fs'},
]
},

Expand All @@ -94,4 +95,4 @@ function sidebar(): DefaultTheme.Sidebar {
]
},
]
}
}
3 changes: 2 additions & 1 deletion docs/.vitepress/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function sidebar(): DefaultTheme.Sidebar {
{text: 'json', link: '/zh-hans/plugins/library/json'},
{text: 'strings', link: '/zh-hans/plugins/library/strings'},
{text: 'archiver', link: '/zh-hans/plugins/library/archiver'},
{text: 'fs', link: '/zh-hans/plugins/library/fs'},
]
},

Expand All @@ -102,4 +103,4 @@ function sidebar(): DefaultTheme.Sidebar {
]
},
]
}
}
22 changes: 22 additions & 0 deletions docs/plugins/library/fs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# FS Library

`vfox` exposes basic path utilities to Lua plugins. Use `require("fs")` to access them. Every API works with both files and directories without exposing file-content reading or writing.

## Copy, remove, and move paths

```lua
local fs = require("fs")

fs.copy(source_path, destination_path)
fs.move(source_path, destination_path)
fs.remove(path)
```

## Create a symbolic link

```lua
local fs = require("fs")
fs.symlink(source_path, link_path)
```

Paths may be absolute or relative to the current `vfox` process. Every operation returns `true` on success and raises a Lua error on failure. Directory copies and removals are recursive. Like `mv`, `move` renames when its destination is a new path and moves the source under its original basename when the destination is an existing directory.
22 changes: 22 additions & 0 deletions docs/zh-hans/plugins/library/fs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# FS 标准库

`vfox` 为 Lua 插件提供基本的路径工具。使用 `require("fs")` 访问这些接口。所有接口都同时支持文件和目录,但不提供文件内容读写能力。

## 复制、删除和移动路径

```lua
local fs = require("fs")

fs.copy(source_path, destination_path)
fs.move(source_path, destination_path)
fs.remove(path)
```

## 创建符号链接

```lua
local fs = require("fs")
fs.symlink(source_path, link_path)
```

路径可以是绝对路径,也可以是相对于当前 `vfox` 进程的路径。所有操作成功时均返回 `true`,失败时抛出 Lua 错误。复制和删除目录时会递归处理其内容。与 `mv` 一样,`move` 的目标是新路径时会执行重命名;目标是已有目录时,会将源路径按原 basename 移入该目录。
61 changes: 0 additions & 61 deletions internal/plugin/luai/module/file/file.go

This file was deleted.

110 changes: 110 additions & 0 deletions internal/plugin/luai/module/fs/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2026 Han Li and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fs

import (
"os"
"path/filepath"

lua "github.com/yuin/gopher-lua"
)

type Operation struct {
rootPath string
}

func (f *Operation) path(path string) string {
return filepath.Join(f.rootPath, path)
}

func raiseOnError(L *lua.LState, err error) {
if err != nil {
L.RaiseError("%s", err.Error())
}
}

func returnTrue(L *lua.LState) int {
L.Push(lua.LTrue)
return 1
}

func (f *Operation) copy(L *lua.LState) int {
src := L.CheckString(1)
dest := L.CheckString(2)
info, err := os.Stat(f.path(src))
raiseOnError(L, err)
if info.IsDir() {
raiseOnError(L, os.CopyFS(f.path(dest), os.DirFS(f.path(src))))
return returnTrue(L)
}
content, err := os.ReadFile(f.path(src))
raiseOnError(L, err)
raiseOnError(L, os.WriteFile(f.path(dest), content, info.Mode().Perm()))
return returnTrue(L)
}

func (f *Operation) remove(L *lua.LState) int {
path := L.CheckString(1)
info, err := os.Lstat(f.path(path))
raiseOnError(L, err)
if info.IsDir() {
raiseOnError(L, os.RemoveAll(f.path(path)))
} else {
raiseOnError(L, os.Remove(f.path(path)))
}
return returnTrue(L)
}

func (f *Operation) move(L *lua.LState) int {
src := L.CheckString(1)
dest := L.CheckString(2)
srcPath := f.path(src)
destPath := f.path(dest)
if info, err := os.Stat(destPath); err == nil && info.IsDir() {
destPath = filepath.Join(destPath, filepath.Base(srcPath))
}
raiseOnError(L, os.Rename(srcPath, destPath))
return returnTrue(L)
}

func (f *Operation) symlink(L *lua.LState) int {
src := L.CheckString(1)
dest := L.CheckString(2)
raiseOnError(L, os.Symlink(f.path(src), f.path(dest)))
return returnTrue(L)
}

func (f *Operation) luaMap() map[string]lua.LGFunction {
return map[string]lua.LGFunction{
"copy": f.copy,
"remove": f.remove,
"move": f.move,
"symlink": f.symlink,
}
}

func (f *Operation) loader(L *lua.LState) int {
t := L.NewTable()
L.SetFuncs(t, f.luaMap())
L.Push(t)
return 1
}

func Preload(L *lua.LState, rootPath string) {
operation := &Operation{rootPath: rootPath}
L.PreloadModule("fs", operation.loader)
}
Loading