{"id":17059093,"url":"https://github.com/toxicfrog/luautil","last_synced_at":"2025-04-12T17:51:49.253Z","repository":{"id":21078169,"uuid":"24377835","full_name":"ToxicFrog/luautil","owner":"ToxicFrog","description":"A convenient utility library for Lua used by some of my other projects.","archived":false,"fork":false,"pushed_at":"2024-03-17T16:38:07.000Z","size":93,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-26T12:11:50.098Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ToxicFrog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-23T15:42:38.000Z","updated_at":"2024-03-20T23:25:38.000Z","dependencies_parsed_at":"2022-08-02T10:40:37.273Z","dependency_job_id":null,"html_url":"https://github.com/ToxicFrog/luautil","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ToxicFrog%2Fluautil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ToxicFrog%2Fluautil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ToxicFrog%2Fluautil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ToxicFrog%2Fluautil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ToxicFrog","download_url":"https://codeload.github.com/ToxicFrog/luautil/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248610407,"owners_count":21132920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T10:32:35.097Z","updated_at":"2025-04-12T17:51:49.231Z","avatar_url":"https://github.com/ToxicFrog.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Contents ##\n\n1. Introduction\n2. Modules\n  1. Global Functions -- misc.lua\n  2. IO -- io.lua\n  3. LuaFilesystem -- lfs.lua\n  4. Strings -- string.lua\n  5. Math -- math.lua\n  6. Tables and Lists -- table.lua\n  7. Command Line Flag Parsing -- flags.lua\n  8. Logging -- logging.lua\n3. License\n\n\n## 1. Introduction ##\n\nThis is a collection of utility functions for Lua that I use in some of my programs. You may find it useful.\n\nIt is organized into a number of modules; these modules are independent of each other, so you can load only the ones you need.\n\nNote that this library does various bad things such as *modifying the metatables for built in types*, *overwriting builtin functions* and *unconditionally creating new globals*. There is currently no way to disable this behaviour except by not loading the modules that do these things.\n\n\n## 2. Modules ##\n\nThis library is organized into a number of individual modules. None of them have any additional dependencies, although some enable additional features if certain other libraries (such as lfs) are loaded first. You can also load the entire library at once via `init.lua`.\n\n\n### 2.1. Global Functions -- misc.lua ###\n\nThis module creates a number of global functions that don't belong in any other module. It also overrides some existing functions.\n\n-------\n\n    assertf(exp, err, ...)\n\nAs `assert(exp, err:format(...))`. This isn't particularly useful for turning 'soft errors' into 'hard errors' as in `assertf(io.read(...))`, because the error returned by the call may not be format-clean; it's mainly intended for runtime checks, as in `assertf(precondition, err, ...)`. (Perhaps it should be called `check()` instead?)\n\n-------\n\n    error(err, ...)\n\nAs stock `error`, but if the `...` is nonempty, calls `err:format(...)` and then throws the result.\n\n-------\n\n    f(body)\n\nConcise creation of simple anonymous functions. `f \"x =\u003e not x\"` is converted into `function(x) return not x end`. Note that this uses `loadstring` and thus the resulting function is *not* lexically scoped inside `f`'s caller; in particular, this means that it has no upvalues.\n\n-------\n\n    getmetafield(v, k)\n\nAs `getmetatable(v)[k]`, except that it returns `nil` rather than throwing if `v` has no metatable.\n\n-------\n\n    partial(f, ...)\n\nReturn a function based on f with the given arguments already applied, such that `partial(f, x)(...)` is equivalent to `f(x, ...)`.\n\n-------\n\n    pairs(v)\n    ipairs(v)\n\nThese are defined only if running under Lua 5.1, and are equivalent to the Lua 5.2 versions that respect the `__pairs` and `__ipairs` metamethods.\n\n-------\n\n    srequire(module)\n\n\"Safe require\". Equivalent to `require`, but returns `nil,error` on failure rather than throwing.\n\n-------\n\n    toboolean(v)\n\nReturns the truth-value of `v`; this is `false` for false or nil and `true` for everything else (including the empty string, the empty table, and 0).\n\n-------\n\n    type(v)\n\nIdentical to the builtin, except that if v has a __type metamethod, it calls that instead and returns the result. Original type is still available as `rawtype`.\n\n\n### 2.2. IO -- io.lua ###\n\nThis module modifies the `io` library by adding new functions, and modifies the `__index` for file objects so that `:printf` is callable on them as a method.\n\n-------\n\n    eprintf(fmt, ...)\n\nEquivalent to `io.stderr:printf(fmt, ...)`.\n\n-------\n\n    file:printf(...)\n\nEquivalent to `file:write(string.format(...))`.\n\n-------\n\n    io.exists(file, [mode])\n\nChecks if `file` can be opened with the given `mode` (default `'r'`). This is not as good as the lfs version -- in particular, if the file exists but you cannot open it in any mode due to permissions, this function can never return true -- but it doesn't require LFS to be installed.\n\n-------\n\n    io.memfile([string])\n\nCreates and returns a file object backed by a string rather than a file on disk. `string` is the initial contents of the memfile; if not specified, `\"\"` is used.\n\nMemfiles behave equivalently to normal files, with the following exceptions:\n\n * `memfile:write()` and `memfile:read()` only take a single argument\n * `memfile:setvbuf()` does nothing; memfiles are always fully buffered\n * `memfile:close()` returns the contents of the memfile and renders it unusable for further calls\n * A new method is added, `memfile:str()`, which returns the contents as a string\n\nThese are generally not as fast as real files (which have C implementations of their methods and are backed by optimized system calls and the system block cache). In particular, if you have a real file, reading it all into memory and then turning it into a memfile will get you *worse* performance than just accessing it directly. It is intended for use when you need a file-like API, but either can't use a file, or value the convenience of not needing one over raw performance.\n\nNote that you can't use memfiles to pass to `io.output()` or `io.input()`; the lua standard library typechecks those and only accepts real files.\n\n-------\n\n    io.readfile(path)\n\nReads and returns the contents of the file `path`. Raises an error on failure.\n\n-------\n\n    io.writefile(path, data)\n\nWrites `data` to the file `path`. The file is created if it didn't already exist, and overwritten if it did. Raises an error on failure.\n\n-------\n\n    printf(fmt, ...)\n\nEquivalent to `io.output():printf(fmt, ...)`.\n\n### 2.3. LuaFilesystem -- lfs.lua ###\n\nThis module contains extension for the LuaFilesystem library, which it assumes is present in the global `lfs`. It is only loaded through `init.lua` if `lfs` is already available; if you load it directly, you are responsible for making sure `lfs` is loaded first!\n\nIt modifies the `lfs` table, and wraps the `lfs.attributes` function.\n\n-------\n\n    lfs.normalize(path)\n\nOn windows, returns the path with all backslashes (`\\`) replaced with forward slashes (`/`). On not-windows, a no-op.\n\n-------\n\n    lfs.attributes(path, ...)\n\nEquivalent to the original function, except that it runs `path` through `lfs.normalize`. On windows, it additionally appends a `.` to the path if the path ends with `/`, to work around a bug on windows where it will fail on paths with trailing slashes even if they reference a directory.\n\n-------\n\n    lfs.exists(path)\n\nReturns true if the named file or directory exists, and false otherwise. A convenience function for `lfs.attributes(path, 'mode') ~= nil`.\n\n-------\n\n    lfs.dirname(path)\n\nAs the POSIX utility `dirname`, returns `path` with the trailing path element removed.\n\n-------\n\n    lfs.rmkdir(path)\n\nRecursive mkdir, akin to `mkdir -p`. Unlike `lfs.mkdir`, this will create any missing intermediate directories, and will succeed even if the destination directory already exists.\n\n\n### 2.4. Strings -- string.lua ###\n\nThis adds several functions to the `string` table, and also modifies the metatable for strings by adding a `__mod` metamethod. As with the standard string library, all of these functions are available as methods on individual strings as well.\n\n-------\n\n    str:tonumber(...)\n\nEquivalent to `tonumber(str, ...)`.\n\n-------\n\n    str % a % b % c\n    str % { ... }\n\nEquivalent to `str:format(a,b,c)` and `str:format(...)` respectively. The former is faster to type but has some edge cases related to `%%` escapes that it doesn't handle properly, so using the latter is recommended with complicated format strings.\n\n------\n\n    string.count(s, pattern)\n\nReturns the number of (non-overlapping) ocurrences of pattern in string.\n\n-------\n\n    string.interpolate(s, data)\n\nSearches `s` for sequences of the form `${key}` or `${key|format}` and recursively expands them. This is effectively the same as replacing `${key}` with the result of `tostring(data['key'])`, and `${key|format}` with the result of `string.format(format, data['key'])`, except that any `${...}` sequences in `key` itself will be expanded first, and any `${...}` sequences in the result of the expansion will themselves be expanded.\n\n    \u003e str = \"The Lua version is ${ver}, and I am currently using ${mem|%d}KB of memory.\"\n    \u003e print(str:interpolate { ver = _VERSION, mem = collectgarbage 'count' })\n    The Lua version is Lua 5.1, and I am currently using 22KB of memory.\n\n-------\n\n    string.join(separator, ...)\n\nEquivalent to table.concat( {...}, separator)\n\n-------\n\n    string.rfind(s, pattern, init, plain)\n\nEquivalent to string.find(), but finds the -last- occurence of the pattern in the string (ie, searches in reverse). Init can be either positive or negative and has the same meaning as in other string functions in either case.\n\n-------\n\n    string.split(s, pattern)\n\nSplits s into multiple substrings by removing all occurrences of pattern, and returns these substrings. For example, the call:\n\n    string.split(\"one two    three  \", \"%s+\")\n\nwould return \"one\",\"two\",\"three\". Note that there is no enclosing table; the results are returned directly.\n\nThe default for `pattern` is `%s+`, i.e. runs of whitespace; the default for `max` is infinity.\n\nNote that directly adjacent separators will be considered to separate the empty string, including separators appearing at the start and end of input; for example, `string.split(';;', ';')` will return `'','',''`, three empty strings -- the ones before, between, and after the separators.\n\n\n-------\n\n    string.trim(s)\n\nReturns a copy of s with all leading and trailing whitespace removed.\n\n-------\n\n    string.wrap(s, cols)\n\nWrap `s` to fit within `cols` columns; returns a table of individual lines. It inserts linebreaks only on whitespace or hyphens, so text containing very long words may not wrap well, resulting in lines that are still longer than `cols`.\n\n### 2.5. Math -- math.lua ###\n\nA few convenience functions, all added to the global `math` table.\n\nAdditionally, it sets `math` as the `__index` for all numbers, so expressions like `x:floor()` will work, and sets math.huge as the value of the global `inf`.\n\n-------\n\n    math.bin(n)\n    math.oct(n)\n    math.hex(n)\n\nAs `tonumber(n, 2)`, `tonumber(n, 8)` and `tonumber(n, 16)` respectively.\n\n-------\n\n    math.dsin(deg)\n    math.dcos(deg)\n    math.dtan(deg)\n\nVersions of `sin`, `cos` and `tan` that take arguments in degrees rather than in radians.\n\n-------\n\n    math.dasin(sin)\n    math.dacos(cos)\n    math.datan(tan)\n    math.datan2(tan)\n\nVersions of `asin`, `acos`, `atan`, and `atan2` that return values in degrees rather than in radians.\n\n-------\n\n    math.bound(n, min, max)\n\nIf `min` ≤ `n` ≤ `max`, returns `n`. Otherwise returns whichever of `min` or `max` is closer to `n`.\n\n### 2.6. Tables and Lists -- table.lua ###\n\n    table.copy(table, depth)\n\nReturns a copy of table, duplicated down to depth. Beyond depth, subtables are copied by reference rather than duplicated. Other objects are copied by reference or by value according to the = operator. Note that metatables are not copied; any resulting tables will be metatable-less.\n\nOmitting depth is equivalent to specifying math.huge. A depth of 0 causes it to return table without copying anything.\n\n--------\n\n    table.dump(t)\n\nReturns a string that, when loaded (with `loadstring()` or similar), produces a function that when called returns a copy of `t`. The serialization is not perfect; in particular:\n\n  * any key-value pair which contains a userdata or coroutine will be cleared\n  * upvalues will be nil once serialized closures are deserialized\n\nTo save and load a table, you would do something like this:\n\n    saved = table.dump(T)\n    -- maybe you save it to a file now and read it back later or something\n    T = loadstring(saved)()\n\nNote that since this outputs executable Lua code, you should use it only in circumstances where people tampering with the output is not a concern. In particular, you probably shouldn't use this for network communications, or for configuration files for setuid programs, or the like.\n\n--------\n\n    table.keys(t)\n\nReturns a list of all the keys in `t`, in unspecified order.\n\n--------\n\n    table.map(t, f)\n\nIterates `t` using `ipairs`, calling `f(v)` on each value `v` in it and storing the result in a new sequence, which it returns.\n\n--------\n\n    table.mapk(t, f)\n    table.mapv(t, f)\n\nAs `table.map`, but iterates using `pairs` and transforms either keys (`mapk`) or values (`mapv`). The results of `mapk` are undefined if two calls to `f(k)` with different inputs return the same key.\n\n--------\n\n    table.mapkv(t, f)\n\nAs `table.mapv`, except that `f` is passed both the key and the value and is expected to return a new key and value.\n\n--------\n\n    table.merge(dst, src, collisions)\n\nShallowly merge `src` into `dst`. The `collisions` argument controls the behaviour in case of key collision: if `\"overwrite\"` the value in `src` takes precedence, if `\"ignore\"` the value in `dst` does, and if `\"error\"` an immediate error is raised. The default is `\"overwrite\"`.\n\n--------\n\n    table.print(t)\n\nEquivalent to `print(table.tostring(t))`.\n\n--------\n\n    table.tostring(t)\n\nPretty-print `t` and all of it subtables in an indented, human-readable form. If the same table appears multiple times it will print the table pointer but not any of its contents on occurences after the first. This does not generate output in a form the interpreter will understand; if you are trying to serialize a table, use table.dump.\n\n\n### 2.7. Command Line Flag Parsing -- flags.lua ###\n\nThis is a library to hopefully remove the pain from processing command line arguments. It supports both long and short flags, with and without arguments, and respects the convention of separating flags from positional arguments with `--`.\n\n#### Terminology ####\n\nA *flag* is a command line option starting with `-` or `--`. A *flag argument* (or just *argument*) is a parameter associated with a flag. A *positional argument* is a command line argument that is not a flag and not associated with any flag. For example, in the following command line:\n\n    ls -l --all --sort=time foo bar\n\nThe flags are `l`, `all`, and `sort`; `sort` has the flag argument `time`; and the positional arguments are `foo` and `bar`.\n\nA *flag key* is the lua identifier associated with a flag, and is derived from the first argument to `flags.register` by replacing any characters that would make an invalid identifier with `_`. For example, the flag key of `verbose` is `verbose`, and the flag key of `log-all` is `log_all`. This is done so that you can use them as field accessors, e.g:\n\n    flags.register('log-all')\n    local opts = flags.parse {...}\n    if opts.log_all then ....\n\nA *flag type* is the type of the value associated with a flag, which defaults to boolean. This library implements this with *type functions*, which are responsible for taking the flag argument parsed from the command line (which is, of necessity, a string) and either returning a value of the appropriate type or raising an error.\n\n#### Flags API ####\n\n    flags.register(name, ...)\n\nRegister a boolean flag. The flag name is the first argument; subsequent arguments are aliases, e.g. `flags.register('verbose', 'v')`.\n\n------\n\n    flags.register(name, ...) {\n        type = flags.boolean;\n        default = nil;\n        required = false;\n        key = nil;\n        value = nil;\n        help = \"\";\n        set = function(k, v) end;\n    }\n\nRegister a flag with non-default settings. The settings listed are all optional and are detailed below; the values listed are the default values.\n\n    type = flags.boolean\n\nSet the type of the flag. See *flag type functions* below.\n\n    default = nil\n\nSet the default value of the flag. If the flag is not specified on the command line, it will have this value.\n\n    required = false\n\nIf true, `flags.parse()` will raise an error if the given flag is not specified on the command line. You cannot set both `default` and `required` on the same flag.\n\n    key = nil\n\nStore the flag's value in this key rather than using a key derived from the flag's canonical name.\n\n    value = nil\n\nIf the flag is present on the command line, store this value instead of the flag's actual value. Most useful with boolean flags when you actually want them to store a special value in some other flag (by using this in conjunction with `key`). For example, you could make `--log-to-stderr` an alias for `--log-to=/dev/stderr` with `key = \"log_to\"; value = \"/dev/stderr\"`.\n\n    help = \"\"\n\nThe help text for the flag, used by `flags.help()`.\n\n    set = function(k, v) end\n\nA function that will be called when the flag is set, passed the flag's canonical name and the value it was just set to.\n\n------\n\n    flags.help()\n\nReturns a string containing help text for all currently registered flags, suitable for output to the terminal.\n\n------\n\n    flags.parse(argv, allow_undefined, allow_missing)\n\nParse the given arguments as the command line. Sets `flags.parsed` to the options parsed and returns it. The returned value will not be changed by future calls to `flags.parse()`, but the value of `flags.parsed` will be.\n\n`argv` should be a sequence of string arguments. Other entries in the table (if any) are ignored, and the table is not modified.\n\n`allow_undefined`, if true, makes the library permissive of unknown command line flags; rather than raising an error, they will be considered positional arguments. This is *usually* not what you want, but can be useful when you want to do early parsing of command line options before all flags are registered.\n\n`allow_missing`, if true, makes the library permissive of absent command line flags even if they have `required = true` in the flag definition. This can be useful if you don't have the complete command line yet, but want to parse what you do have.\n\n------\n\n    flags.parsed\n\nA table containing the values associated with the latest call to `flags.parse`. Keys are the flag keys; values are the parsed values, or the default value for flags which were not present at parse time. Positional arguments are assigned to the numeric indexes, in the same order they originally appeared on the command line.\n\nIf no flag parsing has happened yet, there are no positional arguments, and all flags have their default values.\n\n------\n\n    flags 'name'\n\nReturns the parsed value associated with the given flag *name*. This is the preferred way to get \"current\" flag values. Note that this uses the name, not the key; given a flag `--log-level`, `flags.parsed.log_level`, `flags.get('log_level')`, and `flags 'log-level'` are all equivalent. Unlike reading `flags.parsed`, this will raise an error if you request the value of a flag that doesn't exist.\n\n------\n\n    flags.defaults[name]\n\nReturns the default value associated with the flag `name`.\n\n------\n\n    flags.require(name)\n\nAs `flags 'name'`, but raises an error if the flag was not specified on the most recently parsed command line. You can use this to make flags that are not mandatory at parse time (via `required = true`) mandatory at access time, e.g. to enforce a \"if this flag is specified, these other flags must also be present\" relationship.\n\n#### Flag Type Functions ####\n\nThe library comes with four builtin type functions, detailed below. Writing your own is as simple as defining a `function foo_type(flag, arg)` that takes the flag name and flag argument as arguments, and returns the value that should actually be set in the options.\n\n------\n\n    flags.boolean\n\nThe default type. Boolean flags don't require any argument; a flag registered with `flags.register('v', 'verbose')` can be set with `-v` or `--verbose`, and unset with `+v` or `--no-verbose`.\n\n------\n\n    flags.string\n\nFlags with arbitrary strings as arguments.\n\n------\n\n    flags.number\n\nFlags with base-10 numbers as arguments. The argument is fed to `tonumber` and an error is raised if it doesn't convert successfully.\n\n------\n\n    flags.list\n\nA comma-separated list of strings. This is a convenience function for `flags.listOf(flags.string, ',')`.\n\n------\n\n    flags.listOf(type, separator)\n\nA function for creating list types. `type` must be a type function as defined above, and `separator` a single character to split on. The flag will be parsed into a sequence of values of the given type, suitable for use with `ipairs`. `separator` is optional and defaults to `','`.\n\n------\n\n    flags.map\n\nA comma-separated list of `key=value` map entries, such as `--map-flag=cats=rule,dogs=drool`. This is a convenience function for `flags.mapOf(flags.string, flags.string, ',', '=')`.\n\n------\n\n    flags.mapOf(key_type, value_type, separator, assigner)\n\nA key-value map, with the specified key types, separator (the character between the k-v pairs), and assigner (the character between the key and the value). The key and value types should by type functions (such as `flags.string` or `flags.number`) which will be called to parse the key and value. `separator` defaults to `','`, and `assigner` to `'='`.\n\n\n### 2.8. Logging -- logging.lua ###\n\nThis module categorizes log messages into five levels: error, warning, info, debug, and trace. By default it logs to stdout and sets the log level based on the `LOG_LEVEL` environment variable, or warning otherwise.\n\nIf the `flags` module is loaded, it also registers three command line flags.\n\n  * `--log-level` sets the log level, and overrides the `LOG_LEVEL` environment variable.\n  * `--log-to` is the name of a file to write logs to. Logs will be appended to this file instead of stdout. It will not truncate the file if it already exists.\n  * `--log-flush` causes it to fflush() the log after each write. This slows things down, but is useful when using `tail -f` on the log file.\n\n------\n\n    log.{error,warning,info,debug,trace}(format, ...)\n\nLogs a message with the given level. The arguments will be fed to string.format and the result prefixed with the specified log level and the call site.\n\n------\n\n    log.fatal(format, ...)\n\nEquivalent to `log.error`, but after logging, throws an error containing the log message.\n\n------\n\n    log.setfile(fd)\n\nSets the logging output file. The default is `io.stdout`. It may be useful to change this to `io.stderr` if (e.g.) you use stdout for user interaction but don't want to log to a file.\n\n------\n\n    log.setlevel(level)\n\nSets the logging level. `level` can be either a number (lower numbers result in more logging; 1, `error`, is the lowest) or a string (one of the above mentioned log levels).\n\n------\n\n    log.hook(prefix, message)\n\nIf defined, this is called just before each message is logged. `prefix` is the prefix added by the logging library, containing the log level and call site; `message` is the user-provided message. This can be used to, e.g., send logs over the network, or log to both an in-game console and an external file.\n\n## 3. License\n\nCopyright © 2014 Ben \"ToxicFrog\" Kelly, Google Inc.\n\nDistributed under the MIT license; see the file COPYING for details.\n\n\n### Disclaimer\n\nThis is not an official Google product and is not supported by Google.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoxicfrog%2Fluautil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoxicfrog%2Fluautil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoxicfrog%2Fluautil/lists"}