// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // builtin-os-file.go package expr import ( "bufio" "fmt" "io" "os" ) 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) } func errInvalidFileHandle(funcName string, v any) error { if v != nil { return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v) } else { return fmt.Errorf("%s(): invalid file handle", funcName) } } func createFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[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)} } } else { err = errMissingFilePath(name) } return } func openFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[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)} } } else { err = errMissingFilePath(name) } return } func appendFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { if filePath, ok := args[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)} } } else { err = errMissingFilePath(name) } return } func closeFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool if handle, ok = args[ParamHandle].(osHandle); !ok { invalidFileHandle = args[ParamHandle] } if handle != nil { if fh := handle.getFile(); fh != nil { if w, ok := handle.(*osWriter); ok { err = w.writer.Flush() } if err == nil { err = fh.Close() } } } if err == nil && (handle == nil || invalidFileHandle != nil) { err = errInvalidFileHandle(name, handle) } result = err == nil return } func fileWriteTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool if handle, ok = args[ParamHandle].(osHandle); !ok { invalidFileHandle = args[ParamHandle] } if handle != nil { if w, ok := handle.(*osWriter); ok { if v, exists := args[ParamItem]; exists { argv := v.([]any) result, err = fmt.Fprint(w.writer, argv...) } } else { invalidFileHandle = handle } } if err == nil && (handle == nil || invalidFileHandle != nil) { err = errInvalidFileHandle(name, invalidFileHandle) } return } func fileReadTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool result = nil if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil { invalidFileHandle = args[ParamHandle] } if handle != nil { if r, ok := handle.(*osReader); 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) if err == io.EOF { err = nil } if len(v) > 0 { if v[len(v)-1] == limit { result = v[0 : len(v)-1] } else { result = v } } } else { invalidFileHandle = handle } } if err == nil && (handle == nil || invalidFileHandle != nil) { err = errInvalidFileHandle(name, invalidFileHandle) } return } func fileReadTextAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool result = nil if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil { invalidFileHandle = args[ParamHandle] } if handle != nil { if r, ok := handle.(*osReader); ok { var b []byte b, err = io.ReadAll(r.reader) result = string(b) } else { invalidFileHandle = handle } } if err == nil && (handle == nil || invalidFileHandle != nil) { err = errInvalidFileHandle(name, invalidFileHandle) } return } func ImportOsFuncs(ctx ExprContext) { ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{ NewFuncParam(ParamHandle), }) ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{ NewFuncParam(ParamHandle), NewFuncParamFlagDef(ParamItem, PfDefault|PfRepeat, ""), }) ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{ NewFuncParam(ParamHandle), NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"), }) ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{ NewFuncParam(ParamHandle), }) } func init() { RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions") }