Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
# io.fs
`io.fs` defines small filesystem interfaces that can be shared by different
backends and virtual filesystems.
The module is modeled after Go's `io/fs` package, but follows V naming and type
conventions.
Current helpers:
- `valid_path(name string) bool`
- `read_file(filesystem FS, name string) ![]u8`
- `read_dir(filesystem FS, name string) ![]DirEntry`
- `stat(filesystem FS, name string) !FileInfo`
- `file_info_to_dir_entry(info FileInfo) DirEntry`
+163
View File
@@ -0,0 +1,163 @@
module fs
import io
import os
// FileInfo describes a file or directory entry.
pub interface FileInfo {
name() string
size() u64
mode() os.FileMode
mod_time() i64
is_dir() bool
}
// DirEntry describes a single directory entry.
pub interface DirEntry {
name() string
is_dir() bool
typ() os.FileType
info() !FileInfo
}
// File is the minimum interface required to read from a filesystem entry.
pub interface File {
io.Reader
stat() !FileInfo
mut:
close()
}
// FS opens slash-separated paths that pass `valid_path`.
pub interface FS {
open(name string) !File
}
// ReadDirFile can read directory entries directly from an opened file.
pub interface ReadDirFile {
File
mut:
read_dir(n int) ![]DirEntry
}
// ReadDirFS can read a directory without first opening it as a `File`.
pub interface ReadDirFS {
read_dir(name string) ![]DirEntry
}
// ReadFileFS can read a whole file without first opening it as a `File`.
pub interface ReadFileFS {
read_file(name string) ![]u8
}
// StatFS can stat a path without first opening it as a `File`.
pub interface StatFS {
stat(name string) !FileInfo
}
// GlobFS can expand glob patterns within a filesystem.
pub interface GlobFS {
glob(pattern string) ![]string
}
// SubFS can return a filesystem rooted at a subdirectory.
pub interface SubFS {
sub(dir string) !FS
}
struct FileInfoDirEntry {
source FileInfo
}
fn (entry FileInfoDirEntry) name() string {
return entry.source.name()
}
fn (entry FileInfoDirEntry) is_dir() bool {
return entry.source.is_dir()
}
fn (entry FileInfoDirEntry) typ() os.FileType {
return entry.source.mode().typ
}
fn (entry FileInfoDirEntry) info() !FileInfo {
return entry.source
}
fn sort_dir_entries(mut entries []DirEntry) {
entries.sort(a.name() < b.name())
}
// valid_path reports whether `name` is a valid slash-separated path for `FS.open`.
pub fn valid_path(name string) bool {
if name == '.' {
return true
}
if name.len == 0 {
return false
}
for elem in name.split('/') {
if elem.len == 0 || elem == '.' || elem == '..' {
return false
}
}
return true
}
// file_info_to_dir_entry wraps a `FileInfo` in a `DirEntry`.
pub fn file_info_to_dir_entry(info FileInfo) DirEntry {
return FileInfoDirEntry{
source: info
}
}
// read_file reads the named file from `filesystem`.
pub fn read_file(filesystem FS, name string) ![]u8 {
if filesystem is ReadFileFS {
reader := filesystem as ReadFileFS
return reader.read_file(name)
}
mut file := filesystem.open(name)!
defer {
file.close()
}
reader := file as io.Reader
return io.read_all(
reader: reader
read_to_end_of_stream: true
)
}
// read_dir reads and sorts the named directory from `filesystem`.
pub fn read_dir(filesystem FS, name string) ![]DirEntry {
if filesystem is ReadDirFS {
reader := filesystem as ReadDirFS
mut entries := reader.read_dir(name)!
sort_dir_entries(mut entries)
return entries
}
mut file := filesystem.open(name)!
defer {
file.close()
}
if mut file is ReadDirFile {
mut entries := file.read_dir(-1)!
sort_dir_entries(mut entries)
return entries
}
return error('fs: `${name}` does not support read_dir')
}
// stat returns file information for the named path from `filesystem`.
pub fn stat(filesystem FS, name string) !FileInfo {
if filesystem is StatFS {
stats := filesystem as StatFS
return stats.stat(name)
}
mut file := filesystem.open(name)!
defer {
file.close()
}
return file.stat()
}
+216
View File
@@ -0,0 +1,216 @@
module fs
import io
import os
struct MockInfo {
name_ string
size_ u64
mode_ os.FileMode
mod_time_ i64
}
fn (info MockInfo) name() string {
return info.name_
}
fn (info MockInfo) size() u64 {
return info.size_
}
fn (info MockInfo) mode() os.FileMode {
return info.mode_
}
fn (info MockInfo) mod_time() i64 {
return info.mod_time_
}
fn (info MockInfo) is_dir() bool {
return info.mode_.typ == .directory
}
struct MockFile {
data []u8
info_ MockInfo
mut:
offset int
entries []DirEntry
}
fn (mut file MockFile) read(mut buf []u8) !int {
if file.offset >= file.data.len {
return io.Eof{}
}
read := copy(mut buf, file.data[file.offset..])
file.offset += read
return read
}
fn (file MockFile) stat() !FileInfo {
return file.info_
}
fn (mut file MockFile) close() {}
fn (mut file MockFile) read_dir(n int) ![]DirEntry {
if n <= 0 || n >= file.entries.len {
return file.entries.clone()
}
return file.entries[..n].clone()
}
struct FallbackFS {}
fn (filesystem FallbackFS) open(name string) !File {
match name {
'notes.txt' {
return File(MockFile{
data: 'fallback'.bytes()
info_: MockInfo{
name_: 'notes.txt'
size_: u64('fallback'.len)
mode_: os.FileMode{
typ: .regular
}
mod_time_: 101
}
})
}
'dir' {
return File(MockFile{
info_: MockInfo{
name_: 'dir'
mode_: os.FileMode{
typ: .directory
}
}
entries: [
file_info_to_dir_entry(MockInfo{
name_: 'b.txt'
mode_: os.FileMode{
typ: .regular
}
}),
file_info_to_dir_entry(MockInfo{
name_: 'a.txt'
mode_: os.FileMode{
typ: .regular
}
}),
]
})
}
else {
return error('missing ${name}')
}
}
}
struct OptimizedFS {}
fn (filesystem OptimizedFS) open(name string) !File {
return error('unexpected open for ${name}')
}
fn (filesystem OptimizedFS) read_file(name string) ![]u8 {
return 'optimized'.bytes()
}
fn (filesystem OptimizedFS) read_dir(name string) ![]DirEntry {
return [
file_info_to_dir_entry(MockInfo{
name_: 'z.txt'
mode_: os.FileMode{
typ: .regular
}
}),
file_info_to_dir_entry(MockInfo{
name_: 'm.txt'
mode_: os.FileMode{
typ: .regular
}
}),
]
}
fn (filesystem OptimizedFS) stat(name string) !FileInfo {
return MockInfo{
name_: name
size_: 42
mode_: os.FileMode{
typ: .regular
}
mod_time_: 202
}
}
fn entry_names(entries []DirEntry) []string {
mut names := []string{cap: entries.len}
for entry in entries {
names << entry.name()
}
return names
}
fn test_valid_path() {
assert valid_path('.')
assert valid_path('alpha')
assert valid_path('alpha/beta')
assert valid_path(r'alpha\beta')
assert !valid_path('')
assert !valid_path('/alpha')
assert !valid_path('alpha/')
assert !valid_path('alpha//beta')
assert !valid_path('alpha/./beta')
assert !valid_path('alpha/../beta')
}
fn test_file_info_to_dir_entry() {
info := MockInfo{
name_: 'subdir'
size_: 12
mode_: os.FileMode{
typ: .directory
}
mod_time_: 303
}
entry := file_info_to_dir_entry(info)
assert entry.name() == 'subdir'
assert entry.is_dir()
assert entry.typ() == .directory
assert entry.info()!.mod_time() == 303
}
fn test_read_file_uses_open_fallback() {
data := read_file(FallbackFS{}, 'notes.txt')!
assert data == 'fallback'.bytes()
}
fn test_read_file_uses_read_file_fs_when_available() {
data := read_file(OptimizedFS{}, 'notes.txt')!
assert data == 'optimized'.bytes()
}
fn test_read_dir_uses_open_fallback_and_sorts() {
entries := read_dir(FallbackFS{}, 'dir')!
assert entry_names(entries) == ['a.txt', 'b.txt']
}
fn test_read_dir_uses_read_dir_fs_when_available() {
entries := read_dir(OptimizedFS{}, 'dir')!
assert entry_names(entries) == ['m.txt', 'z.txt']
}
fn test_stat_uses_open_fallback() {
info := stat(FallbackFS{}, 'notes.txt')!
assert info.name() == 'notes.txt'
assert info.size() == u64('fallback'.len)
}
fn test_stat_uses_stat_fs_when_available() {
info := stat(OptimizedFS{}, 'notes.txt')!
assert info.name() == 'notes.txt'
assert info.size() == 42
assert info.mod_time() == 202
}