Library / SDK
lunarmodules/Penlight avatar
lunarmodules/Penlight

Penlight: Python-inspired Lua modules for paths, tables, and functional programming

A set of pure Lua libraries focusing on input data handling (such as reading configuration files), functional programming (such as map, reduce, placeholder expressions,etc), and OS path management. Much of the functionality is inspired by the Python standard libraries.

2,133 stars262 forksLuaMIT

At a glance

What is it?
Penlight is a collection of pure Lua modules that ports ideas from Python's standard library to Lua, covering file paths, string manipulation, table operations, argument parsing, and config file reading. Its SemVer policy explicitly excludes error messages and documentation from the stability guarantee.
Who is it for?
Penlight suits Lua developers who need Python-style utilities covering file paths, config parsing, table operations, and functional programming in one LuaRocks install. Pure Lua code means no compilation step on either PuC-Rio or LuaJIT.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 38 days ago.
What is it written in?
Mainly Lua, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Python-inspired modules for a language with a small standard library

Lua's standard library covers basic string, table, math, and file I/O, but it provides nothing for directory traversal, argument parsing, or configuration file reading. Penlight fills that gap with pure Lua modules that port ideas from Python's standard library. Its scope covers input data handling (reading configuration files), functional programming (map, reduce, placeholder expressions), and OS path management.

Pure Lua means no C compilation step, and every module runs equally on PuC-Rio Lua (the reference implementation) and LuaJIT without recompiling. The versioning section confirms this scope: the library is tested against both runtimes, covering codebases that target Lua 5.1, 5.2, and later versions.

The module list spans four broad categories: paths, files, and directories; application support including argument parsing and config files; extra string and table operations; and iterators, OOP, and functional programming. That range covers much of what a typical command-line or scripting application needs without adding one dependency per feature.

Installing from LuaRocks or by copying lua/pl to the module path

The LuaRocks package name is penlight. One command handles the install and its LuaFileSystem dependency together:

bash
luarocks install penlight

Without LuaRocks, the manual path is to copy the lua/pl directory into the Lua module search path. On Linux, the typical destination is /usr/local/share/lua/5.x. On Windows with Lua for Windows, it is C:\Program Files\Lua\5.x\lua.

LuaFileSystem is the dependency that enables directory traversal. It is a C extension, so Penlight's file system functions are not truly pure Lua in the dependency chain, even though the Penlight code itself is. This is relevant for embedded Lua environments or sandboxed interpreters where loading C modules is restricted.

An optional second dependency is Alien, which makes dir.copyfile work more gracefully on Windows. Both LuaFileSystem and Alien come bundled in Lua for Windows, so that platform needs no extra steps. On a bare LuaJIT installation without Lua for Windows, Alien is not installed automatically.

To run the test suite:

bash
lua run.lua tests

To run the included examples:

bash
lua run.lua examples

Path, dir, and file modules that fill the gap in standard Lua I/O

Three modules handle file system operations. The path module provides queries: isdir, isfile, and exists check what a path points to, while dirname and basename decompose a path into its components. The dir module adds getfiles and getallfiles for listing directory contents, along with functions to create and remove directory trees. The file module provides copy, move, read, and write functions for file contents.

Standard Lua has io.open, io.read, and os.rename, but no directory listing function and no direct way to check whether a path names a directory without attempting to open it as a file. The Penlight modules build on LuaFileSystem to close those gaps.

The dependency on LuaFileSystem is worth stating plainly. Penlight's path, dir, and file modules will not work without it. LuaRocks installs it automatically, but anyone copying the lua/pl directory manually must install LuaFileSystem separately before those modules load.

lapp parses arguments from the usage string rather than from code definitions

Two modules handle command-line arguments. The app module has parse_args for simple cases. The lapp module is the heavier option: it derives the parser from the usage string itself.

That design inverts the usual relationship between parsers and documentation. Most argument parsers take code-level definitions and generate a usage string from them. With lapp, you write the usage string, and lapp reads it to determine what arguments to expect and what types to assign them. The usage string is both the documentation and the parser specification, which keeps them consistent by construction.

The config module handles application configuration files. It reads Unix config files and Windows INI files, managing both formats without a separate parser per platform.

The strict module addresses a common Lua bug: assigning to a mistyped global name silently creates a new global rather than raising an error. Enabling strict turns that into an error. The strict.module variant applies that check to a single module's namespace, which is more practical for library code where you do not want to enforce global strictness on the entire environment.

String lambdas, list comprehensions via C syntax, and seq iterator pipelines

Three modules in the functional category have syntax worth examining directly. The utils.string_lambda function converts a string like '|x| x^2' into a callable Lua function, making it possible to pass short expressions inline wherever a function argument is expected without writing a full function definition.

