package fs import ( "io" "io/fs" "os" "path/filepath" "strings" "time" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) func GetFSFunctions() map[string]luajit.GoFunction { return map[string]luajit.GoFunction{ "file_exists": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_exists: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_exists: argument must be a string") } _, err = os.Stat(path) s.PushBoolean(err == nil) return 1 }, "file_size": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_size: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_size: argument must be a string") } info, err := os.Stat(path) if err != nil { s.PushNumber(-1) return 1 } s.PushNumber(float64(info.Size())) return 1 }, "file_is_dir": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_is_dir: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_is_dir: argument must be a string") } info, err := os.Stat(path) if err != nil { s.PushBoolean(false) return 1 } s.PushBoolean(info.IsDir()) return 1 }, "file_read": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_read: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_read: argument must be a string") } data, err := os.ReadFile(path) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } s.PushString(string(data)) return 1 }, "file_write": func(s *luajit.State) int { if err := s.CheckExactArgs(2); err != nil { return s.PushError("file_write: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_write: first argument must be a string") } content, err := s.SafeToString(2) if err != nil { return s.PushError("file_write: second argument must be a string") } err = os.WriteFile(path, []byte(content), 0644) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_append": func(s *luajit.State) int { if err := s.CheckExactArgs(2); err != nil { return s.PushError("file_append: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_append: first argument must be a string") } content, err := s.SafeToString(2) if err != nil { return s.PushError("file_append: second argument must be a string") } file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } defer file.Close() _, err = file.WriteString(content) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_copy": func(s *luajit.State) int { if err := s.CheckExactArgs(2); err != nil { return s.PushError("file_copy: %v", err) } src, err := s.SafeToString(1) if err != nil { return s.PushError("file_copy: first argument must be a string") } dst, err := s.SafeToString(2) if err != nil { return s.PushError("file_copy: second argument must be a string") } srcFile, err := os.Open(src) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } defer srcFile.Close() dstFile, err := os.Create(dst) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } defer dstFile.Close() _, err = io.Copy(dstFile, srcFile) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_move": func(s *luajit.State) int { if err := s.CheckExactArgs(2); err != nil { return s.PushError("file_move: %v", err) } src, err := s.SafeToString(1) if err != nil { return s.PushError("file_move: first argument must be a string") } dst, err := s.SafeToString(2) if err != nil { return s.PushError("file_move: second argument must be a string") } err = os.Rename(src, dst) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_delete": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_delete: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_delete: argument must be a string") } err = os.Remove(path) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_mtime": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_mtime: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_mtime: argument must be a string") } info, err := os.Stat(path) if err != nil { s.PushNumber(-1) return 1 } s.PushNumber(float64(info.ModTime().Unix())) return 1 }, "dir_create": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("dir_create: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("dir_create: argument must be a string") } err = os.MkdirAll(path, 0755) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "dir_remove": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("dir_remove: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("dir_remove: argument must be a string") } err = os.RemoveAll(path) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "dir_list": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("dir_list: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("dir_list: argument must be a string") } entries, err := os.ReadDir(path) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } s.CreateTable(len(entries), 0) for i, entry := range entries { s.PushNumber(float64(i + 1)) s.CreateTable(0, 4) s.PushString("name") s.PushString(entry.Name()) s.SetTable(-3) s.PushString("is_dir") s.PushBoolean(entry.IsDir()) s.SetTable(-3) if info, err := entry.Info(); err == nil { s.PushString("size") s.PushNumber(float64(info.Size())) s.SetTable(-3) s.PushString("mtime") s.PushNumber(float64(info.ModTime().Unix())) s.SetTable(-3) } s.SetTable(-3) } return 1 }, "path_join": func(s *luajit.State) int { var parts []string for i := 1; i <= s.GetTop(); i++ { part, err := s.SafeToString(i) if err != nil { return s.PushError("path_join: argument %d must be a string", i) } parts = append(parts, part) } s.PushString(filepath.Join(parts...)) return 1 }, "path_dir": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_dir: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_dir: argument must be a string") } s.PushString(filepath.Dir(path)) return 1 }, "path_basename": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_basename: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_basename: argument must be a string") } s.PushString(filepath.Base(path)) return 1 }, "path_ext": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_ext: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_ext: argument must be a string") } s.PushString(filepath.Ext(path)) return 1 }, "path_abs": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_abs: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_abs: argument must be a string") } abs, err := filepath.Abs(path) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } s.PushString(abs) return 1 }, "path_clean": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_clean: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_clean: argument must be a string") } s.PushString(filepath.Clean(path)) return 1 }, "path_split": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("path_split: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("path_split: argument must be a string") } dir, file := filepath.Split(path) s.PushString(dir) s.PushString(file) return 2 }, "temp_file": func(s *luajit.State) int { var prefix string if s.GetTop() >= 1 { if p, err := s.SafeToString(1); err == nil { prefix = p } } file, err := os.CreateTemp("", prefix) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } defer file.Close() s.PushString(file.Name()) return 1 }, "temp_dir": func(s *luajit.State) int { var prefix string if s.GetTop() >= 1 { if p, err := s.SafeToString(1); err == nil { prefix = p } } dir, err := os.MkdirTemp("", prefix) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } s.PushString(dir) return 1 }, "glob": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("glob: %v", err) } pattern, err := s.SafeToString(1) if err != nil { return s.PushError("glob: argument must be a string") } matches, err := filepath.Glob(pattern) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } if err := s.PushValue(matches); err != nil { return s.PushError("glob: failed to push result: %v", err) } return 1 }, "walk": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("walk: %v", err) } root, err := s.SafeToString(1) if err != nil { return s.PushError("walk: argument must be a string") } var files []string err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return nil // Skip errors } files = append(files, path) return nil }) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } if err := s.PushValue(files); err != nil { return s.PushError("walk: failed to push result: %v", err) } return 1 }, "getcwd": func(s *luajit.State) int { cwd, err := os.Getwd() if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } s.PushString(cwd) return 1 }, "chdir": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("chdir: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("chdir: argument must be a string") } err = os.Chdir(path) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, "file_lines": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("file_lines: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("file_lines: argument must be a string") } data, err := os.ReadFile(path) if err != nil { s.PushNil() s.PushString(err.Error()) return 2 } lines := strings.Split(string(data), "\n") // Remove empty last line if file ends with newline if len(lines) > 0 && lines[len(lines)-1] == "" { lines = lines[:len(lines)-1] } if err := s.PushValue(lines); err != nil { return s.PushError("file_lines: failed to push result: %v", err) } return 1 }, "touch": func(s *luajit.State) int { if err := s.CheckMinArgs(1); err != nil { return s.PushError("touch: %v", err) } path, err := s.SafeToString(1) if err != nil { return s.PushError("touch: argument must be a string") } now := time.Now() err = os.Chtimes(path, now, now) if os.IsNotExist(err) { // Create file if it doesn't exist file, err := os.Create(path) if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } file.Close() } else if err != nil { s.PushBoolean(false) s.PushString(err.Error()) return 2 } s.PushBoolean(true) return 1 }, } }