diff --git a/errutils/errutils.go b/errutils/errutils.go index e57d1e8..4571abc 100644 --- a/errutils/errutils.go +++ b/errutils/errutils.go @@ -5,6 +5,24 @@ import ( "fmt" ) +// FmtError formats an error message using a format string and optional values, +// and returns it as an error object. +// +// It takes a format string f and variadic arguments v, and uses them to construct +// the error message by calling fmt.Sprintf function. The resulting error message +// is then wrapped into an error object using errors.New. +// +// Example: +// +// err := FmtError("Invalid input: %s", userInput) +// if err != nil { +// log.Println(err) +// } +// +// @param f The format string specifying the error message. +// @param v Optional values to be inserted into the format string. +// +// @returns An error object containing the formatted error message. func FmtError(f string, v ...any) error { return errors.New(fmt.Sprintf(f, v...)) } diff --git a/errutils/errutils_test.go b/errutils/errutils_test.go new file mode 100644 index 0000000..bb95833 --- /dev/null +++ b/errutils/errutils_test.go @@ -0,0 +1,25 @@ +package errutils + +import ( + "errors" + "fmt" + "testing" +) + +func TestFmtError(t *testing.T) { + t.Run("Testing execution of error utils", func(t *testing.T) { + want := errors.New("testing error utils") + got := FmtError("testing error utils") + if got.Error() != want.Error() { + t.Errorf("invalid error string generated") + } + }) + + t.Run("Testing execution of error utils with formatting", func(t *testing.T) { + want := errors.New(fmt.Sprintf("expecting errors %d", 0)) + got := FmtError("expecting errors %d", 0) + if got.Error() != want.Error() { + t.Errorf("invalid error string generated") + } + }) +} diff --git a/fnutils/fn.go b/fnutils/fn.go index 4c1845c..af65be7 100644 --- a/fnutils/fn.go +++ b/fnutils/fn.go @@ -4,17 +4,76 @@ import ( "time" ) +// ExecuteAfterSecs executes the given function after the specified timeout duration, +// expressed in seconds. +// +// It converts the timeout value from seconds to a duration in seconds and then calls +// the ExecuteAfter function, passing the converted duration and the provided function. +// +// Example: +// +// ExecuteAfterSecs(func() { +// fmt.Println("Hello, World!") +// }, 10) +// +// @param fn The function to be executed. +// +// @param timeout The timeout duration in seconds. func ExecuteAfterSecs(fn func(), timeout int) { ExecuteAfter(fn, time.Second*time.Duration(timeout)) } +// ExecuteAfterMs executes the given function after the specified timeout duration, +// expressed in milliseconds. +// +// It converts the timeout value from milliseconds to a duration in seconds and then calls +// the ExecuteAfter function, passing the converted duration and the provided function. +// +// Example: +// +// ExecuteAfterMs(func() { +// fmt.Println("Hello, World!") +// }, 1000) +// +// @param fn The function to be executed. +// +// @param timeout The timeout duration in milliseconds. func ExecuteAfterMs(fn func(), timeout int64) { ExecuteAfter(fn, time.Millisecond*time.Duration(timeout)) } + +// ExecuteAfterMin executes the given function after the specified timeout duration, +// expressed in minutes. +// +// It converts the timeout value from minutes to a duration in seconds and then calls +// the ExecuteAfter function, passing the converted duration and the provided function. +// +// Example: +// +// ExecuteAfterMin(func() { +// fmt.Println("Hello, World!") +// }, 5) +// +// @param fn The function to be executed. +// +// @param timeout The timeout duration in minutes. func ExecuteAfterMin(fn func(), timeout int) { ExecuteAfter(fn, time.Minute*time.Duration(timeout)) } +// ExecuteAfter executes the given function after the specified timeout duration. +// +// It waits for the specified duration and then calls the provided function. +// +// Example: +// +// ExecuteAfter(func() { +// fmt.Println("Hello, World!") +// }, time.Second) +// +// @param fn The function to be executed. +// +// @param timeout The duration to wait before executing the function. func ExecuteAfter(fn func(), timeout time.Duration) { select { case <-time.After(timeout): diff --git a/fnutils/fn_test.go b/fnutils/fn_test.go new file mode 100644 index 0000000..5f0afc5 --- /dev/null +++ b/fnutils/fn_test.go @@ -0,0 +1,140 @@ +package fnutils + +import ( + "testing" + "time" +) + +func TestExecuteAfter(t *testing.T) { + t.Run("Execution occurs after the specified timeout", func(t *testing.T) { + executed := false + fn := func() { + executed = true + } + timeout := 5 * time.Second + + ExecuteAfter(fn, timeout) + + if !executed { + t.Error("Expected execution to occur after the specified timeout") + } + }) + + t.Run("Execution occurs immediately when timeout is zero", func(t *testing.T) { + executed := false + fn := func() { + executed = true + } + timeout := 0 * time.Second + + ExecuteAfter(fn, timeout) + + if !executed { + t.Error("Expected execution to occur immediately") + } + }) + + t.Run("Execution occurs only once", func(t *testing.T) { + counter := 0 + fn := func() { + counter++ + } + timeout := 1 * time.Second + + ExecuteAfter(fn, timeout) + time.Sleep(2 * time.Second) + + if counter != 1 { + t.Errorf("Expected execution to occur once, got %d times", counter) + } + }) + // + //t.Run("Multiple executions occur if the function takes longer than the timeout", func(t *testing.T) { + // executionCount := 0 + // fn := func() { + // executionCount++ + // time.Sleep(2 * time.Second) + // } + // timeout := 1 * time.Second + // + // go ExecuteAfter(fn, timeout) + // time.Sleep(3 * time.Second) + // + // if executionCount != 2 { + // t.Errorf("Expected execution to occur twice, got %d times", executionCount) + // } + //}) + // + //t.Run("No execution occurs if the function is nil", func(t *testing.T) { + // _ = func() { + // // This function should not be executed + // t.Error("Function should not be executed") + // } + // timeout := 1 * time.Second + // + // ExecuteAfter(nil, timeout) + //}) + // + //t.Run("No execution occurs if the timeout is set to 0 and the function is nil", func(t *testing.T) { + // _ = func() { + // // This function should not be executed + // t.Error("Function should not be executed") + // } + // timeout := 0 * time.Second + // + // ExecuteAfter(nil, timeout) + //}) + // + //t.Run("Execution does not occur when timeout is negative", func(t *testing.T) { + // executed := false + // fn := func() { + // executed = true + // } + // timeout := -1 * time.Second + // + // ExecuteAfter(fn, timeout) + // + // if executed { + // t.Error("Expected execution to not occur with negative timeout") + // } + //}) +} + +func TestExecuteAfterSecs(t *testing.T) { + t.Run("Execution after specific timeout (seconds)", func(t *testing.T) { + executed := false + fn := func() { + executed = true + } + ExecuteAfterSecs(fn, 5) + if !executed { + t.Error("Expected execution to occur after the specified timeout") + } + }) +} + +func TestExecuteAfterMs(t *testing.T) { + t.Run("Execution after specific timeout (milliseconds)", func(t *testing.T) { + executed := false + fn := func() { + executed = true + } + ExecuteAfterMs(fn, 50) + if !executed { + t.Error("Expected execution to occur after the specified timeout") + } + }) +} + +func TestExecuteAfterMin(t *testing.T) { + t.Run("Execution after specific timeout (minutes)", func(t *testing.T) { + executed := false + fn := func() { + executed = true + } + ExecuteAfterMin(fn, 1) + if !executed { + t.Error("Expected execution to occur after the specified timeout") + } + }) +} diff --git a/vfs/README.md b/vfs/README.md index 434ac20..b81e0df 100644 --- a/vfs/README.md +++ b/vfs/README.md @@ -1,8 +1,47 @@ # Virtual File System (VFS) Package -The VFS package provides a unified api for accessing multple file system. It is extensible and new implementation can be +The VFS package provides a unified api for accessing multiple file system. It is extensible and new implementation can be easily plugged in. - The default package has methods for local file system. +--- +- [Installation](#installation) +- [Usage](#usage) +--- + +### Installation +```bash +go get go.nandlabs.io/commons/vfs +``` + +### Usage +A simple usage of the library to create a directory in the OS. + +```go +package main + +import ( + "fmt" + "go.nandlabs.io/commons/vfs" +) + +var ( + testManager = GetManager() +) + +func GetRawPath(input string) (output string) { + currentPath, _ := os.Getwd() + u, _ := url.Parse(input) + path := currentPath + u.Path + output = u.Scheme + "://" + path + return +} +func main() { + u := GetRawPath("file:///test-data") + _, err := testManager.MkdirRaw(u) + if err != nil { + fmt.Errorf("MkdirRaw() error = %v", err) + } +} +``` \ No newline at end of file diff --git a/vfs/base_fs.go b/vfs/base_fs.go index 2729426..95c2008 100644 --- a/vfs/base_fs.go +++ b/vfs/base_fs.go @@ -1,7 +1,6 @@ package vfs import ( - "fmt" "io" "net/url" @@ -185,12 +184,10 @@ func (b *BaseVFS) Walk(u *url.URL, fn WalkFn) (err error) { if err == nil { if srcFi.IsDir() { children, err = src.ListAll() - fmt.Println(children) if err == nil { for _, child := range children { childInfo, err = child.Info() if err == nil { - if childInfo.IsDir() { err = b.Walk(child.Url(), fn) } else { diff --git a/vfs/local_file.go b/vfs/local_file.go index 206f2c7..b3078a8 100644 --- a/vfs/local_file.go +++ b/vfs/local_file.go @@ -1,15 +1,14 @@ package vfs import ( - "fmt" "io/fs" "io/ioutil" "net/url" "os" + "path/filepath" "go.nandlabs.io/commons/errutils" "go.nandlabs.io/commons/fsutils" - "go.nandlabs.io/commons/textutils" ) type OsFile struct { @@ -40,32 +39,27 @@ func (o *OsFile) ContentType() string { } func (o *OsFile) ListAll() (files []VFile, err error) { - var fis []fs.FileInfo manager := GetManager() - fis, err = ioutil.ReadDir(o.Location.Path) + var children []VFile + err = filepath.WalkDir(o.Location.Path, visit(manager, &children)) if err == nil { - var children []VFile - var child VFile - var childUrl *url.URL - for _, fi := range fis { - // TODO : in case of nested folder structure, does this work? - childUrl, err = o.Location.Parse(textutils.ForwardSlashStr + fi.Name()) - if err == nil { - child, err = manager.Open(childUrl) - if err == nil { - children = append(children, child) - } else { - break - } - } else { - break - } + files = children + } + return +} + +func visit(manager Manager, paths *[]VFile) func(string, os.DirEntry, error) (err error) { + return func(path string, info os.DirEntry, err2 error) (err error) { + if err2 != nil { + return } - if err == nil { - files = children + if !info.IsDir() { + var child VFile + child, err = manager.OpenRaw(path) + *paths = append(*paths, child) } + return } - return } func (o *OsFile) Delete() error { @@ -82,9 +76,7 @@ func (o *OsFile) Info() (VFileInfo, error) { func (o *OsFile) Parent() (file VFile, err error) { var fileInfos []fs.FileInfo - fmt.Println(o.Location.Path) fileInfos, err = ioutil.ReadDir(o.Location.Path) - fmt.Println(fileInfos) if err == nil { for _, info := range fileInfos { var f *os.File diff --git a/vfs/local_file_test.go b/vfs/local_file_test.go index 03c1794..5a1c2c2 100644 --- a/vfs/local_file_test.go +++ b/vfs/local_file_test.go @@ -167,7 +167,6 @@ func TestBaseVFS_Find(t *testing.T) { filterFunc := func(createdFile VFile) (result bool, err error) { var fileInfo VFileInfo result = false - fileInfo, err = createdFile.Info() if err != nil { return @@ -179,8 +178,24 @@ func TestBaseVFS_Find(t *testing.T) { } u2 := GetParsedUrl("file:///test-data") files, err := testManager.Find(u2, filterFunc) - // TODO : filter func not implemented completely due to issue with ListAll() - fmt.Println(len(files)) + if len(files) != 1 { + t.Errorf("Files not found = %v", err) + } +} + +func TestOsFile_ListAll(t *testing.T) { + u := GetParsedUrl("file:///test-data/raw-folder/listFile-2.txt") + _, _ = testManager.Create(u) + + u = GetParsedUrl("file:///test-data") + output, err := testManager.List(u) + if err != nil { + t.Errorf("Error listing files = %v", err) + } + for _, item := range output { + fileInfo, _ := item.Info() + fmt.Println(fileInfo.Name()) + } } func TestOsFs_Delete(t *testing.T) {