From 6308e4ae87fc988c4a24bfda320622fff2304f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 21 Jul 2026 22:38:31 +0200 Subject: [PATCH] rebrand to "luna" --- Cargo.lock | 66 +++++++++++++++++----------------- Cargo.toml | 2 +- README.md | 24 ++++++------- docs/README.md | 8 ++--- docs/concurrency.md | 2 +- docs/fs.md | 10 +++--- docs/math.md | 2 +- docs/os.md | 4 +-- docs/require.md | 16 ++++----- docs/table.md | 2 +- lua/demo/weather.lua | 2 +- lua/stubs/README.md | 4 +-- lua/stubs/os.lua | 2 +- lua/stubs/require.lua | 4 +-- src/lua_tests/http_tests.rs | 2 +- src/lua_tests/require_tests.rs | 2 +- src/main.rs | 10 +++--- src/repl.rs | 4 +-- src/stdlib/fs/store.rs | 7 ++-- src/stdlib/require.rs | 2 +- 20 files changed, 88 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c5dc99..2059cea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,39 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "a" -version = "0.1.0" -dependencies = [ - "base64", - "chrono", - "clap", - "colored", - "cookie_store", - "crossterm", - "digest_auth", - "dotenvy", - "env_logger", - "form_urlencoded", - "futures", - "inquire", - "log", - "mlua", - "publicsuffix", - "reqwest", - "reqwest_cookie_store", - "rusqlite", - "rustix", - "rustls", - "rustyline", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tiny_http", - "tokio", -] - [[package]] name = "ahash" version = "0.8.12" @@ -1279,6 +1246,39 @@ dependencies = [ "which", ] +[[package]] +name = "luna" +version = "0.1.0" +dependencies = [ + "base64", + "chrono", + "clap", + "colored", + "cookie_store", + "crossterm", + "digest_auth", + "dotenvy", + "env_logger", + "form_urlencoded", + "futures", + "inquire", + "log", + "mlua", + "publicsuffix", + "reqwest", + "reqwest_cookie_store", + "rusqlite", + "rustix", + "rustls", + "rustyline", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tiny_http", + "tokio", +] + [[package]] name = "md-5" version = "0.10.6" diff --git a/Cargo.toml b/Cargo.toml index 66d17d6..89b7703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "a" +name = "luna" version = "0.1.0" edition = "2024" publish = false diff --git a/README.md b/README.md index ba430e5..c660b9c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# `a` +# Luna -`a` embeds a Lua interpreter and adds a standard library and IO access, so a script can reach the outside world — today an HTTP client, SQLite and interactive terminal prompts, with more of the surface below planned — without additional setup. +Luna embeds a Lua interpreter and adds a standard library and IO access, so a script can reach the outside world — today an HTTP client, SQLite and interactive terminal prompts, with more of the surface below planned — without additional setup. ```sh -a file.lua # run a script -a # start an interactive REPL +luna file.lua # run a script +luna # start an interactive REPL ``` -Run `a` with a script path to execute it, or with no arguments to start an +Run `luna` with a script path to execute it, or with no arguments to start an interactive REPL. ## The REPL -Running `a` with no arguments starts a read-eval-print loop in the same +Running `luna` with no arguments starts a read-eval-print loop in the same sandboxed environment used for scripts, with the full standard library available. It is convenient for trying out the library or exploring data. @@ -23,7 +23,7 @@ available. It is convenient for trying out the library or exploring data. - Async calls such as `os.sleep`, `http.get`, and `task.join` suspend and resume just as they do in a script — each entry runs on the async executor. - Line editing and history are provided, with history persisted to - `~/.a_history`. Press Ctrl-D to exit (`os.exit` is not available in the + `~/.luna_history`. Press Ctrl-D to exit (`os.exit` is not available in the sandbox). Each entry is compiled as its own chunk, so a `local` declared on one line is @@ -32,13 +32,13 @@ across entries. ## What it is -`a` is not a Lua implementation; it embeds [Lua 5.5](https://www.lua.org/) (via [`mlua`](https://github.com/mlua-rs/mlua)) and adds the parts stock Lua omits. Plain Lua ships almost no standard library and no way to reach the outside world without C modules and a build toolchain. `a` provides a standard library written partly in Rust and partly in Lua, together with IO access. +Luna is not a Lua implementation; it embeds [Lua 5.5](https://www.lua.org/) (via [`mlua`](https://github.com/mlua-rs/mlua)) and adds the parts stock Lua omits. Plain Lua ships almost no standard library and no way to reach the outside world without C modules and a build toolchain. Luna provides a standard library written partly in Rust and partly in Lua, together with IO access. The result is a single binary that runs a `.lua` file. There is no package manager, build step, or project scaffolding. ## The standard library -The standard library is the main purpose of `a`. Available today: +The standard library is the main purpose of Luna. Available today: - **Networking** — HTTP client (WebSocket and MQTT are planned). - **SQLite** — access to embedded SQLite databases. @@ -57,15 +57,15 @@ The intent is for the common case to be a single call rather than a multi-step s - Embeds Lua 5.5 via `mlua`, on an async core ([`tokio`](https://tokio.rs/)) so IO-heavy scripts don't block. - A single self-contained binary. No LuaRocks, no make, no virtualenv. -- Scripts run in a **sandboxed** environment modeled on [Luau's safe environment](https://luau.org/sandbox): `io`, `package`, and `debug` are not loaded; `os` is trimmed to its time functions; the bytecode/chunk-loading escape hatches (`load`, `loadfile`, `dofile`, `string.dump`) are removed. The library `a` provides is the supported way to reach the outside world. +- Scripts run in a **sandboxed** environment modeled on [Luau's safe environment](https://luau.org/sandbox): `io`, `package`, and `debug` are not loaded; `os` is trimmed to its time functions; the bytecode/chunk-loading escape hatches (`load`, `loadfile`, `dofile`, `string.dump`) are removed. The library Luna provides is the supported way to reach the outside world. - A leading `#!` shebang line is skipped (after an optional UTF-8 BOM), so scripts can be made executable directly. Script files need not be valid UTF-8 — Lua source is byte-oriented. -- Scripts can be split into files with a sandboxed [`require`](docs/require.md): modules are looked up next to the main script (symlinks resolved, so an executable script symlinked into `~/.local/bin` finds its files) and in `$A_INCLUDE_PATHS`, which can also be set in a `.env` beside the script. +- Scripts can be split into files with a sandboxed [`require`](docs/require.md): modules are looked up next to the main script (symlinks resolved, so an executable script symlinked into `~/.local/bin` finds its files) and in `$LUNA_INCLUDE_PATHS`, which can also be set in a `.env` beside the script. ## Building ```sh cargo build --release -# binary at target/release/a +# binary at target/release/luna cargo run -- lua/test.lua # run a script during development ``` diff --git a/docs/README.md b/docs/README.md index db64582..c195ff0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# `a` standard library +# Luna standard library -Reference docs for the library `a` adds on top of Lua 5.5. Every module below is +Reference docs for the library Luna adds on top of Lua 5.5. Every module below is a **global**, present in every script with no setup (`require`-ing one by name also works and returns the same table). @@ -23,6 +23,6 @@ also works and returns the same table). | Page | What it covers | |------|----------------| | [Concurrency](concurrency.md) | How async calls suspend, and running work in parallel with `task.join`. | -| [Modules & `require`](require.md) | Splitting scripts into files: search roots, `A_INCLUDE_PATHS`, `.env`, caching, LSP setup. | +| [Modules & `require`](require.md) | Splitting scripts into files: search roots, `LUNA_INCLUDE_PATHS`, `.env`, caching, LSP setup. | -See the top-level [README](../README.md) for what `a` is and how to build it. +See the top-level [README](../README.md) for what Luna is and how to build it. diff --git a/docs/concurrency.md b/docs/concurrency.md index 1ed7c04..df4676c 100644 --- a/docs/concurrency.md +++ b/docs/concurrency.md @@ -1,6 +1,6 @@ # Concurrency -`a` runs every script on an async core ([tokio](https://tokio.rs/)). The async +Luna runs every script on an async core ([tokio](https://tokio.rs/)). The async stdlib calls — `os.sleep`, the `http` client, `sqlite` queries — don't block the OS thread while they wait: they suspend and let other work run. This page covers how that interacts with Lua coroutines and how to run several pieces of work at diff --git a/docs/fs.md b/docs/fs.md index 23996b8..37b8868 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -105,12 +105,12 @@ script: - Read and write are tracked separately; each is approved, denied, or not decided yet. -When an operation needs a decision that isn't recorded, `a` asks on the +When an operation needs a decision that isn't recorded, Luna asks on the terminal (stderr, so it shows even with stdout piped) — one keypress, no Enter: ``` -a: permission request +luna: permission request script: /home/me/bin/deploy.lua wants: read access to directory /home/me/project [y] allow once [n] deny once [A] allow always [N] never allow: @@ -126,8 +126,8 @@ a: permission request ### `access.json` -Recorded answers live in `$XDG_CONFIG_HOME/a/access.json` (usually -`~/.config/a/access.json`), keyed by the script's canonical path — moving or +Recorded answers live in `$XDG_CONFIG_HOME/luna/access.json` (usually +`~/.config/luna/access.json`), keyed by the script's canonical path — moving or renaming a script starts it with a clean slate. The file is plain JSON and safe to hand-edit; each flag is `true` (approved), `false` (denied), or `null` (not decided yet): @@ -144,7 +144,7 @@ safe to hand-edit; each flag is `true` (approved), `false` (denied), or } ``` -Concurrent `a` processes update it safely (the write cycle holds a file +Concurrent Luna processes update it safely (the write cycle holds a file lock and replaces the file atomically). The **REPL** prompts the same way, but its answers — including `A`/`N` — diff --git a/docs/math.md b/docs/math.md index 3543ef1..f54e11d 100644 --- a/docs/math.md +++ b/docs/math.md @@ -1,6 +1,6 @@ # `math` -`a` keeps Lua's standard `math` library intact and adds a handful of functions +Luna keeps Lua's standard `math` library intact and adds a handful of functions that come up constantly in everyday scripting: rounding, clamping, sign, finiteness, and linear rescaling. The additions live on the same global `math` table, so `math.floor` and `math.round` sit side by side. diff --git a/docs/os.md b/docs/os.md index 0a7a38c..c24568d 100644 --- a/docs/os.md +++ b/docs/os.md @@ -1,9 +1,9 @@ # `os` -`a` runs scripts in a sandbox, so the standard `os` table is trimmed to its +Luna runs scripts in a sandbox, so the standard `os` table is trimmed to its safe, time-related functions — `os.time`, `os.clock`, `os.date`, `os.difftime`, `os.getenv` — while the parts that touch the system -(`os.execute`, `os.remove`, `os.exit`, …) are removed. To that trimmed table `a` +(`os.execute`, `os.remove`, `os.exit`, …) are removed. To that trimmed table Luna adds two functions: a high-resolution clock and an async sleep. ```lua diff --git a/docs/require.md b/docs/require.md index 9c014e4..e9e79f7 100644 --- a/docs/require.md +++ b/docs/require.md @@ -1,6 +1,6 @@ # Modules & `require` -`a` provides a sandboxed `require` for splitting a script into multiple files. +Luna provides a sandboxed `require` for splitting a script into multiple files. It follows stock Lua's conventions — dot-separated module names, one execution per module, cached results — so editors and the Lua language server understand the imports without special setup. @@ -27,7 +27,7 @@ Names are resolved against a fixed list of directories, in order: so a script symlinked into `~/.local/bin` still loads modules (and its `.env`, below) from next to the real file. In the REPL, the current working directory is used instead. -2. **Each entry of `$A_INCLUDE_PATHS`**, colon-separated like `$PATH`. +2. **Each entry of `$LUNA_INCLUDE_PATHS`**, colon-separated like `$PATH`. In every root, two candidates are tried (the Lua language server's default templates): @@ -43,14 +43,14 @@ lists every path that was tried, in the stock Lua format. ## `.env` -Before resolving anything, `a` loads a `.env` file from the main script's +Before resolving anything, Luna loads a `.env` file from the main script's real directory (the CWD in the REPL), if one exists. Variables already set in the real environment take precedence. This is the intended place to set -`A_INCLUDE_PATHS` for a project: +`LUNA_INCLUDE_PATHS` for a project: ``` # myproject/.env -A_INCLUDE_PATHS=/home/me/lua-libs:/opt/shared-lua +LUNA_INCLUDE_PATHS=/home/me/lua-libs:/opt/shared-lua ``` Since the script's symlink is resolved first, `myproject/main.lua` symlinked @@ -93,15 +93,15 @@ across `require` boundaries: } ``` -List the same directories in `workspace.library` as in `A_INCLUDE_PATHS`; +List the same directories in `workspace.library` as in `LUNA_INCLUDE_PATHS`; modules under the script's own directory are found via the workspace root. ## Example layout ``` myproject/ -├── main.lua -- #!/usr/bin/env a; symlinked to ~/.local/bin/mytool -├── .env -- A_INCLUDE_PATHS=... +├── main.lua -- #!/usr/bin/env luna; symlinked to ~/.local/bin/mytool +├── .env -- LUNA_INCLUDE_PATHS=... ├── .luarc.json └── lib/ ├── helper.lua -- require "lib.helper" diff --git a/docs/table.md b/docs/table.md index cad19a6..e6b4f08 100644 --- a/docs/table.md +++ b/docs/table.md @@ -1,6 +1,6 @@ # `table` -`a` keeps Lua's standard `table` library (`insert`, `remove`, `concat`, `sort`, +Luna keeps Lua's standard `table` library (`insert`, `remove`, `concat`, `sort`, `pack`, `unpack`) and adds the higher-order and collection helpers stock Lua leaves out: map/filter/reduce, merge and deep-copy, aggregates, lookups, and a read-only proxy. The additions live on the same global `table`. diff --git a/lua/demo/weather.lua b/lua/demo/weather.lua index 97631f7..9996c79 100644 --- a/lua/demo/weather.lua +++ b/lua/demo/weather.lua @@ -1,4 +1,4 @@ --- Weather forecast demo — a small app that exercises most of the `a` stdlib: +-- Weather forecast demo — a small app that exercises most of the Luna stdlib: -- -- tui prompts (text w/ suggestions, select, confirm), styled markup, -- custom presets, escape for untrusted text in markup, message & diff --git a/lua/stubs/README.md b/lua/stubs/README.md index 873aa56..ca866e9 100644 --- a/lua/stubs/README.md +++ b/lua/stubs/README.md @@ -1,6 +1,6 @@ -# Lua stubs for the `a` runtime environment +# Lua stubs for the Luna runtime environment -`---@meta` type definitions of the **Rust-native** parts of the `a` stdlib, for +`---@meta` type definitions of the **Rust-native** parts of the Luna stdlib, for [lua-language-server](https://github.com/LuaLS/lua-language-server) (completion, hover docs, static checking). Definitions only — these files are never executed and are not part of the runtime. diff --git a/lua/stubs/os.lua b/lua/stubs/os.lua index 513fef1..75ffbfa 100644 --- a/lua/stubs/os.lua +++ b/lua/stubs/os.lua @@ -1,6 +1,6 @@ ---@meta ---@diagnostic disable: missing-return --- Type stubs for the `a` additions to the sandboxed `os` table. +-- Type stubs for the Luna additions to the sandboxed `os` table. -- -- The sandbox trims `os` to its time-related functions — os.time, os.clock, -- os.date, os.difftime, os.getenv — plus the two additions below. The diff --git a/lua/stubs/require.lua b/lua/stubs/require.lua index 261495f..e252458 100644 --- a/lua/stubs/require.lua +++ b/lua/stubs/require.lua @@ -1,12 +1,12 @@ ---@meta ---@diagnostic disable: missing-return -- Type stub for the sandboxed `require`. The stock `package` library does not --- exist in `a` (it is disabled in .luarc.json, which also removes the stock +-- exist in Luna (it is disabled in .luarc.json, which also removes the stock -- `require` definition — this stub replaces it). Reference: docs/require.md -- -- Names are dot-separated (`require "lib.helper"` → lib/helper.lua or -- lib/helper/init.lua), resolved against the main script's directory and each --- entry of $A_INCLUDE_PATHS. Each module runs once; the result is cached. +-- entry of $LUNA_INCLUDE_PATHS. Each module runs once; the result is cached. ---Load a module. Returns the module's value and, unlike stock Lua's loader ---data, the resolved file path as the second value. diff --git a/src/lua_tests/http_tests.rs b/src/lua_tests/http_tests.rs index f392419..2816f39 100644 --- a/src/lua_tests/http_tests.rs +++ b/src/lua_tests/http_tests.rs @@ -133,7 +133,7 @@ async fn test_get_response_fields() { // The default User-Agent goes out unless overridden. let reqs = server.requests(); - assert!(reqs[0].header("user-agent").unwrap().starts_with("a/"), "{reqs:?}"); + assert!(reqs[0].header("user-agent").unwrap().starts_with("luna/"), "{reqs:?}"); } #[tokio::test] diff --git a/src/lua_tests/require_tests.rs b/src/lua_tests/require_tests.rs index 1a26542..b6c9e12 100644 --- a/src/lua_tests/require_tests.rs +++ b/src/lua_tests/require_tests.rs @@ -254,7 +254,7 @@ async fn test_require_module_error_propagates_and_is_not_cached() { #[tokio::test] async fn test_require_module_with_shebang_and_bom() { let root = TempRoot::new("shebang"); - root.write("sh.lua", "\u{FEFF}#!/usr/bin/env a\nreturn 7"); + root.write("sh.lua", "\u{FEFF}#!/usr/bin/env luna\nreturn 7"); let lua = lua_with_roots(&[&root]); let got: i64 = lua.load(r#"return require "sh""#).eval_async().await.unwrap(); diff --git a/src/main.rs b/src/main.rs index 22512c4..1215aeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,7 +101,7 @@ async fn run() -> Result<(), Box> { // to the real file. let realpath = tokio::fs::canonicalize(&filepath) .await - .map_err(|e| format!("a: cannot open {}: {e}", filepath.display()))?; + .map_err(|e| format!("luna: cannot open {}: {e}", filepath.display()))?; let script_dir = realpath.parent().unwrap_or(Path::new("/")); setup_require(&lua, script_dir)?; // Exposed to scripts as fs.getScriptDir() — the base for bundled assets. @@ -116,7 +116,7 @@ async fn run() -> Result<(), Box> { // byte strings, and stock Lua runs files that aren't valid UTF-8. let source = tokio::fs::read(&realpath) .await - .map_err(|e| format!("a: cannot read {}: {e}", filepath.display()))?; + .map_err(|e| format!("luna: cannot read {}: {e}", filepath.display()))?; lua.load(stdlib::require::prepare_chunk(&source)) // "@" marks the chunk name as a file path in error messages / tracebacks @@ -130,17 +130,17 @@ async fn run() -> Result<(), Box> { /// Prepare module loading rooted at `dir` — the real directory of the main /// script, or the CWD in the REPL. Loads `dir/.env` into the process /// environment (variables already set in the real environment win), then -/// installs `require` searching `dir` followed by the `$A_INCLUDE_PATHS` +/// installs `require` searching `dir` followed by the `$LUNA_INCLUDE_PATHS` /// entries (colon-separated, like `$PATH`). fn setup_require(lua: &mlua::Lua, dir: &Path) -> Result<(), Box> { match dotenvy::from_path(dir.join(".env")) { Ok(()) => {} Err(dotenvy::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(format!("a: cannot load {}: {e}", dir.join(".env").display()).into()), + Err(e) => return Err(format!("luna: cannot load {}: {e}", dir.join(".env").display()).into()), } let mut roots = vec![dir.to_path_buf()]; - if let Some(paths) = std::env::var_os("A_INCLUDE_PATHS") { + if let Some(paths) = std::env::var_os("LUNA_INCLUDE_PATHS") { roots.extend(std::env::split_paths(&paths).filter(|p| !p.as_os_str().is_empty())); } stdlib::require::install(lua, roots)?; diff --git a/src/repl.rs b/src/repl.rs index 2842b35..ea5091d 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -69,7 +69,7 @@ pub(crate) async fn run(lua: &Lua) -> Result<(), Box> { let mut editor = DefaultEditor::new()?; let history_path = std::env::var_os("HOME").map(|home| { let mut p = std::path::PathBuf::from(home); - p.push(".a_history"); + p.push(".luna_history"); p }); if let Some(path) = &history_path { @@ -78,7 +78,7 @@ pub(crate) async fn run(lua: &Lua) -> Result<(), Box> { } println!( - "a {} — interactive Lua. Ctrl-D to exit.", + "luna {} — interactive Lua. Ctrl-D to exit.", env!("CARGO_PKG_VERSION") ); diff --git a/src/stdlib/fs/store.rs b/src/stdlib/fs/store.rs index aef8525..ef6886c 100644 --- a/src/stdlib/fs/store.rs +++ b/src/stdlib/fs/store.rs @@ -3,7 +3,7 @@ //! Grants are keyed by the script's canonical path, then by canonical //! directory, each carrying a tri-state per direction: `null` (not decided), //! `true` (approved), `false` (denied). The file is hand-editable; [`persist`] -//! keeps concurrent `a` processes from losing each other's updates by holding +//! keeps concurrent Luna processes from losing each other's updates by holding //! an exclusive `flock` on a sidecar lock file across its read-merge-write //! cycle, and replaces the store with `rename()` so readers never see a //! half-written file. @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; /// Config directory name under `$XDG_CONFIG_HOME`. A literal, not /// `CARGO_PKG_NAME`: renaming the binary silently abandoning every recorded /// grant should be an explicit decision made here. -pub(crate) const APP_NAME: &str = "a"; +pub(crate) const APP_NAME: &str = "luna"; /// Tri-state access recorded for one directory. `None` = not decided yet, /// `Some(true)` = approved, `Some(false)` = denied — serialized as JSON @@ -61,7 +61,8 @@ impl Default for AccessStore { } } -/// `$XDG_CONFIG_HOME/a/access.json`, falling back to `~/.config/a/access.json`. +/// `$XDG_CONFIG_HOME/luna/access.json`, falling back to +/// `~/.config/luna/access.json`. /// Per the XDG spec a non-absolute `$XDG_CONFIG_HOME` is ignored. `None` when /// no absolute base can be found — permissions then live for the session only. pub(crate) fn default_store_path() -> Option { diff --git a/src/stdlib/require.rs b/src/stdlib/require.rs index eab6656..5002db7 100644 --- a/src/stdlib/require.rs +++ b/src/stdlib/require.rs @@ -3,7 +3,7 @@ //! Module names use stock Lua dot notation (`require "db.migrations"`) and are //! resolved against a fixed list of root directories — the main script's real //! directory (the CWD in the REPL) followed by the entries of -//! `$A_INCLUDE_PATHS` — through the standard `?.lua` / `?/init.lua` templates. +//! `$LUNA_INCLUDE_PATHS` — through the standard `?.lua` / `?/init.lua` templates. //! Because a name may only contain identifier-like segments, a module can //! never name a file outside the roots. //!