# tecs.io.files Resolves game paths and performs complete filesystem operations. Use `assetPath` for shipped content, `writablePath` for persistent mutable state, and `cachePath` for data the application can reconstruct. Set the publisher and game names before resolving the first writable path: ```teal local files = tecs.io.files files.setPreferenceIdentity("Ex Nihilo", "Starfarer") local directory = files.writablePath("saves") local ok, reason = files.createDirectory(directory) if not ok then error(reason) end local path = files.writablePath("saves/slot1.json") local bytes = tecs.data.encodeJSON({level = 3, hp = 100}) ok, reason = files.writeAtomic(path, bytes) if not ok then error(reason) end ``` Filesystem outcomes return a status and a platform reason. Invalid arguments raise because they indicate a defect in the calling program. Operations are synchronous except `writeAtomicAsync`, whose worker-backed future is advanced by `tecs.runtime.poll`. `glob` exposes a caller-owned pull stream so recursive enumeration does not retain a complete tree. Temporary files, temporary directories, and streams are [`Closeable`](/modules/#tecs.Closeable) and fit directly in `tecs.scoped`. Every operation that accepts a filesystem location accepts either a string or an immutable [`Path`](/modules/io/Path/). String results remain useful at Lua and platform boundaries; wrap one with `tecs.io.Path.new` when the next operation benefits from component-aware manipulation. `tecs.io.watcher` polls paths recorded by `read` and the built-in asset loaders. It does not walk the whole asset tree. `open` uses the standard Lua modes and returns one seekable file cursor. The SDL backend operates on the file directly. A storage backend without random access falls back to its required whole-file operations and retains the bytes until the file closes. ## Module contents ### Types | Type | Kind | Description | | --- | --- | --- | | [`DirectoryEntry`](/modules/io/files/#tecs.io.files.DirectoryEntry) | record | DirectoryEntry describes one streamed child. | | [`DirectoryStream`](/modules/io/files/#tecs.io.files.DirectoryStream) | record | DirectoryStream is a caller-owned pull cursor over directory entries. | | [`File`](/modules/io/files/#tecs.io.files.File) | interface | File is the seekable cursor returned by open. | | [`FileMode`](/modules/io/files/#tecs.io.files.FileMode) | enum | FileMode selects standard read, write, append, or update behavior. | | [`GlobOptions`](/modules/io/files/#tecs.io.files.GlobOptions) | record | GlobOptions controls pattern matching and traversal depth. | | [`Info`](/modules/io/files/#tecs.io.files.Info) | record | Info describes one resolved path. | | [`LineIterator`](/modules/io/files/#tecs.io.files.LineIterator) | type | LineIterator yields each line in a file and returns nil at the end. | | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | type | PathInput accepts either a platform path string or an immutable path object. | | [`PathType`](/modules/io/files/#tecs.io.files.PathType) | enum | PathType identifies a file, directory or other platform object after following symbolic links. | | [`SymlinkKind`](/modules/io/files/#tecs.io.files.SymlinkKind) | enum | SymlinkKind selects a file or directory symbolic link. | | [`TemporaryOptions`](/modules/io/files/#tecs.io.files.TemporaryOptions) | record | TemporaryOptions selects generated-name details. | | [`TemporaryPath`](/modules/io/files/#tecs.io.files.TemporaryPath) | record | TemporaryPath owns an automatically removed file or directory. | | [`UserFolder`](/modules/io/files/#tecs.io.files.UserFolder) | enum | UserFolder identifies a well-known platform folder. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`assetPath`](/modules/io/files/#tecs.io.files.assetPath) | Static | Resolves relative against the asset root. | | [`assetRoot`](/modules/io/files/#tecs.io.files.assetRoot) | Static | Returns the root from which the engine reads content. | | [`basePath`](/modules/io/files/#tecs.io.files.basePath) | Static | Returns the directory that contains the executable. | | [`cachePath`](/modules/io/files/#tecs.io.files.cachePath) | Static | Returns the system cache directory for this application and creates it if absent. | | [`copy`](/modules/io/files/#tecs.io.files.copy) | Static | Copies the file at from to to, replacing whatever was at to. | | [`createDirectory`](/modules/io/files/#tecs.io.files.createDirectory) | Static | Creates path, and any parents it needs. | | [`createSymlink`](/modules/io/files/#tecs.io.files.createSymlink) | Static | Creates a symbolic link to a file or directory. | | [`createTemporaryDirectory`](/modules/io/files/#tecs.io.files.createTemporaryDirectory) | Static | Creates an empty temporary directory owned by the returned resource. | | [`createTemporaryFile`](/modules/io/files/#tecs.io.files.createTemporaryFile) | Static | Creates an empty temporary file owned by the returned resource. | | [`currentDirectory`](/modules/io/files/#tecs.io.files.currentDirectory) | Static | Returns the process working directory. | | [`exists`](/modules/io/files/#tecs.io.files.exists) | Static | Returns whether anything occupies path. | | [`glob`](/modules/io/files/#tecs.io.files.glob) | Static | Opens a pull stream over entries under path that match pattern. | | [`info`](/modules/io/files/#tecs.io.files.info) | Static | Returns information about path, or nil when nothing occupies it. | | [`isDirectory`](/modules/io/files/#tecs.io.files.isDirectory) | Static | Returns whether path resolves to a directory. | | [`isFile`](/modules/io/files/#tecs.io.files.isFile) | Static | Returns whether path resolves to a regular file. | | [`isSymlink`](/modules/io/files/#tecs.io.files.isSymlink) | Static | Returns whether the final object named by path is a symbolic link. | | [`lines`](/modules/io/files/#tecs.io.files.lines) | Static | Iterates over the lines in a whole file. | | [`load`](/modules/io/files/#tecs.io.files.load) | Static | Compiles a Lua file without running it. | | [`open`](/modules/io/files/#tecs.io.files.open) | Static | Opens a file through one seekable cursor. | | [`preferenceIdentity`](/modules/io/files/#tecs.io.files.preferenceIdentity) | Static | Returns the publisher and game names used for writable user data. | | [`preferencePath`](/modules/io/files/#tecs.io.files.preferencePath) | Static | Returns the writable directory for this application and creates it if absent. | | [`read`](/modules/io/files/#tecs.io.files.read) | Static | Reads a whole file and returns its bytes, or nil when there is none there. | | [`readLink`](/modules/io/files/#tecs.io.files.readLink) | Static | Returns the target spelling stored in a symbolic link. | | [`remove`](/modules/io/files/#tecs.io.files.remove) | Static | Removes a file, or an empty directory. | | [`rename`](/modules/io/files/#tecs.io.files.rename) | Static | Moves from to to, replacing whatever was at to. | | [`setAssetRoot`](/modules/io/files/#tecs.io.files.setAssetRoot) | Static | Overrides the asset root, for a test or a tool. | | [`setPreferenceIdentity`](/modules/io/files/#tecs.io.files.setPreferenceIdentity) | Static | Sets the publisher and game names used for writable user data. | | [`setReadOnly`](/modules/io/files/#tecs.io.files.setReadOnly) | Static | Changes the portable read-only state of a path. | | [`userFolder`](/modules/io/files/#tecs.io.files.userFolder) | Static | Returns one of the platform's well-known folders. | | [`writablePath`](/modules/io/files/#tecs.io.files.writablePath) | Static | Resolves relative against the writable root. | | [`write`](/modules/io/files/#tecs.io.files.write) | Static | Writes bytes to path, replacing whatever was there. | | [`writeAtomic`](/modules/io/files/#tecs.io.files.writeAtomic) | Static | Durably writes bytes and atomically replaces path. | | [`writeAtomicAsync`](/modules/io/files/#tecs.io.files.writeAtomicAsync) | Static | Durably and atomically writes a complete file on a worker. | ## Types ### tecs.io.files.DirectoryEntry record `DirectoryEntry` describes one streamed child. ```teal record tecs.io.files.DirectoryEntry path: Path name: string kind: storagebackend.PathType depth: integer symlink: boolean end ``` #### tecs.io.files.DirectoryEntry.path field Read-only. Contains the complete immutable path to this entry. ```teal tecs.io.files.DirectoryEntry.path: Path ``` #### tecs.io.files.DirectoryEntry.name field Read-only. Contains the entry name relative to its immediate parent. ```teal tecs.io.files.DirectoryEntry.name: string ``` #### tecs.io.files.DirectoryEntry.kind field Read-only. Identifies the resolved object's kind. ```teal tecs.io.files.DirectoryEntry.kind: storagebackend.PathType ``` #### tecs.io.files.DirectoryEntry.depth field Read-only. Reports one for an immediate child and increases during a recursive glob. ```teal tecs.io.files.DirectoryEntry.depth: integer ``` #### tecs.io.files.DirectoryEntry.symlink field Read-only. Reports whether the final path itself is a symbolic link. ```teal tecs.io.files.DirectoryEntry.symlink: boolean ``` ### tecs.io.files.DirectoryStream record `DirectoryStream` is a caller-owned pull cursor over directory entries. ```teal record tecs.io.files.DirectoryStream is Closeable close: function(self): boolean, string next: function(self): DirectoryEntry, string skipDirectory: function(self): boolean toArray: function(self): {DirectoryEntry}, string end ``` #### Interfaces | Interface | | --- | | `Closeable` | #### tecs.io.files.DirectoryStream:close Instance ```teal function tecs.io.files.DirectoryStream.close(self): boolean, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `DirectoryStream` | | ##### Returns | Type | Description | | --- | --- | | `boolean` | | | `string` | | #### tecs.io.files.DirectoryStream:next Instance Returns the next entry. ```teal function tecs.io.files.DirectoryStream.next( self ): DirectoryEntry, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `DirectoryStream` | The open stream to advance. | ##### Returns | Type | Description | | --- | --- | | [`DirectoryEntry`](/modules/io/files/#tecs.io.files.DirectoryEntry) | Returns the next entry, or nil at the end or after close. | | `string` | Returns a platform reason when traversal fails. Nil means end of stream. | #### tecs.io.files.DirectoryStream:skipDirectory Instance Prevents descent into the directory returned by the preceding `next` call. The method returns false when the preceding entry was not a directory, was a symbolic link, had no deeper pattern component, or has already been skipped. Calling `next` first makes an earlier directory no longer eligible for pruning. ```teal function tecs.io.files.DirectoryStream.skipDirectory(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `DirectoryStream` | The open stream whose pending descent the caller controls. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether a pending directory descent was removed. | #### tecs.io.files.DirectoryStream:toArray Instance Consumes the remaining entries into an array and closes the stream. Entries returned by earlier `next` calls are not repeated. A traversal failure discards the partial array and closes the stream. ```teal function tecs.io.files.DirectoryStream.toArray( self ): {DirectoryEntry}, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `DirectoryStream` | The open stream to consume. | ##### Returns | Type | Description | | --- | --- | | `{`[`DirectoryEntry`](/modules/io/files/#tecs.io.files.DirectoryEntry)`}` | Returns every remaining entry, or nil when traversal fails. | | `string` | Returns the platform reason when the first return is nil. | ### tecs.io.files.File interface `File` is the seekable cursor returned by `open`. ```teal interface tecs.io.files.File is types.SeekableReader, types.SeekableWriter end ``` #### Interfaces | Interface | | --- | | [`types.SeekableReader`](/modules/io/#tecs.io.SeekableReader) | | [`types.SeekableWriter`](/modules/io/#tecs.io.SeekableWriter) | ### tecs.io.files.FileMode enum `FileMode` selects standard read, write, append, or update behavior. ```teal enum tecs.io.files.FileMode "a" "a+" "r" "r+" "w" "w+" end ``` ### tecs.io.files.GlobOptions record `GlobOptions` controls pattern matching and traversal depth. ```teal record tecs.io.files.GlobOptions caseInsensitive: boolean maxDepth: integer end ``` #### tecs.io.files.GlobOptions.caseInsensitive field Caller-writable. Matches ASCII letters without regard to case when true. Defaults to false, which compares every UTF-8 codepoint exactly on every platform including the ones whose filesystem is not. ```teal tecs.io.files.GlobOptions.caseInsensitive: boolean ``` #### tecs.io.files.GlobOptions.maxDepth field Caller-writable. Limits traversal to this many levels below the root. Zero yields no entries, one yields immediate children, and nil permits unlimited traversal. Defaults to nil. ```teal tecs.io.files.GlobOptions.maxDepth: integer ``` ### tecs.io.files.Info record `Info` describes one resolved path. ```teal record tecs.io.files.Info kind: PathType size: integer createdAt: number modifiedAt: number accessedAt: number readOnly: boolean end ``` #### tecs.io.files.Info.kind field Read-only. Identifies the resolved platform object's kind. ```teal tecs.io.files.Info.kind: PathType ``` #### tecs.io.files.Info.size field Read-only. Reports size in bytes. Zero for a directory. ```teal tecs.io.files.Info.size: integer ``` #### tecs.io.files.Info.createdAt field Read-only. Reports nanoseconds since the epoch as a double. Zero means the platform or filesystem does not record a creation time. ```teal tecs.io.files.Info.createdAt: number ``` #### tecs.io.files.Info.modifiedAt field Read-only. Reports modification time in nanoseconds since the epoch. ```teal tecs.io.files.Info.modifiedAt: number ``` #### tecs.io.files.Info.accessedAt field Read-only. Reports access time in nanoseconds since the epoch. ```teal tecs.io.files.Info.accessedAt: number ``` #### tecs.io.files.Info.readOnly field Read-only. Reports the portable read-only state of the resolved path. ```teal tecs.io.files.Info.readOnly: boolean ``` ### tecs.io.files.LineIterator type `LineIterator` yields each line in a file and returns nil at the end. ```teal type tecs.io.files.LineIterator = function(): string ``` ### tecs.io.files.PathInput type `PathInput` accepts either a platform path string or an immutable path object. ```teal type tecs.io.files.PathInput = string | Path ``` ### tecs.io.files.PathType enum `PathType` identifies a file, directory or other platform object after following symbolic links. ```teal enum tecs.io.files.PathType "directory" "file" "other" end ``` ### tecs.io.files.SymlinkKind enum `SymlinkKind` selects a file or directory symbolic link. ```teal enum tecs.io.files.SymlinkKind "directory" "file" end ``` ### tecs.io.files.TemporaryOptions record `TemporaryOptions` selects generated-name details. ```teal record tecs.io.files.TemporaryOptions directory: string | Path prefix: string suffix: string end ``` #### tecs.io.files.TemporaryOptions.directory field Caller-writable. Selects the parent directory. Nil uses the operating system's temporary directory. ```teal tecs.io.files.TemporaryOptions.directory: string | Path ``` #### tecs.io.files.TemporaryOptions.prefix field Caller-writable. Prefixes the generated name. Defaults to `"tecs-"`. ```teal tecs.io.files.TemporaryOptions.prefix: string ``` #### tecs.io.files.TemporaryOptions.suffix field Caller-writable. Suffixes the generated name. Defaults to an empty string. ```teal tecs.io.files.TemporaryOptions.suffix: string ``` ### tecs.io.files.TemporaryPath record `TemporaryPath` owns an automatically removed file or directory. ```teal record tecs.io.files.TemporaryPath is Closeable path: Path close: function(self): boolean, string persist: function(self, destination: string | Path): boolean, string end ``` #### Interfaces | Interface | | --- | | `Closeable` | #### tecs.io.files.TemporaryPath.path field Read-only. Contains the exclusively created path. ```teal tecs.io.files.TemporaryPath.path: Path ``` #### tecs.io.files.TemporaryPath:close Instance ```teal function tecs.io.files.TemporaryPath.close(self): boolean, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `TemporaryPath` | | ##### Returns | Type | Description | | --- | --- | | `boolean` | | | `string` | | #### tecs.io.files.TemporaryPath:persist Instance Moves the resource to a permanent, absent destination and relinquishes cleanup. ```teal function tecs.io.files.TemporaryPath.persist( self, destination: string | Path ): boolean, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `TemporaryPath` | The open temporary resource. | | `destination` | string | [`Path`](/modules/io/Path/) | The caller supplies a destination which must not exist. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the resource became permanent. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.UserFolder enum `UserFolder` identifies a well-known platform folder. ```teal enum tecs.io.files.UserFolder "desktop" "documents" "downloads" "home" "music" "pictures" "publicShare" "savedGames" "screenshots" "templates" "videos" end ``` ## Functions ### tecs.io.files.assetPath Static Resolves `relative` against the asset root. ```teal function tecs.io.files.assetPath(relative: PathInput): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `relative` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies a path under the content root with `/` separators. The function joins it without checking existence. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the absolute path. | ### tecs.io.files.assetRoot Static Returns the root from which the engine reads content. ```teal function tecs.io.files.assetRoot(): string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns `TECS_ASSETS` when set, otherwise the host-configured root or `basePath`. The value stays cached until the platform changes. | ### tecs.io.files.basePath Static Returns the directory that contains the executable. ```teal function tecs.io.files.basePath(): string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns the directory with a trailing separator. It returns an empty string rather than nil when the platform declines to say, which is the case on some targets and is not an error: a caller falls back to a relative path. | ### tecs.io.files.cachePath Static Returns the system cache directory for this application and creates it if absent. The operating system may empty this directory between any two runs. Store only reconstructable downloads, compiled artifacts, thumbnails and similar derived data here. Saves, settings and other durable state belong under `preferencePath`. ```teal function tecs.io.files.cachePath(): string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns the directory with a trailing platform separator, named from the preference identity. | ### tecs.io.files.copy Static Copies the file at `from` to `to`, replacing whatever was at `to`. The function refuses directories and requires an existing destination parent. ```teal function tecs.io.files.copy( from: PathInput, to: PathInput ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `from` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the existing source file. | | `to` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the destination file to replace. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform copied the file. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.createDirectory Static Creates `path`, and any parents it needs. It succeeds when the directory already exists. ```teal function tecs.io.files.createDirectory(path: PathInput): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the directory to create. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform created or found the directory. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.createSymlink Static Creates a symbolic link to a file or directory. The target spelling is stored unchanged, so a relative target is resolved from the link's parent when later opened. `kind` is required because Windows must choose a file or directory link even for a dangling target. ```teal function tecs.io.files.createSymlink( target: PathInput, link: PathInput, kind: SymlinkKind ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `target` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the target spelling to store. | | `link` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the new link path. | | `kind` | [`SymlinkKind`](/modules/io/files/#tecs.io.files.SymlinkKind) | The caller supplies `"file"` or `"directory"`. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform created the link. | | `string` | Returns the platform or unsupported reason when the first return is false. | ### tecs.io.files.createTemporaryDirectory Static Creates an empty temporary directory owned by the returned resource. Closing recursively removes its contents without following symbolic links. Call `persist` to keep the complete tree under an absent destination. ```teal function tecs.io.files.createTemporaryDirectory( options: TemporaryOptions ): TemporaryPath, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`TemporaryOptions`](/modules/io/files/#tecs.io.files.TemporaryOptions) | The caller may select the parent, prefix, and suffix. | #### Returns | Type | Description | | --- | --- | | [`TemporaryPath`](/modules/io/files/#tecs.io.files.TemporaryPath) | Returns the caller-owned temporary directory. | | `string` | Returns the platform or unsupported reason when creation fails. | ### tecs.io.files.createTemporaryFile Static Creates an empty temporary file owned by the returned resource. Creation is exclusive. Closing removes the file; garbage collection is a leak safety net, while [`tecs.scoped`](/modules/#tecs.scoped) provides deterministic cleanup. Call `persist` to keep the file under an absent permanent destination. ```teal function tecs.io.files.createTemporaryFile( options: TemporaryOptions ): TemporaryPath, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`TemporaryOptions`](/modules/io/files/#tecs.io.files.TemporaryOptions) | The caller may select the parent, prefix, and suffix. | #### Returns | Type | Description | | --- | --- | | [`TemporaryPath`](/modules/io/files/#tecs.io.files.TemporaryPath) | Returns the caller-owned temporary file. | | `string` | Returns the platform or unsupported reason when creation fails. | ### tecs.io.files.currentDirectory Static Returns the process working directory. Command-line tools receive relative paths and resolve them against this directory. It is not where a game reads content from or writes state to: `assetRoot`, `preferencePath` and `cachePath` answer that, and say why. A platform with no working directory answers nil, which is most of the ones that are not a desktop. ```teal function tecs.io.files.currentDirectory(): string, string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns the directory with a trailing separator, or nil when the platform has no working directory. | | `string` | Returns the platform reason when the first return is nil. | ### tecs.io.files.exists Static Returns whether anything occupies `path`. ```teal function tecs.io.files.exists(path: PathInput): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute or platform-resolvable path. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when the platform finds any object. | ### tecs.io.files.glob Static Opens a pull stream over entries under `path` that match `pattern`. **Recursive unless the pattern stops it**: with no pattern this walks the whole tree below `path`. `*` and `?` never match a separator, and `?` consumes one UTF-8 codepoint, so `"*"` is one level, `"*/*"` is exactly two, and `"*.png"` matches only immediate children. Results include directories alongside files. Symbolic links are yielded but never followed. The order is the platform's own and is not sorted. The stream retains only the unvisited names in each directory on its current descent. Close it when leaving early. After receiving a directory, call `skipDirectory` before `next` to prune that complete subtree. Call `toArray` to consume and close the stream when every remaining entry should be retained. A fixed bound belongs in `maxDepth`; `skipDirectory` remains useful when descent depends on the entry itself. The checked example combines both. ```teal function tecs.io.files.glob( path: PathInput, pattern: string, options: GlobOptions ): DirectoryStream, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the directory to enumerate. | | `pattern` | `string` | The caller supplies `/`-separated `*` and `?` wildcards, or nil to include every descendant recursively. | | `options` | [`GlobOptions`](/modules/io/files/#tecs.io.files.GlobOptions) | The caller supplies matching and depth options, or omits them for case-sensitive, unlimited traversal. | #### Returns | Type | Description | | --- | --- | | [`DirectoryStream`](/modules/io/files/#tecs.io.files.DirectoryStream) | Returns a caller-owned stream, or nil when enumeration cannot start. | | `string` | Returns the platform reason when the first return is nil. | #### Examples ```teal local files = tecs.io.files tecs.scoped(function(scope: tecs.Scope) local stream, reason = files.glob( files.assetPath("levels"), nil, {maxDepth = 2} ) if stream == nil then error(reason) end scope:own(stream) while true do local entry, nextReason = stream:next() if entry == nil then if nextReason ~= nil then error(nextReason) end break end print(entry.depth, entry.path) if entry.kind == "directory" and entry.name == "generated" then stream:skipDirectory() end end end) ``` ### tecs.io.files.info Static Returns information about `path`, or nil when nothing occupies it. Nil is the answer for a path that does not exist, for a broken symbolic link, and when the platform cannot search a directory; the second return separates them. ```teal function tecs.io.files.info(path: PathInput): Info, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute or platform-resolvable path. | #### Returns | Type | Description | | --- | --- | | [`Info`](/modules/io/files/#tecs.io.files.Info) | Returns the path information, or nil when unavailable. | | `string` | Returns the platform reason when the first return is nil. | ### tecs.io.files.isDirectory Static Returns whether `path` resolves to a directory. ```teal function tecs.io.files.isDirectory(path: PathInput): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute or platform-resolvable path. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when the resolved object is a directory. | ### tecs.io.files.isFile Static Returns whether `path` resolves to a regular file. ```teal function tecs.io.files.isFile(path: PathInput): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute or platform-resolvable path. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when the resolved object is a regular file. | ### tecs.io.files.isSymlink Static Returns whether the final object named by `path` is a symbolic link. Unlike `info`, this function does not follow the final link. It does follow links in parent directories, so the result is path introspection and not a security boundary. A path that is absent or cannot be inspected returns false. ```teal function tecs.io.files.isSymlink(path: PathInput): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute or platform-resolvable path. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when the final path itself is a symbolic link. | ### tecs.io.files.lines Static Iterates over the lines in a whole file. The function reads and closes the file before it returns the iterator, so breaking the loop retains only the immutable bytes and no file descriptor. It strips LF and CRLF terminators, returns empty lines between adjacent terminators, and does not invent another empty line after a final terminator. Use `open` when the input is too large to hold whole. ```teal function tecs.io.files.lines(path: PathInput): LineIterator, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the source file. | #### Returns | Type | Description | | --- | --- | | [`LineIterator`](/modules/io/files/#tecs.io.files.LineIterator) | Returns a [`LineIterator`](/modules/io/files/#tecs.io.files.LineIterator) that yields each line in order, or nil when the platform cannot read the file. | | `string` | Returns the failure reason when the first return is nil. | #### Examples ```teal local files = require("tecs.io.files") local nextLine = assert(files.lines("save.txt")) for line in nextLine do print(line) end ``` ### tecs.io.files.load Static Compiles a Lua file without running it. The function reads through the installed storage backend rather than `loadfile`, records the source for file watching, and uses `@path` as the compiler's chunk name. The default environment is the caller's global environment. A supplied environment is installed exactly as given and is not a security boundary unless the caller makes it one. ```teal function tecs.io.files.load( path: PathInput, environment: {string: any} ): function(...: any): any..., string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the Lua source file. | | `environment` | `{string : any}` | The caller supplies the globals visible when the chunk runs, or omits them to use the process globals. | #### Returns | Type | Description | | --- | --- | | `function(...: any): any...` | Returns the compiled chunk, or nil when reading or compilation fails. Calling the returned function executes the file. | | `string` | Returns the read or compiler reason when the first return is nil. | ### tecs.io.files.open Static Opens a file through one seekable cursor. The mode follows Lua's standard file modes. `"r"` opens an existing file for reading and is the default. `"w"` creates or truncates for writing. `"a"` creates or preserves for writes that always land at the end. Adding `+` permits both reading and writing. The cursor starts at byte zero except for `"a"`; `"a+"` starts at byte zero for reading while every write still lands at the current end. Seeks may not move past the end. Close the file even after its last write succeeds. `close` flushes buffered bytes and can report a delayed storage failure. A backend without direct random access retains the complete file until close. ```teal function tecs.io.files.open( path: PathInput, mode: FileMode ): File, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the file as a string or immutable [`Path`](/modules/io/Path/). | | `mode` | [`FileMode`](/modules/io/files/#tecs.io.files.FileMode) | The caller selects `"r"`, `"w"`, `"a"`, `"r+"`, `"w+"`, or `"a+"`, or omits it for `"r"`. | #### Returns | Type | Description | | --- | --- | | [`File`](/modules/io/files/#tecs.io.files.File) | Returns a caller-owned [`File`](/modules/io/files/#tecs.io.files.File), or nil when the platform cannot open it. | | `string` | Returns the platform reason when the first return is nil. | #### Examples ```teal local files = require("tecs.io.files") local file, reason = files.open("world.pack", "w+") if file == nil then error(reason) end local wrote, writeReason = file:write("HEADpayload bytes") if not wrote then file:close() error(writeReason) end local position, seekReason = file:seek("start") if position == nil then file:close() error(seekReason) end local header = assert(file:read(4)) assert(header == "HEAD") local closed, closeReason = file:close() assert(closed, closeReason) ``` ### tecs.io.files.preferenceIdentity Static Returns the publisher and game names used for writable user data. The pair starts as `"tecs", "tecs"` and changes when `setPreferenceIdentity` succeeds. ```teal function tecs.io.files.preferenceIdentity(): string, string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns the current publisher, studio, or organization name. | | `string` | Returns the current game or application name. | ### tecs.io.files.preferencePath Static Returns the writable directory for this application and creates it if absent. ```teal function tecs.io.files.preferencePath(): string ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `string` | Returns the directory with a trailing separator, named from the preference identity. This is where a build writes durable state; `cachePath` is the other writable root and holds only reconstructable data. | ### tecs.io.files.read Static Reads a whole file and returns its bytes, or nil when there is none there. The binary string retains embedded NUL bytes. The synchronous call suits small documents; use `tecs.assets.loadString` when a whole-file read should run off the main thread, or its image and sound loaders for decoding. ```teal function tecs.io.files.read(path: PathInput, kind: string): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies an absolute path or one from `assetPath`. | | `kind` | `string` | The caller supplies the content kind for watching, or omits it to record a document. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the file bytes, or nil when the platform cannot read them. | ### tecs.io.files.readLink Static Returns the target spelling stored in a symbolic link. A relative target remains relative. The function does not resolve it against the link's parent and fails when `path` is not a symbolic link. ```teal function tecs.io.files.readLink(path: PathInput): Path, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the symbolic link to inspect. | #### Returns | Type | Description | | --- | --- | | [`Path`](/modules/io/Path/) | Returns the stored target as an immutable [`Path`](/modules/io/Path/). | | `string` | Returns the platform or unsupported reason when the first return is nil. | ### tecs.io.files.remove Static Removes a file, or an empty directory. It does not recurse and fails on a directory with anything in it. It **succeeds when there is nothing at `path`**: the result says the path is gone, not that this call is what removed it. Emptying a tree requires a glob and a deepest-first loop: local stream = assert(files.glob(root)) local paths = {} while true do local entry, reason = stream:next() if entry == nil then if reason ~= nil then error(reason) end break end paths[#paths + 1] = entry.path end stream:close() table.sort(paths, function(a, b) return #(a:toString()) > #(b:toString()) end) for _, path in ipairs(paths) do files.remove(path) end files.remove(root) ```teal function tecs.io.files.remove(path: PathInput): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the file or empty directory to remove. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true when nothing remains at `path`. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.rename Static Moves `from` to `to`, replacing whatever was at `to`. Directories move as well as files, and an existing destination is overwritten with no warning and nothing to undo it. Whether this works across filesystems is the platform's business. ```teal function tecs.io.files.rename( from: PathInput, to: PathInput ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `from` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the existing source path. | | `to` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the destination path to replace. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform moved the path. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.setAssetRoot Static Overrides the asset root, for a test or a tool. ```teal function tecs.io.files.setAssetRoot(root: PathInput) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `root` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the new content directory. The function adds a trailing separator only when needed. | #### Returns None. ### tecs.io.files.setPreferenceIdentity Static Sets the publisher and game names used for writable user data. SDL combines these values with the current user and platform conventions to choose the directory returned by `preferencePath`. The defaults are `"tecs"` and `"tecs"`. Later calls change which directory the next `preferencePath`, `writablePath`, or `cachePath` resolves. ```teal function tecs.io.files.setPreferenceIdentity( organization: string, application: string ) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `organization` | `string` | The caller supplies a non-empty publisher, studio, or organization name. | | `application` | `string` | The caller supplies a non-empty game or application name. | #### Returns None. ### tecs.io.files.setReadOnly Static Changes the portable read-only state of a path. On Unix, making a path read-only clears every write bit and making it writable restores only the owner's write bit. The API deliberately does not model platform ACLs, ownership, or executable bits. ```teal function tecs.io.files.setReadOnly( path: PathInput, readOnly: boolean ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the existing path to change. | | `readOnly` | `boolean` | The caller supplies true to prevent ordinary writes. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform changed the state. | | `string` | Returns the platform or unsupported reason when the first return is false. | ### tecs.io.files.userFolder Static Returns one of the platform's well-known folders. **Often nil, and legitimately so.** A platform that has no such concept says so rather than inventing a path: macOS has no saved-games, screenshots or templates folder and answers nil for all three, and a platform that is not a desktop may have none of them. Treat every one of these as absent until it is not, and never as a place a build may write; `preferencePath` and `cachePath` are the two writable roots, with different durability. ```teal function tecs.io.files.userFolder(which: UserFolder): string, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `which` | [`UserFolder`](/modules/io/files/#tecs.io.files.UserFolder) | The caller supplies the well-known folder to resolve. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the folder with a trailing separator, or nil when the platform has no matching folder. | | `string` | Returns the platform reason when the first return is nil. | ### tecs.io.files.writablePath Static Resolves `relative` against the writable root. ```teal function tecs.io.files.writablePath(relative: PathInput): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `relative` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies a path under the preference directory. | #### Returns | Type | Description | | --- | --- | | `string` | Returns the absolute path. | ### tecs.io.files.write Static Writes `bytes` to `path`, replacing whatever was there. The string's length controls the binary write, so embedded NUL bytes remain data and an empty string creates an empty file. The parent directory must already exist. ```teal function tecs.io.files.write( path: PathInput, bytes: string ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the destination file. | | `bytes` | `string` | The caller supplies the complete binary contents. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform wrote the complete file. | | `string` | Returns the platform reason when the first return is false. | ### tecs.io.files.writeAtomic Static Durably writes `bytes` and atomically replaces `path`. The platform writes a uniquely named temporary file beside `path`, commits its complete contents to durable storage, atomically replaces the destination, and commits the containing directory where the operating system provides that operation. An existing destination remains untouched when anything fails before replacement, and no temporary file is retained. A failure after replacement means the complete new file is visible but the platform could not confirm that the directory update will survive a power loss. The function returns false in that case rather than overstating durability. Hardware can still violate an operating system's completed flush request. A storage backend without an atomic durable commit returns false and an unsupported reason. The function never falls back to `write` plus `rename`. The parent directory must already exist. ```teal function tecs.io.files.writeAtomic( path: PathInput, bytes: ByteInput ): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the destination file. | | `bytes` | `ByteInput` | The caller supplies a string or open [`Buffer`](/modules/io/#tecs.io.Buffer) or [`ByteView`](/modules/io/#tecs.io.ByteView) containing the complete binary contents. Buffers and views are borrowed only for this synchronous call and are not copied or closed. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the platform confirmed the atomic durable write. | | `string` | Returns the platform or unsupported reason when the first return is false. | #### Examples ```teal local save = tecs.io.Path.new( tecs.io.files.writablePath("saves"), "slot1.bin" ) local ok, reason = tecs.io.files.writeAtomic(save, "snapshot bytes") if not ok then error(reason) end ``` ### tecs.io.files.writeAtomicAsync Static Durably and atomically writes a complete file on a worker. The returned [`Future`](/modules/Future/) becomes ready with true after the same commit performed by `writeAtomic`, or fails with its platform reason. The call copies a buffer or byte view before returning, so the caller may mutate or close it immediately. String bytes cross the worker channel by value as well. `tecs.runtime.poll` advances the future each frame; `Future:wait` advances it in headless code. Canceling the future stops interest in its result but cannot safely interrupt a filesystem commit already in progress. The shared worker exits automatically after its last queued result is drained. A non-SDL storage backend receives a failed future because a separate worker state cannot inherit the main state's installed Lua backend. ```teal function tecs.io.files.writeAtomicAsync( path: PathInput, bytes: ByteInput ): Future ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `path` | [`PathInput`](/modules/io/files/#tecs.io.files.PathInput) | The caller supplies the destination file. | | `bytes` | `ByteInput` | The caller supplies the complete binary contents. | #### Returns | Type | Description | | --- | --- | | [`Future`](/modules/Future/)`` | Returns a future which becomes ready with true or fails. |