hackpadfs
Go语言的文件系统、接口和辅助工具。
想要开始使用吗?查看下面的指南。
文件系统
hackpadfs
包含多个已实现的文件系统,可用于各种应用场景:
os.FS
- 熟悉的os
包。使用新的接口设计实现了标准库中所有熟悉的行为。mem.FS
- 内存文件系统。indexeddb.FS
- WebAssembly兼容的文件系统,底层使用IndexedDB。tar.ReaderFS
- 用于内存和时间受限程序的流式tar文件系统。mount.FS
- 可组合的文件系统。能够将文件系统相互叠加。keyvalue.FS
- 通用键值文件系统。非常适合快速编写自己的文件系统。mem.FS
和indexeddb.FS
就是基于它构建的。
寻找自定义文件系统的灵感?示例包括:
这些文件系统都经过了严格的hackpadfs/fstest
套件测试,以确保其正确性和与标准库os
包行为的一致性。如果您正在实现自己的文件系统,我们建议您也在测试中使用fstest
。
接口
基于Go 1.16中io/fs
包奠定的基础,hackpadfs
定义了许多基本的文件系统接口。
以下是hackpadfs
定义的一些接口:
type FS interface {
Open(name string) (File, error)
}
type CreateFS interface {
FS
Create(name string) (File, error)
}
type MkdirFS interface {
FS
Mkdir(name string, perm FileMode) error
}
type StatFS interface {
FS
Stat(name string) (FileInfo, error)
}
完整文档请参阅参考文档。
使用这些接口,您可以创建和组合自己的文件系统。这些接口很小,使自定义文件系统只需实现必要的部分。
入门指南
使用hackpadfs
有多种方法。跳转到以下指南之一:
快速开始
如果您使用过标准库的os
包,那么您可能已经了解os.FS
的大部分工作原理!
在这个例子中,我们创建一个新的os.FS
并打印/tmp/hello.txt
的内容。
import (
"fmt"
"github.com/hack-pad/hackpadfs"
"github.com/hack-pad/hackpadfs/os"
)
filePath := "tmp/hello.txt"
fs, _ := os.NewFS()
file, _ := fs.Open(filePath)
defer file.Close()
buffer := make([]byte, 1024)
n, _ := file.Read(buffer)
fmt.Println("hello.txt的内容:", string(buffer[:n]))
相对文件路径
Go的io/fs
规范不允许使用相对路径,因此我们必须使用绝对路径(不带第一个/
)。
要模拟相对路径,请使用SubFS
接口创建一个新的"根"文件系统,如下所示:
import (
goOS "os"
"github.com/hack-pad/hackpadfs/os"
)
fs := os.NewFS()
workingDirectory, _ := goOS.Getwd() // 获取当前工作目录
workingDirectory, _ = fs.FromOSPath(workingDirectory) // 转换为FS路径
workingDirFS, _ := fs.Sub(workingDirectory) // 在当前工作目录根目录下运行所有文件系统操作
路径分隔符(斜杠)
遵循io/fs
规范:
传递给open的路径名是UTF-8编码的、非根目录的、以斜杠分隔的路径元素序列,如"x/y/z"。路径名不得包含"."、".."或空字符串元素,除非根目录被命名为"."的特殊情况。路径不能以斜杠开始或结束:"/x"和"x/"是无效的。
请注意,即使在Windows上,路径也是以斜杠分隔的。包含其他字符(如反斜杠和冒号)的路径被接受为有效路径,但FS实现绝不能将这些字符解释为路径元素分隔符。
在hackpadfs
中,这意味着:
- 所有路径分隔符都是"正斜杠"或
/
。即使在Windows上,斜杠也会在底层进行转换。 - 以
/
开始或结束的路径是无效的 - 要引用根文件路径,请使用
.
- 所有路径都是非根目录的(不是相对路径)
- 当包含相对路径元素时(例如
mypath/.././myotherpath
),路径不一定会被清理。某些文件系统实现会解析这些路径,但这不是保证的。文件系统应通过io/fs.ValidPath()
拒绝这些路径。
使用接口
使用接口是个好主意 -- 文件系统也不例外。可交换的文件系统能够实现强大的组合。
然而,直接处理接口值可能会比较困难。幸运的是,我们有几个可用的辅助工具。
在下面的例子中,我们使用hackpadfs
的包级函数来进行接口检查并为我们调用适当的方法:
import (
"github.com/hack-pad/hackpadfs"
"github.com/hack-pad/hackpadfs/mem"
)
func main() {
fs, _ := mem.NewFS()
helloWorld(fs)
}
// helloWorld使用通用的hackpadfs.FS创建一个包含"world"的文件。
// 如果'fs'不支持创建或写入文件,则返回错误。
func helloWorld(fs hackpadfs.FS) error {
// hackpadfs.Create(...)检查'fs'是否实现Create,还有几个备用接口。
file, err := hackpadfs.Create(fs, "hello.txt")
if err != nil {
return err
}
// 这里同样使用hackpadfs.WriteFile(...)。如果文件不支持写入,则返回"未实现"错误。
_, err = hackpadfs.WriteFile(file, []byte("world"))
return err
}
注意对hackpadfs.Create(...)
和hackpadfs.WriteFile(...)
的包级函数调用。
由于我们使用的接口不知道这些方法,我们使用这些辅助函数来检测支持并在一次调用中运行这些操作。
现在,无论何时我们需要使用完全不同的文件系统重用helloWorld()
,它都已经准备就绪!