From c10053253cf02e8672b18f9745aa56a48320bfc0 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Fri, 8 May 2026 10:16:03 +0200 Subject: [PATCH] os file builtins refactored with the package 'file' --- builtin-base.go | 36 ++-- builtin-os-file-iter-byte.go | 123 +++++++++++++ builtin-os-file-iter-line.go | 143 ++++++++++++++++ builtin-os-file-iter.go | 129 +------------- builtin-os-file.go | 114 +++++-------- file/coverage.html | 322 +++++++++++++++++++++++++++++++++++ file/file.go | 46 +++++ file/reader.go | 83 +++++++++ file/t_reader_test.go | 62 +++++++ file/t_writer_test.go | 69 ++++++++ file/writer.go | 82 +++++++++ t_builtin-iterator_test.go | 2 +- t_builtin-os-file_test.go | 15 +- t_iterator_test.go | 6 +- 14 files changed, 1011 insertions(+), 221 deletions(-) create mode 100644 builtin-os-file-iter-byte.go create mode 100644 builtin-os-file-iter-line.go create mode 100644 file/coverage.html create mode 100644 file/file.go create mode 100644 file/reader.go create mode 100644 file/t_reader_test.go create mode 100644 file/t_writer_test.go create mode 100644 file/writer.go diff --git a/builtin-base.go b/builtin-base.go index 3ad6ae3..4bc243a 100644 --- a/builtin-base.go +++ b/builtin-base.go @@ -255,18 +255,25 @@ func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any return } -// func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { -// var varName string -// var ok bool +func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { + var ord int -// if varName, ok = args[kern.ParamName].(string); !ok { -// return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName]) -// } else { -// ctx.GetParent().DeleteVar(varName) -// result = nil -// } -// return -// } + if n, ok := args[kern.ParamValue].(byte); ok { + ord = int(n) + } else if n, ok := args[kern.ParamValue].(int64); ok { + ord = int(n) + } else if n, ok := args[kern.ParamValue].(int); ok { + ord = n + } else { + return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName]) + } + if ord < 0 || ord > 255 { + err = kern.ErrFuncInvalidArg(name, fmt.Sprintf("character code must be in range 0-255, got %d", ord)) + } else { + result = string(rune(ord)) + } + return +} //// import @@ -308,10 +315,9 @@ func ImportBuiltinsFuncs(ctx kern.ExprContext) { kern.NewFuncParam(kern.ParamValue), }) - // ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{ - // kern.NewFuncParam(kern.ParamName), - // kern.NewFuncParam(kern.ParamValue), - // }) + ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{ + kern.NewFuncParam(kern.ParamValue), + }) } func init() { diff --git a/builtin-os-file-iter-byte.go b/builtin-os-file-iter-byte.go new file mode 100644 index 0000000..553140b --- /dev/null +++ b/builtin-os-file-iter-byte.go @@ -0,0 +1,123 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// builtin-os-file.go +package expr + +import ( + "fmt" + "slices" + + "git.portale-stac.it/go-pkg/expr/file" + "git.portale-stac.it/go-pkg/expr/kern" +) + +const fileByteIteratorType = "fileByteIterator" + +type fileFileByteIterator struct { + reader *file.Reader + index int64 + count int64 + b byte + autoClose bool +} + +func newFileByteIterator(r *file.Reader, autoClose bool) *fileFileByteIterator { + return &fileFileByteIterator{reader: r, index: -1, autoClose: autoClose} +} + +func (it *fileFileByteIterator) TypeName() string { + return fileByteIteratorType +} + +func (it *fileFileByteIterator) String() string { + if it.reader.Valid() { + return fmt.Sprintf("$(%s@%q)", fileByteIteratorType, it.reader.GetName()) + } + return fmt.Sprintf("$(%s@)", fileByteIteratorType) +} + +func (it *fileFileByteIterator) Count() int64 { + return it.count +} + +func (it *fileFileByteIterator) Next() (item any, err error) { // must return io.EOF after the last item + if it.b, err = it.reader.ReadByte(); err == nil { + it.index++ + it.count++ + item = it.b + } else if it.autoClose { + it.Clean() + } + return +} + +func (it *fileFileByteIterator) Current() (item any, err error) { + item = it.b + return +} + +func (it *fileFileByteIterator) Index() int64 { + return it.index +} + +func (it *fileFileByteIterator) Reset() (err error) { + if err = it.reader.Reset(); err == nil { + it.index = -1 + it.count = 0 + it.b = 0 + } + return +} + +func (it *fileFileByteIterator) HasOperation(name string) bool { + return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name) +} + +func (it *fileFileByteIterator) Clean() (err error) { + if it.reader.Valid() { + if err = it.reader.GetFile().Close(); err == nil { + it.reader = nil + } + } + return nil +} + +func (it *fileFileByteIterator) CallOperation(name string, args map[string]any) (v any, err error) { + switch name { + case kern.NextName: + v, err = it.Next() + case kern.ResetName: + err = it.Reset() + case kern.CleanName: + err = it.Clean() + case kern.IndexName: + v = int64(it.Index()) + case kern.CurrentName: + v, err = it.Current() + case kern.CountName: + v = it.count + default: + err = kern.ErrNoOperation(name) + } + return +} + +func fileByteIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { + var handle *file.Reader + var invalidFileHandle any + var autoClose bool + + result = nil + + if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil { + if handle != nil { + result = newFileByteIterator(handle, autoClose) + } + } + + if err == nil && (handle == nil || invalidFileHandle != nil) { + err = errInvalidFileHandle(name, invalidFileHandle) + } + return +} diff --git a/builtin-os-file-iter-line.go b/builtin-os-file-iter-line.go new file mode 100644 index 0000000..587c236 --- /dev/null +++ b/builtin-os-file-iter-line.go @@ -0,0 +1,143 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// builtin-os-file.go +package expr + +import ( + "fmt" + "slices" + + "git.portale-stac.it/go-pkg/expr/file" + "git.portale-stac.it/go-pkg/expr/kern" +) + +const fileLineIteratorType = "fileLineIterator" + +type fileFileLineIterator struct { + reader *file.Reader + index int64 + count int64 + line string + autoClose bool +} + +func newFileLineIterator(r *file.Reader, autoClose bool) *fileFileLineIterator { + return &fileFileLineIterator{reader: r, index: -1, autoClose: autoClose} +} + +func (it *fileFileLineIterator) TypeName() string { + return fileLineIteratorType +} + +func (it *fileFileLineIterator) String() string { + if it.reader != nil && it.reader.GetFile() != nil { + return fmt.Sprintf("$(%s@%q)", fileLineIteratorType, it.reader.GetName()) + } + return fmt.Sprintf("$(%s@)", fileLineIteratorType) +} + +func (it *fileFileLineIterator) Count() int64 { + return it.count +} + +func (it *fileFileLineIterator) Next() (item any, err error) { // must return io.EOF after the last item + if it.line, err = it.reader.ReadString('\n'); err == nil { + it.index++ + it.count++ + item = it.line[0 : len(it.line)-1] + } else if it.autoClose { + it.Clean() + } + return +} + +func (it *fileFileLineIterator) Current() (item any, err error) { + if len(it.line) > 0 { + item = it.line[0 : len(it.line)-1] + } + return +} + +func (it *fileFileLineIterator) Index() int64 { + return it.index +} + +func (it *fileFileLineIterator) Reset() (err error) { + if err = it.reader.Reset(); err == nil { + it.index = -1 + it.count = 0 + it.line = "" + } + return +} + +func (it *fileFileLineIterator) HasOperation(name string) bool { + return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name) +} + +func (it *fileFileLineIterator) Clean() (err error) { + if it.reader != nil { + if err = it.reader.Close(); err == nil { + it.reader = nil + } + } + return nil +} + +func (it *fileFileLineIterator) CallOperation(name string, args map[string]any) (v any, err error) { + switch name { + case kern.NextName: + v, err = it.Next() + case kern.ResetName: + err = it.Reset() + case kern.CleanName: + err = it.Clean() + case kern.IndexName: + v = int64(it.Index()) + case kern.CurrentName: + v, err = it.Current() + case kern.CountName: + v = it.count + default: + err = kern.ErrNoOperation(name) + } + return +} + +func fileLineIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { + var handle *file.Reader + var invalidFileHandle any + var autoClose bool + + result = nil + // if handle, ok = args[paramHandleOrPath].(*osReader); !ok { + // if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 { + // var handleAny any + // if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil { + // return + // } + // if handleAny != nil { + // handle = handleAny.(*osReader) + // autoClose = true + // } + // } else { + // invalidFileHandle = args[paramHandleOrPath] + // } + // } + + // if handle != nil { + // result = newFileLineIterator(handle, autoClose) + // } + + if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil { + if handle != nil { + result = newFileLineIterator(handle, autoClose) + } + } + + if err == nil && (handle == nil || invalidFileHandle != nil) { + err = errInvalidFileHandle(name, invalidFileHandle) + } + return +} diff --git a/builtin-os-file-iter.go b/builtin-os-file-iter.go index 5d9deb5..c2c1b9a 100644 --- a/builtin-os-file-iter.go +++ b/builtin-os-file-iter.go @@ -5,147 +5,28 @@ package expr import ( - "fmt" - "io" - "slices" - + "git.portale-stac.it/go-pkg/expr/file" "git.portale-stac.it/go-pkg/expr/kern" ) const paramHandleOrPath = "handle-or-path" -const fileReadTextIteratorType = "fileReadTextIterator" -type fileReadTextIterator struct { - osReader *osReader - index int64 - count int64 - line string - autoClose bool -} +func initFileHandle(ctx kern.ExprContext, name string, args map[string]any) (handle *file.Reader, invalidFileHandle any, autoClose bool, err error) { + var ok bool -func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator { - return &fileReadTextIterator{osReader: r, index: -1, autoClose: autoClose} -} - -func (it *fileReadTextIterator) TypeName() string { - return fileReadTextIteratorType -} - -func (it *fileReadTextIterator) String() string { - if it.osReader != nil && it.osReader.fh != nil { - return fmt.Sprintf("$(%s@%q)", fileReadTextIteratorType, it.osReader.fh.Name()) - } - return fmt.Sprintf("$(%s@)", fileReadTextIteratorType) -} - -func (it *fileReadTextIterator) Count() int64 { - return it.count -} - -func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item - if it.osReader.fh != nil { - if it.line, err = it.osReader.reader.ReadString('\n'); err == nil { - it.index++ - it.count++ - item = it.line[0 : len(it.line)-1] - } else if it.autoClose { - it.Clean() - } - } - return -} - -func (it *fileReadTextIterator) Current() (item any, err error) { - if len(it.line) > 0 { - item = it.line[0 : len(it.line)-1] - } - return -} - -func (it *fileReadTextIterator) Index() int64 { - return it.index -} - -func (it *fileReadTextIterator) Reset() (err error) { - if _, err = it.osReader.fh.Seek(0, io.SeekStart); err == nil { - it.index = -1 - it.count = 0 - it.line = "" - } - return -} - -func (it *fileReadTextIterator) HasOperation(name string) bool { - return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name) -} - -func (it *fileReadTextIterator) Clean() (err error) { - if it.osReader.fh != nil { - if err = it.osReader.fh.Close(); err == nil { - it.osReader = nil - } - } - return nil -} - -func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) { - switch name { - case kern.NextName: - v, err = it.Next() - case kern.ResetName: - err = it.Reset() - case kern.CleanName: - err = it.Clean() - case kern.IndexName: - v = int64(it.Index()) - case kern.CurrentName: - v, err = it.Current() - case kern.CountName: - v = it.count - default: - err = kern.ErrNoOperation(name) - } - return -} - -func fileReadIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { - var handle *osReader - var invalidFileHandle any - var ok, autoClose bool - - result = nil - if handle, ok = args[paramHandleOrPath].(*osReader); !ok { + if handle, ok = args[paramHandleOrPath].(*file.Reader); !ok { if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 { var handleAny any if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil { return } if handleAny != nil { - handle = handleAny.(*osReader) + handle = handleAny.(*file.Reader) autoClose = true } } else { invalidFileHandle = args[paramHandleOrPath] } } - - if handle != nil { - result = newReadTextIterator(handle, autoClose) - - } - - if err == nil && (handle == nil || invalidFileHandle != nil) { - err = errInvalidFileHandle(name, invalidFileHandle) - } return } - -// func ImportOsIterFuncs(ctx ExprContext) { -// ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{ -// NewFuncParam(paramHandleOrPath), -// }) -// } - -// func init() { -// RegisterBuiltinModule("os.file", ImportOsIterFuncs, "Operating system file iterator functions") -// } diff --git a/builtin-os-file.go b/builtin-os-file.go index 31d0107..b43ff3f 100644 --- a/builtin-os-file.go +++ b/builtin-os-file.go @@ -5,11 +5,10 @@ package expr import ( - "bufio" "fmt" "io" - "os" + "git.portale-stac.it/go-pkg/expr/file" "git.portale-stac.it/go-pkg/expr/kern" ) @@ -17,44 +16,6 @@ const ( osLimitCh = "limitCh" ) -type osHandle interface { - getFile() *os.File -} - -type osWriter struct { - fh *os.File - writer *bufio.Writer -} - -func (h *osWriter) TypeName() string { - return "osWriter" -} - -func (h *osWriter) String() string { - return "writer" -} - -func (h *osWriter) getFile() *os.File { - return h.fh -} - -type osReader struct { - fh *os.File - reader *bufio.Reader -} - -func (h *osReader) TypeName() string { - return "osReader" -} - -func (h *osReader) String() string { - return "reader" -} - -func (h *osReader) getFile() *os.File { - return h.fh -} - func errMissingFilePath(funcName string) error { return fmt.Errorf("%s(): missing or invalid file path", funcName) } @@ -67,24 +28,26 @@ func errInvalidFileHandle(funcName string, v any) error { } } -func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { +func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 { - var fh *os.File - if fh, err = os.Create(filePath); err == nil { - result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} - } + // var fh *os.File + // if fh, err = os.Open(filePath); err == nil { + // result = file.NewReader(fh) + // } + result, err = file.OpenReader(filePath) } else { err = errMissingFilePath(name) } return } -func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { +func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 { - var fh *os.File - if fh, err = os.Open(filePath); err == nil { - result = &osReader{fh: fh, reader: bufio.NewReader(fh)} - } + // var fh *os.File + // if fh, err = os.Create(filePath); err == nil { + // result = file.NewWriter(fh) + // } + result, err = file.CreateWriter(filePath) } else { err = errMissingFilePath(name) } @@ -93,10 +56,11 @@ func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resul func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 { - var fh *os.File - if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { - result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} - } + // var fh *os.File + // if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { + // result = file.NewWriter(fh) + // } + result, err = file.AppendWriter(filePath) } else { err = errMissingFilePath(name) } @@ -104,18 +68,18 @@ func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (res } func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { - var handle osHandle + var handle file.Handle var invalidFileHandle any var ok bool - if handle, ok = args[kern.ParamHandle].(osHandle); !ok { + if handle, ok = args[kern.ParamHandle].(file.Handle); !ok { invalidFileHandle = args[kern.ParamHandle] } if handle != nil { - if fh := handle.getFile(); fh != nil { - if w, ok := handle.(*osWriter); ok { - err = w.writer.Flush() + if fh := handle.GetFile(); fh != nil { + if w, ok := handle.(*file.Writer); ok { + err = w.Flush() } if err == nil { @@ -131,19 +95,20 @@ func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (resu } func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { - var handle osHandle + var handle file.Handle var invalidFileHandle any var ok bool - if handle, ok = args[kern.ParamHandle].(osHandle); !ok { + if handle, ok = args[kern.ParamHandle].(file.Handle); !ok { invalidFileHandle = args[kern.ParamHandle] } if handle != nil { - if w, ok := handle.(*osWriter); ok { + if w, ok := handle.(*file.Writer); ok { if v, exists := args[kern.ParamItem]; exists { argv := v.([]any) - result, err = fmt.Fprint(w.writer, argv...) + // result, err = fmt.Fprint(w.writer, argv...) + result, err = w.Write(argv...) } } else { invalidFileHandle = handle @@ -157,24 +122,24 @@ func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) ( } func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { - var handle osHandle + var handle file.Handle var invalidFileHandle any var ok bool result = nil - if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil { + if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil { invalidFileHandle = args[kern.ParamHandle] } if handle != nil { - if r, ok := handle.(*osReader); ok { + if r, ok := handle.(*file.Reader); ok { var limit byte = '\n' var v string if s, ok := args[osLimitCh].(string); ok && len(s) > 0 { limit = s[0] } - v, err = r.reader.ReadString(limit) + v, err = r.ReadString(limit) if err == io.EOF { err = nil } @@ -197,19 +162,19 @@ func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (r } func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) { - var handle osHandle + var handle file.Handle var invalidFileHandle any var ok bool result = nil - if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil { + if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil { invalidFileHandle = args[kern.ParamHandle] } if handle != nil { - if r, ok := handle.(*osReader); ok { + if r, ok := handle.(*file.Reader); ok { var b []byte - b, err = io.ReadAll(r.reader) + b, err = r.ReadAll() result = string(b) } else { invalidFileHandle = handle @@ -253,9 +218,14 @@ func ImportOsFuncs(ctx kern.ExprContext) { kern.NewFuncParam(kern.ParamHandle), }) - ctx.RegisterFunc("fileReadIterator", kern.NewGolangFunctor(fileReadIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{ + ctx.RegisterFunc("fileLineIterator", kern.NewGolangFunctor(fileLineIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{ kern.NewFuncParam(paramHandleOrPath), }) + + ctx.RegisterFunc("fileByteIterator", kern.NewGolangFunctor(fileByteIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{ + kern.NewFuncParam(paramHandleOrPath), + }) + } func init() { diff --git a/file/coverage.html b/file/coverage.html new file mode 100644 index 0000000..6a529bd --- /dev/null +++ b/file/coverage.html @@ -0,0 +1,322 @@ + + + + + + file: Go Coverage Report + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + + + + + +
+ + + diff --git a/file/file.go b/file/file.go new file mode 100644 index 0000000..2121188 --- /dev/null +++ b/file/file.go @@ -0,0 +1,46 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// file.go +package file + +import ( + "os" + + "git.portale-stac.it/go-pkg/expr/kern" +) + +type Handle interface { + kern.Typer + GetFile() *os.File + GetName() string + Valid() bool + Close() error +} + +type handleBase struct { + fh *os.File +} + +func (h *handleBase) GetFile() *os.File { + return h.fh +} + +func (h *handleBase) GetName() (name string) { + if h.fh != nil { + name = h.fh.Name() + } + return +} + +func (h *handleBase) Valid() bool { + return h.fh != nil +} + +func (h *handleBase) Close() (err error) { + if h.fh != nil { + err = h.fh.Close() + h.fh = nil + } + return +} diff --git a/file/reader.go b/file/reader.go new file mode 100644 index 0000000..0d8201b --- /dev/null +++ b/file/reader.go @@ -0,0 +1,83 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// reader.go +package file + +import ( + "bufio" + "io" + "os" +) + +type Reader struct { + // fh *os.File + handleBase + reader *bufio.Reader +} + +func NewReader(fh *os.File) *Reader { + return &Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)} +} + +func OpenReader(filePath string) (r *Reader, err error) { + var fh *os.File + if fh, err = os.Open(filePath); err == nil { + r = NewReader(fh) + } + return +} + +func (h *Reader) TypeName() string { + return "fileReader" +} + +func (h *Reader) String() string { + return "reader" +} + +func (h *Reader) Valid() bool { + return h.handleBase.Valid() && h.reader != nil +} + +func (w *Reader) Close() (err error) { + w.reader = nil + err = w.handleBase.Close() + return +} + +func (h *Reader) ReadByte() (b byte, err error) { + if h.reader != nil { + b, err = h.reader.ReadByte() + } else { + err = io.ErrClosedPipe + } + return +} + +func (h *Reader) ReadAll() (p []byte, err error) { + if h.reader != nil { + p, err = io.ReadAll(h.reader) + } else { + err = io.ErrClosedPipe + } + return +} + +func (h *Reader) ReadString(delim byte) (s string, err error) { + if h.reader != nil { + s, err = h.reader.ReadString(delim) + } else { + err = io.ErrClosedPipe + } + return +} + +func (h *Reader) Reset() (err error) { + if h.fh != nil { + if _, err = h.fh.Seek(0, 0); err == nil { + h.reader.Reset(h.fh) + } + } + return +} diff --git a/file/t_reader_test.go b/file/t_reader_test.go new file mode 100644 index 0000000..5c3d37c --- /dev/null +++ b/file/t_reader_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// reader.go +package file + +import "testing" + +func TestOpenReader(t *testing.T) { + r, err := OpenReader("t_reader_test.go") + if err != nil { + t.Fatalf("OpenReader failed: %v", err) + } + defer r.Close() + + if !r.Valid() { + t.Fatal("Reader should be valid after opening") + } + + if r.TypeName() != "fileReader" { + t.Fatalf("Expected TypeName 'fileReader', got '%s'", r.TypeName()) + } + + if r.String() != "reader" { + t.Fatalf("Expected String 'reader', got '%s'", r.String()) + } + + // GetName may return either "t_reader_test.go" or "./t_reader_test.go" depending on the environment + name := r.GetName() + if (name != "t_reader_test.go") && (name != "./t_reader_test.go") { + t.Fatalf("Expected GetName 't_reader_test.go' or './t_reader_test.go', got '%s'", name) + } + + // Test reading a byte + b, err := r.ReadByte() + if err != nil { + t.Fatalf("ReadByte failed: %v", err) + } + if b == 0 { + t.Fatal("ReadByte should not return zero byte") + } + + err = r.Reset() + if err != nil { + t.Fatalf("Reset failed: %v", err) + } + + if s, err := r.ReadString('\n'); err != nil { + t.Fatalf("ReadString failed: %v", err) + } else { + t.Logf("ReadString: %s", s) + } + + // Test reading all content + content, err := r.ReadAll() + if err != nil { + t.Fatalf("ReadAll failed: %v", err) + } + if len(content) == 0 { + t.Fatal("ReadAll should return non-empty content") + } +} diff --git a/file/t_writer_test.go b/file/t_writer_test.go new file mode 100644 index 0000000..dcf4208 --- /dev/null +++ b/file/t_writer_test.go @@ -0,0 +1,69 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// writer.go +package file + +import "testing" + +func TestCreateWriter(t *testing.T) { + w, err := CreateWriter("/tmp/test_writer.txt") + if err != nil { + t.Fatalf("CreateWriter failed: %v", err) + } + defer w.Close() + + if !w.Valid() { + t.Fatal("Writer should be valid after creation") + } + + if w.TypeName() != "fileWriter" { + t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName()) + } + + if w.String() != "writer" { + t.Fatalf("Expected String 'writer', got '%s'", w.String()) + } + + name := w.GetName() + if name != "/tmp/test_writer.txt" { + t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name) + } + + if n, err := w.Write("Hello, World!\n"); err != nil { + t.Fatalf("Write failed: %v", err) + } else if n != len("Hello, World!\n") { + t.Fatalf("Expected to write %d bytes, wrote %d", len("Hello, World!\n"), n) + } + + if n, err := w.Writef("This is a %s.\n", "test"); err != nil { + t.Fatalf("Writef failed: %v", err) + } else if n != len("This is a test.\n") { + t.Fatalf("Expected to write %d bytes, wrote %d", len("This is a test.\n"), n) + } +} + +func TestAppendWriter(t *testing.T) { + w, err := AppendWriter("/tmp/test_writer.txt") + if err != nil { + t.Fatalf("AppendWriter failed: %v", err) + } + defer w.Close() + + if !w.Valid() { + t.Fatal("Writer should be valid after opening for append") + } + + if w.TypeName() != "fileWriter" { + t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName()) + } + + if w.String() != "writer" { + t.Fatalf("Expected String 'writer', got '%s'", w.String()) + } + + name := w.GetName() + if name != "/tmp/test_writer.txt" { + t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name) + } +} diff --git a/file/writer.go b/file/writer.go new file mode 100644 index 0000000..fd5069e --- /dev/null +++ b/file/writer.go @@ -0,0 +1,82 @@ +// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// writer.go +package file + +import ( + "bufio" + "fmt" + "os" +) + +type Writer struct { + // fh *os.File + handleBase + writer *bufio.Writer +} + +func NewWriter(fh *os.File) *Writer { + return &Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)} +} + +func CreateWriter(filePath string) (w *Writer, err error) { + var fh *os.File + if fh, err = os.Create(filePath); err == nil { + w = NewWriter(fh) + } + return +} + +func AppendWriter(filePath string) (w *Writer, err error) { + var fh *os.File + if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil { + w = NewWriter(fh) + } + return +} + +func (w *Writer) TypeName() string { + return "fileWriter" +} + +func (w *Writer) String() string { + return "writer" +} + +func (w *Writer) Valid() bool { + return w.handleBase.Valid() && w.writer != nil +} + +func (w *Writer) Close() (err error) { + var err1 error + if w.writer != nil { + err1 = w.Flush() + w.writer = nil + } + if err = w.handleBase.Close(); err == nil { + err = err1 + } + return +} + +func (w *Writer) Flush() (err error) { + if w.writer != nil { + err = w.writer.Flush() + } + return +} + +func (w *Writer) Write(args ...any) (n int, err error) { + if w.writer != nil { + n, err = fmt.Fprint(w.writer, args...) + } + return +} + +func (w *Writer) Writef(format string, args ...any) (n int, err error) { + if w.writer != nil { + n, err = fmt.Fprintf(w.writer, format, args...) + } + return +} diff --git a/t_builtin-iterator_test.go b/t_builtin-iterator_test.go index b456d91..21af405 100644 --- a/t_builtin-iterator_test.go +++ b/t_builtin-iterator_test.go @@ -21,7 +21,7 @@ func TestFuncRun(t *testing.T) { /* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`}, /* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil}, /* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`}, - /* 10 */ {`builtin ["os.file", "iterator"]; it = fileReadIterator("test-file.txt"); run(it)`, nil, nil}, + /* 10 */ {`builtin ["os.file", "iterator"]; it = fileLineIterator("test-file.txt"); run(it)`, nil, nil}, } //t.Setenv("EXPR_PATH", ".") diff --git a/t_builtin-os-file_test.go b/t_builtin-os-file_test.go index acef856..357a9a1 100644 --- a/t_builtin-os-file_test.go +++ b/t_builtin-os-file_test.go @@ -27,16 +27,19 @@ func TestFuncOs(t *testing.T) { /* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`}, /* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil}, /* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`}, - /* 16 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++`, "uno", nil}, - /* 17 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()}, - /* 18 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); it.clean`, nil, nil}, - /* 19 */ {`builtin "os.file"; it=fileReadIterator("test-file.txt"); string(it)`, `$(fileReadTextIterator@"test-file.txt")`, nil}, + /* 16 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it++`, "uno", nil}, + /* 17 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it++;it++;it++`, nil, io.EOF.Error()}, + /* 18 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); it.clean`, nil, nil}, + /* 19 */ {`builtin "os.file"; it=fileLineIterator("test-file.txt"); string(it)`, `$(fileLineIterator@"test-file.txt")`, nil}, /* 20 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle); string(handle)`, `reader`, nil}, /* 21 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle); string(handle)`, `writer`, nil}, + /* 22 */ {`builtin "os.file"; it=fileByteIterator("test-file.txt"); string(it)`, `$(fileByteIterator@"test-file.txt")`, nil}, + /* 23 */ {`builtin ["os.file", "string"]; it=fileByteIterator("test-file.txt"); char(it++)`, `u`, nil}, + /* 24 */ {`builtin ["os.file", "string"]; it=fileByteIterator("test-file.txt"); it++; it.reset; char(it++)`, `u`, nil}, } // t.Setenv("EXPR_PATH", ".") - // runTestSuiteSpec(t, section, inputs, 19) - runTestSuite(t, section, inputs) + runTestSuiteSpec(t, section, inputs, 24) + // runTestSuite(t, section, inputs) } diff --git a/t_iterator_test.go b/t_iterator_test.go index 2e9f6c5..d3a55ef 100644 --- a/t_iterator_test.go +++ b/t_iterator_test.go @@ -35,9 +35,9 @@ func TestIteratorParser(t *testing.T) { /* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil}, /* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil}, /* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, kern.NewList([]any{int64(1), "one"}), nil}, - /* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${__}`, kern.NewList([]any{int64(0), int64(1)}), nil}, - /* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil}, - /* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil}, + /* 23 */ {`builtin "os.file"; fileLineIterator("test-file.txt") map ${__}`, kern.NewList([]any{int64(0), int64(1)}), nil}, + /* 24 */ {`builtin "os.file"; #(fileLineIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil}, + /* 25 */ {`builtin "os.file"; #(fileLineIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil}, /* 26 */ {`#($(10) map ${_})`, int64(10), nil}, /* 27 */ {`#($(10,0) map ${_})`, int64(10), nil}, /* 28 */ {`$(10) digest ${_}`, int64(9), nil},