The comprehension module provides list comprehensions through a string-parsing constructor:

lua
C'x for x=1,4'()=={1,2,3,4}

Here C is the comprehension constructor, which parses the string at call time and returns a callable. Invoking it produces a table. The syntax mirrors Lua's numeric for loop, which makes it readable to anyone familiar with Lua's iteration model. Because the string is parsed at call time rather than compiled into Lua bytecode, using it in a tight inner loop has different performance characteristics than a native Lua for statement.

The seq module works with iterator pipelines and can collect an iterator's output into a table. Together with the List type, which implements Python's list behavior including slicing, concatenation, and partitioning, these modules give Lua developers a functional vocabulary closer to Python's itertools than to anything in standard Lua.

SemVer covers function signatures but not error message text or documentation

Penlight follows Semantic Versioning for its module and class interfaces, tested on PuC-Rio Lua and LuaJIT. That guarantee covers the behavior of functions. It does not cover everything in the codebase, and the README states the boundaries explicitly: documentation, error message text, and deprecation warnings are outside the SemVer scope.

For production code, the error message exclusion is the one that bites. Any update, whether minor or patch, can change the text of an error message without signaling a breaking change in the version number. Code that pattern-matches against Penlight error strings can break silently during a routine upgrade.

Deprecated functionality is removed only when the first number in the version increments, not on smaller increments. The advance signal is a deprecation warning that goes to stderr by default rather than raising an error. To catch uses of deprecated functions before a major version upgrade, the deprecation warning mechanism can be enabled:

lua
require "pl.compat"
warn "@on"

Lua 5.4 introduced the warn() function natively. The compat module provides a polyfill for Lua 5.1 and 5.2, so code that calls warn() on those versions needs compat loaded first.

Tables, data arrays, and pretty-printing with safe Lua data loading

Penlight's table-related modules extend what standard Lua provides. The tablex module handles copying, comparing, and mapping over tables. Map, Set, and OrderedMap add specialized data structures. The data module reads tabular data into 2D arrays and supports queries on those arrays, with array2d adding dedicated two-dimensional operations and permute generating permutations of a given set.

The pretty module handles pretty-printing of Lua tables and provides what it describes as safe ways to load Lua as data. That phrase is meaningful in the Lua context: Lua's load() and dostring() execute arbitrary code, so using them to deserialize saved tables is a well-known security problem. The specific mechanism by which pretty makes loading safe is not described in the README, and anyone relying on that guarantee should read the module documentation at lunarmodules.github.io/Penlight/ before depending on it.

Versioning is tracked through tags and the CHANGELOG.md file at the repository root. Penlight is distributed under the MIT license, and the last push to the master branch was on 2026-09-03.

Editorial conclusion

Penlight suits Lua developers who need Python-style utilities covering file paths, config parsing, table operations, and functional programming in one LuaRocks install. Pure Lua code means no compilation step on either PuC-Rio or LuaJIT. Before crossing a major version boundary, enable the deprecation warning mechanism by loading pl.compat and calling warn with argument @on, since function removals only happen at major version increments. Error message text is outside the stability guarantee, so code that pattern-matches against Penlight error strings can break on any update.

Frequently asked questions

How do I install Penlight for Lua?

The LuaRocks package is named penlight; running luarocks install penlight installs it along with LuaFileSystem. Manual installation involves copying the lua/pl directory to the Lua module search path, typically /usr/local/share/lua/5.x on Linux.

What Lua versions does Penlight support?

Penlight is tested against PuC-Rio Lua and LuaJIT, according to the versioning section. The compat module provides a compatibility layer for unified Lua 5.1 and 5.2 codebases, and it provides the warn() function as a polyfill for Lua versions before 5.4.

How do I enable Penlight deprecation warnings?

Load the compat module with require "pl.compat" and then call warn "@on". Deprecation warnings go to stderr by default and do not raise errors. The compat module provides the warn() function on Lua versions before 5.4.

What is the lapp module in Penlight?

lapp is a command-line argument parser that derives argument definitions from the usage string you write, rather than from code definitions. The usage string is both the user-facing documentation and the parser specification.

Does Penlight work with LuaJIT?

Yes. The versioning section states that Penlight is tested against both stock Lua PuC-Rio and LuaJIT. The compat module handles differences between Lua 5.1, which LuaJIT is based on, and later versions.

Official sources

  1. Issues
  2. License: MIT
  3. lunarmodules/Penlight on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lunarmodules-penlight.svg)](https://hysenlabs.com/projects/lunarmodules-penlight)