From 98e375fc99328793ddf9b2b457db09cc495488bc Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 15:30:15 +0200 Subject: [PATCH 01/10] registry: keep boot-time service registration working Dropping the pid<=64 caller shortcut left init unable to register the endpoints it publishes for each capsule at spawn. It holds only the ambient caps and no RegisterService, so register_endpoint refused every one and no capsule reached the run queue. register_endpoint now checks only that the owner holds the caps the endpoint advertises, which is all the trusted spawn path needs. MkServiceRegister keeps the register-right gate for a new name and lets a capsule re-assert the endpoint the kernel already published for it, so a runtime squat is still refused. --- src/services/registry.rs | 16 ++++++++- .../registry/auth/caller_can_register.rs | 33 ------------------- .../auth/caller_has_register_right.rs | 2 +- src/services/registry/auth/mod.rs | 4 +-- .../registry/auth/owner_has_required.rs | 2 +- src/syscall/microkernel/ipc/register.rs | 11 +++++++ 6 files changed, 30 insertions(+), 38 deletions(-) delete mode 100644 src/services/registry/auth/caller_can_register.rs diff --git a/src/services/registry.rs b/src/services/registry.rs index d96788fd3b..b1726c0fdf 100644 --- a/src/services/registry.rs +++ b/src/services/registry.rs @@ -32,8 +32,14 @@ pub(crate) use reserved::is_reserved_service; pub const MAX_SERVICES: usize = 256; static ENDPOINTS: Mutex> = Mutex::new(Vec::new()); +/// Register a service endpoint on behalf of `pid`. This is the trusted core +/// path: the kernel spawn path calls it to publish a verified capsule's own +/// declared endpoint, so the only authorization it enforces is that `pid` +/// actually holds the capabilities the endpoint advertises. The runtime +/// `sys_service_register` syscall layers the caller-side register-right check +/// on top before reaching here (see `caller_has_register_right`). pub fn register_endpoint(name: &str, port: u32, pid: u32, caps: u64) -> Result<(), RegError> { - if !auth::caller_can_register(pid, caps) { + if !auth::owner_has_required(pid, caps) { return Err(RegError::PermissionDenied); } let mut eps = ENDPOINTS.lock(); @@ -50,6 +56,14 @@ pub fn register_endpoint(name: &str, port: u32, pid: u32, caps: u64) -> Result<( Ok(()) } +/// True if the current process is allowed to register a service name it does +/// not already own. Gates the runtime `sys_service_register` syscall so an +/// ordinary capsule cannot squat a peer's service; publishers granted +/// `RegisterService` (e.g. net.core) pass. +pub(crate) fn caller_has_register_right() -> bool { + auth::caller_has_register_right() +} + pub fn lookup_service(name: &str) -> Option { ENDPOINTS.lock().iter().find(|e| e.name == name).cloned() } diff --git a/src/services/registry/auth/caller_can_register.rs b/src/services/registry/auth/caller_can_register.rs deleted file mode 100644 index a14c8d3b41..0000000000 --- a/src/services/registry/auth/caller_can_register.rs +++ /dev/null @@ -1,33 +0,0 @@ -// NONOS Operating System -// Copyright (C) 2026 NONOS Contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use super::caller_has_register_right::caller_has_register_right; -use super::owner_has_required::owner_has_required; - -pub(in crate::services::registry) fn caller_can_register(owner_pid: u32, required: u64) -> bool { - if !owner_has_required(owner_pid, required) { - return false; - } - // Kernel-context registration (boot-time trusted services) has no current - // pid and is allowed. Every capsule with a pid must hold the register right; - // the old `pid <= 64` shortcut handed unauthenticated registration authority - // to any capsule spawned early enough to land in that range, which let an - // ordinary app squat/impersonate a service name. - match crate::process::current_pid() { - None => true, - Some(_) => caller_has_register_right(), - } -} diff --git a/src/services/registry/auth/caller_has_register_right.rs b/src/services/registry/auth/caller_has_register_right.rs index ad09183be5..202d7f066b 100644 --- a/src/services/registry/auth/caller_has_register_right.rs +++ b/src/services/registry/auth/caller_has_register_right.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pub(super) fn caller_has_register_right() -> bool { +pub(in crate::services::registry) fn caller_has_register_right() -> bool { let token = crate::syscall::capabilities::current_caps_or_default(); token.can_register_service() || token.is_admin() } diff --git a/src/services/registry/auth/mod.rs b/src/services/registry/auth/mod.rs index b59b326875..468b0f6754 100644 --- a/src/services/registry/auth/mod.rs +++ b/src/services/registry/auth/mod.rs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -mod caller_can_register; mod caller_has_register_right; mod owner_has_required; -pub(in crate::services::registry) use caller_can_register::caller_can_register; +pub(in crate::services::registry) use caller_has_register_right::caller_has_register_right; +pub(in crate::services::registry) use owner_has_required::owner_has_required; diff --git a/src/services/registry/auth/owner_has_required.rs b/src/services/registry/auth/owner_has_required.rs index bbfac2defb..24a29b9681 100644 --- a/src/services/registry/auth/owner_has_required.rs +++ b/src/services/registry/auth/owner_has_required.rs @@ -14,6 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pub(super) fn owner_has_required(owner_pid: u32, required: u64) -> bool { +pub(in crate::services::registry) fn owner_has_required(owner_pid: u32, required: u64) -> bool { required == 0 || crate::process::caps::has(owner_pid, required) } diff --git a/src/syscall/microkernel/ipc/register.rs b/src/syscall/microkernel/ipc/register.rs index cb5fa2a593..bbee468fe5 100644 --- a/src/syscall/microkernel/ipc/register.rs +++ b/src/syscall/microkernel/ipc/register.rs @@ -61,6 +61,17 @@ pub fn sys_service_register(name_ptr: u64, name_len: usize, port: u32) -> i64 { if crate::services::registry::is_reserved_service(name, port) { return ERRNO_PERM; } + // A capsule may confirm the endpoint the kernel already published for it at + // spawn (idempotent self-registration); claiming any other name requires the + // RegisterService right. This lets an ordinary capsule re-assert its own + // service without holding the right, keeps a publisher like net.core able to + // register its extra sub-endpoints, and still bars a runtime capsule from + // squatting a peer's name. + let owns_already = crate::services::registry::lookup_service(name) + .map_or(false, |e| e.pid == pid && e.port == port); + if !owns_already && !crate::services::registry::caller_has_register_right() { + return ERRNO_PERM; + } match register_endpoint(name, port, pid, required_caps(name, Capability::IPC.bit())) { Ok(()) => 0, Err(RegError::Full) => ERRNO_NOMEM, From 5b3ae330ba3c652141f58467230281b0045017b2 Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 15:30:26 +0200 Subject: [PATCH 02/10] ipc: stamp a reply from the pending-reply entry it answers mk_ipc_reply took the correlation token from a per-client queue kept separately from the pending-reply table it validated the reply against. With several callers waiting on one server the two orders diverged, replies went out under the wrong token, and callers discarded them and blocked forever. The whole desktop stalled at the compositor handshake. The pending-reply entry, matched by the caller's own inbox, already carries that call's token, so return it from remove() and use it directly. The separate correlation table is gone. A forged mk_ipc_send reply still carries token 0 and no pending entry, so it is still dropped. --- .../microkernel/ipc/call/sys_ipc_call.rs | 14 +++--- src/syscall/microkernel/ipc/correlation.rs | 44 ------------------- src/syscall/microkernel/ipc/mod.rs | 1 - .../microkernel/ipc/pending_reply/remove.rs | 23 ++++++---- src/syscall/microkernel/ipc/recv_from.rs | 2 - src/syscall/microkernel/ipc/reply.rs | 11 +++-- 6 files changed, 28 insertions(+), 67 deletions(-) delete mode 100644 src/syscall/microkernel/ipc/correlation.rs diff --git a/src/syscall/microkernel/ipc/call/sys_ipc_call.rs b/src/syscall/microkernel/ipc/call/sys_ipc_call.rs index 761581b8f2..3f05ad5b1c 100644 --- a/src/syscall/microkernel/ipc/call/sys_ipc_call.rs +++ b/src/syscall/microkernel/ipc/call/sys_ipc_call.rs @@ -57,11 +57,11 @@ pub fn sys_ipc_call( if crate::usercopy::validate_user_write(resp, resp_len).is_err() { return ERRNO_FAULT; } - // This call's correlation token. It is registered with the pending reply so - // the redirect-reply path (a service replying via `mk_ipc_send` to its fixed - // reply endpoint) stamps the reply with it, and it is sent on the request so - // an `mk_ipc_reply` service echoes it via `take_for_reply`. Either way the - // genuine reply carries this token; a forged injection can only carry 0. + // This call's correlation token, registered with the pending reply keyed by + // this caller's inbox. Both reply paths read it back from that same entry: + // the redirect path (a service replying via `mk_ipc_send` to its fixed reply + // endpoint) and the direct `mk_ipc_reply` path both stamp the reply with it. + // The genuine reply carries this token; a forged injection can only carry 0. let token = next_call_token(); let inbox = reply_inbox::for_pid(pid); let endpoint = lookup_port(ep as u32); @@ -75,7 +75,7 @@ pub fn sys_ipc_call( trace(pid, b"send", send_result); if send_result < 0 { if let Some(server_pid) = endpoint_pid { - pending_reply::remove(server_pid, &inbox); + let _ = pending_reply::remove(server_pid, &inbox); } return send_result; } @@ -83,7 +83,7 @@ pub fn sys_ipc_call( let recv_result = recv_reply_correlated(pid, &inbox, resp, resp_len, timeout, token); if recv_result < 0 { if let Some(server_pid) = endpoint_pid { - pending_reply::remove(server_pid, &inbox); + let _ = pending_reply::remove(server_pid, &inbox); } } if recv_result >= 24 && req_len >= 20 { diff --git a/src/syscall/microkernel/ipc/correlation.rs b/src/syscall/microkernel/ipc/correlation.rs deleted file mode 100644 index 33a0035e85..0000000000 --- a/src/syscall/microkernel/ipc/correlation.rs +++ /dev/null @@ -1,44 +0,0 @@ -// NONOS Operating System -// Copyright (C) 2026 NONOS Contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -extern crate alloc; - -use alloc::collections::{BTreeMap, VecDeque}; -use spin::Mutex; - -const MAX_PENDING_PER_PID: usize = 16; - -static PENDING: Mutex>> = Mutex::new(BTreeMap::new()); - -pub(super) fn note_request(client_pid: u32, token: u64) { - if token == 0 { - return; - } - let mut map = PENDING.lock(); - let queue = map.entry(client_pid).or_insert_with(VecDeque::new); - if queue.len() >= MAX_PENDING_PER_PID { - queue.pop_front(); - } - queue.push_back(token); -} - -pub(super) fn take_for_reply(client_pid: u32) -> u64 { - let mut map = PENDING.lock(); - match map.get_mut(&client_pid) { - Some(queue) => queue.pop_front().unwrap_or(0), - None => 0, - } -} diff --git a/src/syscall/microkernel/ipc/mod.rs b/src/syscall/microkernel/ipc/mod.rs index eaf3d6349b..cfab526082 100644 --- a/src/syscall/microkernel/ipc/mod.rs +++ b/src/syscall/microkernel/ipc/mod.rs @@ -15,7 +15,6 @@ // along with this program. If not, see . mod call; -mod correlation; mod inbox_name; mod lookup; mod pending_reply; diff --git a/src/syscall/microkernel/ipc/pending_reply/remove.rs b/src/syscall/microkernel/ipc/pending_reply/remove.rs index f4a97f700a..7a44a73eb2 100644 --- a/src/syscall/microkernel/ipc/pending_reply/remove.rs +++ b/src/syscall/microkernel/ipc/pending_reply/remove.rs @@ -16,17 +16,22 @@ use super::state::PENDING; -pub(in crate::syscall::microkernel::ipc) fn remove(server_pid: u32, caller_inbox: &str) -> bool { +/// Remove the pending-reply entry for `caller_inbox` under `server_pid` and +/// return the per-call correlation token it carried. Matching is by the +/// caller's private inbox, so the token is the one that exact `mk_ipc_call` +/// registered, even when several callers have replies outstanding on the same +/// server. Returns `None` when there is no such pending call (a forged or +/// duplicate reply), which the caller treats as "drop". +pub(in crate::syscall::microkernel::ipc) fn remove( + server_pid: u32, + caller_inbox: &str, +) -> Option { let mut map = PENDING.lock(); - let Some(queue) = map.get_mut(&server_pid) else { - return false; - }; - let Some(pos) = queue.iter().position(|(inbox, _)| inbox == caller_inbox) else { - return false; - }; - queue.remove(pos); + let queue = map.get_mut(&server_pid)?; + let pos = queue.iter().position(|(inbox, _)| inbox == caller_inbox)?; + let (_, token) = queue.remove(pos)?; if queue.is_empty() { map.remove(&server_pid); } - true + Some(token) } diff --git a/src/syscall/microkernel/ipc/recv_from.rs b/src/syscall/microkernel/ipc/recv_from.rs index 68ced6b14b..4a7a579ad3 100644 --- a/src/syscall/microkernel/ipc/recv_from.rs +++ b/src/syscall/microkernel/ipc/recv_from.rs @@ -19,7 +19,6 @@ use crate::process::current_pid; use crate::syscall::microkernel::errnos::{ERRNO_FAULT, ERRNO_INVAL, ERRNO_NOENT, ERRNO_TIMEDOUT}; use core::sync::atomic::{AtomicU32, Ordering}; -use super::correlation::note_request; use super::inbox_name::resolve_for_recv; use super::sender_pid::from_envelope; @@ -94,7 +93,6 @@ fn deliver( sender_pid_out: u64, ) -> i64 { let sender_pid = from_envelope(&msg.from); - note_request(sender_pid, msg.correlation); trace_dequeue(pid, sender_pid); let copy_len = msg.data.len().min(len); if crate::usercopy::copy_to_user(buf, &msg.data[..copy_len]).is_err() { diff --git a/src/syscall/microkernel/ipc/reply.rs b/src/syscall/microkernel/ipc/reply.rs index 233a2cd8a9..078b6b67c5 100644 --- a/src/syscall/microkernel/ipc/reply.rs +++ b/src/syscall/microkernel/ipc/reply.rs @@ -23,7 +23,6 @@ use crate::syscall::microkernel::errnos::{ ERRNO_BUSY, ERRNO_FAULT, ERRNO_INVAL, ERRNO_NOENT, ERRNO_NOMEM, }; -use super::correlation::take_for_reply; use super::pending_reply; use super::reply_inbox; @@ -69,10 +68,14 @@ pub fn sys_ipc_reply(dest_pid: u64, buf: u64, len: usize) -> i64 { } let caller_pid = current_pid().unwrap_or(0); let dest = reply_inbox::for_pid(dest_pid as u32); - if !pending_reply::remove(caller_pid, &dest) { + // The token comes from the same pending-reply entry that authorizes this + // reply, matched by the caller's inbox. Using that entry's token (rather + // than a separate per-client FIFO) keeps the stamp correct when several + // callers have replies outstanding on this server at once. + let Some(token) = pending_reply::remove(caller_pid, &dest) else { trace(caller_pid, dest_pid, b"drop no-call", 0, &dest); return 0; - } + }; let from = alloc::format!("proc.{}", caller_pid); let mut msg = match IpcMessage::new(&from, &dest, &data) { Ok(msg) => msg, @@ -81,7 +84,7 @@ pub fn sys_ipc_reply(dest_pid: u64, buf: u64, len: usize) -> i64 { return ERRNO_NOMEM; } }; - msg.correlation = take_for_reply(dest_pid as u32); + msg.correlation = token; let rc = match try_enqueue_strict(&dest, msg) { Ok(()) => { crate::sched::wake_process(dest_pid as u32); From e9e2cdd501c4ad02015a032cdaf5c85dd7b06c33 Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 15:30:52 +0200 Subject: [PATCH 03/10] desktop: add the Launchpad application grid A dock button opens a full-screen grid of every desktop app and installed tool, so the dock stays short instead of growing a tile per app. The grid is built from the launcher and tool tables, so adding an app or installing a tool fills a cell with no layout change. A click launches the service, or focuses the window when it is already open. Tools that ship no artwork get an icon drawn from the name. --- .../src/render/bottom_taskbar.rs | 21 +++++++- .../src/render/chrome.rs | 4 ++ .../src/render/launchpad/gen_icon.rs | 51 +++++++++++++++++++ .../src/render/launchpad/grid.rs | 33 ++++++++++++ .../src/render/launchpad/hit.rs | 33 ++++++++++++ .../src/render/launchpad/mod.rs | 16 ++++++ .../src/render/launchpad/paint.rs | 26 ++++++++++ .../src/render/launchpad/tile.rs | 40 +++++++++++++++ .../src/render/layout.rs | 9 +++- .../capsule_desktop_shell/src/render/mod.rs | 1 + .../src/server/handlers/launcher_focus.rs | 12 ++++- .../src/server/handlers/launcher_request.rs | 10 +++- .../src/server/handlers/launchpad.rs | 33 ++++++++++++ .../src/server/handlers/mod.rs | 1 + .../capsule_desktop_shell/src/server/input.rs | 8 ++- .../src/setup/prime/run/build_context.rs | 1 + .../src/state/context.rs | 2 + .../capsule_desktop_shell/src/state/mod.rs | 2 + .../src/state/tool_apps.rs | 15 ++++++ 19 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/gen_icon.rs create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/grid.rs create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/hit.rs create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/mod.rs create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/paint.rs create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/tile.rs create mode 100644 userland/capsule_desktop_shell/src/server/handlers/launchpad.rs create mode 100644 userland/capsule_desktop_shell/src/state/tool_apps.rs diff --git a/userland/capsule_desktop_shell/src/render/bottom_taskbar.rs b/userland/capsule_desktop_shell/src/render/bottom_taskbar.rs index 26accb507c..e04c3ef253 100644 --- a/userland/capsule_desktop_shell/src/render/bottom_taskbar.rs +++ b/userland/capsule_desktop_shell/src/render/bottom_taskbar.rs @@ -16,10 +16,28 @@ use super::draw_app_icon; use super::fill::fill_rect; -use super::layout::{bottom_dock_rect, TASKBAR_ENTRY_W}; +use super::layout::{bottom_dock_rect, launchpad_slot_x, TASKBAR_ENTRY_W}; use crate::state::{Context, LAUNCHER_APPS, TASKBAR_NO_ACTIVE}; const ICON_SIZE: u32 = 40; +const LAUNCHPAD_DOT: u32 = 0xFF9F_B4D6; + +// The Launchpad button: the familiar 3x3 grid of cells, sitting in the dock +// slot just past the last app. +fn draw_launchpad_button(ctx: &Context, box_top: u32, box_h: u32) { + let slot_x = launchpad_slot_x(bottom_dock_rect(ctx.width, ctx.height)); + let x0 = slot_x + (TASKBAR_ENTRY_W - ICON_SIZE) / 2; + let y0 = box_top + (box_h - ICON_SIZE) / 2; + let cell = ICON_SIZE / 3; + let dot = cell.saturating_sub(4); + for row in 0..3 { + for col in 0..3 { + let x = x0 + col * cell + 2; + let y = y0 + row * cell + 2; + fill_rect(ctx.backing_va, ctx.stride, ctx.width, ctx.height, x, y, dot, dot, LAUNCHPAD_DOT); + } + } +} pub fn paint_bottom_taskbar(ctx: &Context) { let dock = bottom_dock_rect(ctx.width, ctx.height); @@ -70,4 +88,5 @@ pub fn paint_bottom_taskbar(ctx: &Context) { draw_app_icon(ctx, icon_x, icon_y, app.icon, ICON_SIZE); x += TASKBAR_ENTRY_W + 6; } + draw_launchpad_button(ctx, box_top, box_h); } diff --git a/userland/capsule_desktop_shell/src/render/chrome.rs b/userland/capsule_desktop_shell/src/render/chrome.rs index 11ef0971fb..bdb9974c01 100644 --- a/userland/capsule_desktop_shell/src/render/chrome.rs +++ b/userland/capsule_desktop_shell/src/render/chrome.rs @@ -43,6 +43,10 @@ pub fn paint_chrome(ctx: &Context) { if ctx.spotlight.visible { paint_rect::paint_rect(ctx, spotlight_rect(ctx.width, ctx.height), SPOTLIGHT_ARGB); } + // The Launchpad, when open, covers the whole desktop and its dock. + if ctx.launchpad { + super::launchpad::paint_launchpad(ctx); + } // The right-click menu floats above everything else, dock included. super::desktop_menu::paint(ctx); } diff --git a/userland/capsule_desktop_shell/src/render/launchpad/gen_icon.rs b/userland/capsule_desktop_shell/src/render/launchpad/gen_icon.rs new file mode 100644 index 0000000000..6dc834785b --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/gen_icon.rs @@ -0,0 +1,51 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! A generated tile for a command-line tool that ships no artwork: a coloured +//! rounded square, its hue derived from the name so each tool stays visually +//! distinct, with the tool's uppercase initial in the centre. + +use crate::render::fill::fill_rect; +use crate::render::text::draw_overlay_text; +use crate::state::Context; + +const TILE_BG: u32 = 0xFF0C_1016; +const GLYPH_ADV: u32 = 8; + +pub(super) fn draw(ctx: &Context, x: u32, y: u32, size: u32, label: &[u8]) { + let (va, st, vw, vh) = (ctx.backing_va, ctx.stride, ctx.width, ctx.height); + fill_rect(va, st, vw, vh, x, y, size, size, TILE_BG); + let accent = hue(label); + fill_rect(va, st, vw, vh, x + 4, y + 4, size - 8, size - 8, accent); + // Knock a small stair off each corner so the fill reads as rounded. + let s2 = size.saturating_sub(6); + for &(cx, cy) in &[(x + 4, y + 4), (x + s2, y + 4), (x + 4, y + s2), (x + s2, y + s2)] { + fill_rect(va, st, vw, vh, cx, cy, 2, 2, TILE_BG); + } + let ch = [initial(label)]; + let gx = x + size / 2 - GLYPH_ADV / 2; + let gy = y + size / 2 - 6; + draw_overlay_text(ctx, gx, gy, &ch, 0xFF0A_0E14); +} + +// The tool's first letter, uppercased for the tile glyph. +fn initial(label: &[u8]) -> u8 { + match label.first().copied() { + Some(c @ b'a'..=b'z') => c - 32, + Some(c) => c, + None => b'?', + } +} + +// A stable, bright hue from the name so distinct tools never share a colour. +fn hue(label: &[u8]) -> u32 { + let mut h: u32 = 2166136261; + for &b in label { + h = (h ^ b as u32).wrapping_mul(16777619); + } + let r = 0x60 | (h & 0x7F); + let g = 0x60 | ((h >> 8) & 0x7F); + let b = 0x60 | ((h >> 16) & 0x7F); + 0xFF00_0000 | (r << 16) | (g << 8) | b +} diff --git a/userland/capsule_desktop_shell/src/render/launchpad/grid.rs b/userland/capsule_desktop_shell/src/render/launchpad/grid.rs new file mode 100644 index 0000000000..1fcce3b544 --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/grid.rs @@ -0,0 +1,33 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Grid geometry for the Launchpad: a centred, row-major grid holding every +//! desktop app followed by every installed tool. + +use crate::state::{LAUNCHER_APPS, TOOL_APPS}; + +pub(super) const TILE: u32 = 72; +pub(super) const CELL_W: u32 = 120; +pub(super) const CELL_H: u32 = 108; +pub(super) const TOP_PAD: u32 = 110; + +/// Total tiles: the desktop apps first, then the installed command-line tools. +pub(super) fn count() -> usize { + LAUNCHER_APPS.len() + TOOL_APPS.len() +} + +/// Columns that fit the display, kept within a sensible range so the grid stays +/// centred rather than stretching edge to edge on a wide screen. +pub(super) fn cols(width: u32) -> u32 { + (width / CELL_W).clamp(1, 8) +} + +/// Top-left screen position of the nth cell. +pub(super) fn cell_origin(width: u32, index: usize) -> (u32, u32) { + let c = cols(width); + let i = index as u32; + let grid_w = c * CELL_W; + let left = width.saturating_sub(grid_w) / 2; + (left + (i % c) * CELL_W, TOP_PAD + (i / c) * CELL_H) +} diff --git a/userland/capsule_desktop_shell/src/render/launchpad/hit.rs b/userland/capsule_desktop_shell/src/render/launchpad/hit.rs new file mode 100644 index 0000000000..7cd1cb21a4 --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/hit.rs @@ -0,0 +1,33 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Which Launchpad tile a point falls on, and what it launches. + +use super::grid::{cell_origin, count, CELL_H, CELL_W}; +use crate::state::LAUNCHER_APPS; + +/// What a clicked tile launches: a desktop app by index, or an installed tool +/// by index into the tool table. +pub enum Target { + App(usize), + Tool(usize), +} + +/// Index of the tile under a point, if any. +pub fn hit(width: u32, px: u32, py: u32) -> Option { + (0..count()).find(|&i| { + let (x, y) = cell_origin(width, i); + px >= x && px < x + CELL_W && py >= y && py < y + CELL_H + }) +} + +/// Resolve a tile index to the app or tool it stands for. +pub fn target(index: usize) -> Target { + let apps = LAUNCHER_APPS.len(); + if index < apps { + Target::App(index) + } else { + Target::Tool(index - apps) + } +} diff --git a/userland/capsule_desktop_shell/src/render/launchpad/mod.rs b/userland/capsule_desktop_shell/src/render/launchpad/mod.rs new file mode 100644 index 0000000000..d70142e24e --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/mod.rs @@ -0,0 +1,16 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! The Launchpad: a full-screen grid of every desktop app and installed tool, +//! opened from the dock. Drawing and hit-testing live here; the click action +//! that launches a tile and dismisses the overlay lives in the server layer. + +mod gen_icon; +mod grid; +mod hit; +mod paint; +mod tile; + +pub use hit::{hit, target, Target}; +pub use paint::paint_launchpad; diff --git a/userland/capsule_desktop_shell/src/render/launchpad/paint.rs b/userland/capsule_desktop_shell/src/render/launchpad/paint.rs new file mode 100644 index 0000000000..f023e75df3 --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/paint.rs @@ -0,0 +1,26 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Paint the Launchpad: a dark full-screen wash, a title, and every app tile. + +use super::grid::count; +use super::tile; +use crate::render::fill::fill_rect; +use crate::render::text::draw_overlay_text; +use crate::state::Context; + +const OVERLAY_BG: u32 = 0xF2_0A0E_15; +const TITLE_FG: u32 = 0xFFF2_F6FC; +const GLYPH_ADV: u32 = 8; +const TITLE: &[u8] = b"Applications"; + +pub fn paint_launchpad(ctx: &Context) { + fill_rect(ctx.backing_va, ctx.stride, ctx.width, ctx.height, 0, 0, ctx.width, ctx.height, OVERLAY_BG); + let title_w = TITLE.len() as u32 * GLYPH_ADV; + let title_x = ctx.width.saturating_sub(title_w) / 2; + draw_overlay_text(ctx, title_x, 56, TITLE, TITLE_FG); + for i in 0..count() { + tile::paint(ctx, i); + } +} diff --git a/userland/capsule_desktop_shell/src/render/launchpad/tile.rs b/userland/capsule_desktop_shell/src/render/launchpad/tile.rs new file mode 100644 index 0000000000..e887d315de --- /dev/null +++ b/userland/capsule_desktop_shell/src/render/launchpad/tile.rs @@ -0,0 +1,40 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Draw one Launchpad tile: a desktop app uses its own artwork, an installed +//! tool gets a generated tile, and both carry a centred label underneath. + +use super::gen_icon; +use super::grid::{cell_origin, CELL_W, TILE}; +use crate::render::draw_app_icon; +use crate::render::text::draw_overlay_text; +use crate::state::{Context, LAUNCHER_APPS, TOOL_APPS}; + +const LABEL_FG: u32 = 0xFFE4_EEF8; +const GLYPH_ADV: u32 = 8; +const MAX_LABEL: usize = 14; + +pub(super) fn paint(ctx: &Context, index: usize) { + let (cx, cy) = cell_origin(ctx.width, index); + let icon_x = cx + (CELL_W - TILE) / 2; + let apps = LAUNCHER_APPS.len(); + let label: &[u8] = if index < apps { + let app = &LAUNCHER_APPS[index]; + draw_app_icon(ctx, icon_x, cy, app.icon, TILE); + app.label + } else { + let tool = &TOOL_APPS[index - apps]; + gen_icon::draw(ctx, icon_x, cy, TILE, tool.label); + tool.label + }; + paint_label(ctx, label, cx, cy + TILE + 8); +} + +// The tile label, centred within the cell. +fn paint_label(ctx: &Context, name: &[u8], cx: u32, y: u32) { + let shown = if name.len() > MAX_LABEL { &name[..MAX_LABEL] } else { name }; + let text_w = shown.len() as u32 * GLYPH_ADV; + let x = cx + CELL_W.saturating_sub(text_w) / 2; + draw_overlay_text(ctx, x, y, shown, LABEL_FG); +} diff --git a/userland/capsule_desktop_shell/src/render/layout.rs b/userland/capsule_desktop_shell/src/render/layout.rs index d441777899..110ea7bb05 100644 --- a/userland/capsule_desktop_shell/src/render/layout.rs +++ b/userland/capsule_desktop_shell/src/render/layout.rs @@ -18,11 +18,18 @@ use crate::state::spotlight::{SPOTLIGHT_HEIGHT, SPOTLIGHT_WIDTH}; use crate::state::LAUNCHER_APPS; pub const MENUBAR_HEIGHT: u32 = 28; -pub const BOTTOM_DOCK_WIDTH: u32 = LAUNCHER_APPS.len() as u32 * (TASKBAR_ENTRY_W + 6) - 6 + 24; +// One slot per desktop app, plus a trailing slot for the Launchpad button. +pub const BOTTOM_DOCK_WIDTH: u32 = + (LAUNCHER_APPS.len() as u32 + 1) * (TASKBAR_ENTRY_W + 6) - 6 + 24; pub const BOTTOM_DOCK_HEIGHT: u32 = 64; pub const BOTTOM_DOCK_BOTTOM_INSET: u32 = 24; pub const TASKBAR_ENTRY_W: u32 = 80; +/// Left edge of the Launchpad button: the slot just past the last app. +pub fn launchpad_slot_x(dock: Rect) -> u32 { + dock.x + 12 + LAUNCHER_APPS.len() as u32 * (TASKBAR_ENTRY_W + 6) +} + #[derive(Clone, Copy, Default)] pub struct Rect { pub x: u32, diff --git a/userland/capsule_desktop_shell/src/render/mod.rs b/userland/capsule_desktop_shell/src/render/mod.rs index 30288ef3e0..9b84ace7dc 100644 --- a/userland/capsule_desktop_shell/src/render/mod.rs +++ b/userland/capsule_desktop_shell/src/render/mod.rs @@ -20,6 +20,7 @@ pub mod desktop_icons; pub mod desktop_menu; pub mod fill; mod icons; +pub mod launchpad; pub mod layout; pub mod text; pub mod toasts; diff --git a/userland/capsule_desktop_shell/src/server/handlers/launcher_focus.rs b/userland/capsule_desktop_shell/src/server/handlers/launcher_focus.rs index ade83e40d4..6c2f8bf450 100644 --- a/userland/capsule_desktop_shell/src/server/handlers/launcher_focus.rs +++ b/userland/capsule_desktop_shell/src/server/handlers/launcher_focus.rs @@ -16,8 +16,9 @@ use nonos_libc::mk_time_millis; -use crate::render::layout::{bottom_dock_rect, TASKBAR_ENTRY_W}; +use crate::render::layout::{bottom_dock_rect, launchpad_slot_x, TASKBAR_ENTRY_W}; use crate::server::handlers::launcher_request::{self, LaunchOutcome}; +use crate::server::handlers::launchpad; use crate::server::refresh_taskbar::refresh_taskbar; use crate::state::{mark_taskbar_launch, Context, NotifyLevel, LAUNCHER_APPS}; @@ -52,4 +53,13 @@ pub fn handle(ctx: &mut Context, x: u32, y: u32) { } row_x += TASKBAR_ENTRY_W + 6; } + // The trailing dock slot is the Launchpad button. + let lp = launchpad_slot_x(bottom); + if x >= lp + && x < lp + TASKBAR_ENTRY_W + && y >= bottom.y + 10 + && y < bottom.y + bottom.height - 10 + { + launchpad::open(ctx); + } } diff --git a/userland/capsule_desktop_shell/src/server/handlers/launcher_request.rs b/userland/capsule_desktop_shell/src/server/handlers/launcher_request.rs index e979569561..5aa9b1702c 100644 --- a/userland/capsule_desktop_shell/src/server/handlers/launcher_request.rs +++ b/userland/capsule_desktop_shell/src/server/handlers/launcher_request.rs @@ -41,10 +41,16 @@ const OP_FOCUS_SELF: u16 = 1; // an error and we focus the running instance instead, so a click is never a // dead end. pub fn request(app: &LauncherApp) -> LaunchOutcome { - if mk_spawn_instance(app.service) >= 0 { + request_service(app.service) +} + +/// Launch, or focus if already running, whatever capsule owns `service`. Used +/// by both the dock (a desktop app) and the Launchpad (an installed tool). +pub fn request_service(service: &[u8]) -> LaunchOutcome { + if mk_spawn_instance(service) >= 0 { return LaunchOutcome::Queued; } - let Some(pid) = lookup_pid(app.service) else { return LaunchOutcome::Failed }; + let Some(pid) = lookup_pid(service) else { return LaunchOutcome::Failed }; let frame = focus_frame(); if mk_ipc_send_to_pid(pid, frame.as_ptr(), frame.len()) >= 0 { LaunchOutcome::Focused diff --git a/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs b/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs new file mode 100644 index 0000000000..340e2a86b5 --- /dev/null +++ b/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs @@ -0,0 +1,33 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Opening the Launchpad from the dock, and acting on a click while it is open: +//! a tile launches its app or tool, and any click then dismisses the overlay. + +use crate::render::launchpad::{hit, target, Target}; +use crate::server::handlers::launcher_request; +use crate::server::repaint::repaint; +use crate::state::{Context, LAUNCHER_APPS, TOOL_APPS}; + +pub fn open(ctx: &mut Context) { + ctx.launchpad = true; + repaint(ctx); +} + +pub fn click(ctx: &mut Context, px: u32, py: u32) { + if let Some(index) = hit(ctx.width, px, py) { + match target(index) { + Target::App(a) => { + let _ = launcher_request::request(&LAUNCHER_APPS[a]); + } + Target::Tool(t) => { + let _ = launcher_request::request_service(TOOL_APPS[t].service); + } + } + } + // A click anywhere closes the Launchpad, whether it landed on a tile or on + // empty space, matching how a full-screen launcher is expected to behave. + ctx.launchpad = false; + repaint(ctx); +} diff --git a/userland/capsule_desktop_shell/src/server/handlers/mod.rs b/userland/capsule_desktop_shell/src/server/handlers/mod.rs index 1d90ca883b..d87e684436 100644 --- a/userland/capsule_desktop_shell/src/server/handlers/mod.rs +++ b/userland/capsule_desktop_shell/src/server/handlers/mod.rs @@ -17,6 +17,7 @@ pub mod health; pub mod launcher_focus; pub mod launcher_request; +pub mod launchpad; pub mod notify; pub mod open_with; pub mod spotlight_open; diff --git a/userland/capsule_desktop_shell/src/server/input.rs b/userland/capsule_desktop_shell/src/server/input.rs index dc0d05b42f..2977103d9e 100644 --- a/userland/capsule_desktop_shell/src/server/input.rs +++ b/userland/capsule_desktop_shell/src/server/input.rs @@ -18,7 +18,7 @@ use crate::protocol::{read_i32, read_u16, read_u32}; use crate::render::layout::{bottom_dock_rect, MENUBAR_HEIGHT}; use crate::render::{desktop_icons, desktop_menu, topbar}; use crate::server::desktop; -use crate::server::handlers::{launcher_focus, launcher_request}; +use crate::server::handlers::{launcher_focus, launcher_request, launchpad}; use crate::server::refresh_taskbar::refresh_taskbar; use crate::state::{collapse_taskbar, reveal_taskbar, Context, LAUNCHER_APPS}; use nonos_libc::{ @@ -105,6 +105,12 @@ pub fn handle(ctx: &mut Context, buf: &[u8]) -> bool { return true; } let (px, py) = (x as u32, y as u32); + // The Launchpad, while open, captures every click: a tile launches its app + // or tool, and anything else dismisses the overlay. + if ctx.launchpad { + launchpad::click(ctx, px, py); + return true; + } // While the right-click menu is open, the next click either picks an item // or dismisses it. Handle that before anything else consumes the click. if ctx.desktop_menu.is_some() { diff --git a/userland/capsule_desktop_shell/src/setup/prime/run/build_context.rs b/userland/capsule_desktop_shell/src/setup/prime/run/build_context.rs index 0c935f96d6..d907a719c8 100644 --- a/userland/capsule_desktop_shell/src/setup/prime/run/build_context.rs +++ b/userland/capsule_desktop_shell/src/setup/prime/run/build_context.rs @@ -32,6 +32,7 @@ pub fn build_context(peers: &Peers, overlay: &Overlay) -> Context { tray: TrayTable::new(), taskbar: new_taskbar_state(), spotlight: SpotlightState::new(), + launchpad: false, last_notify_level: None, toasts: ToastQueue::new(), toast_layer_live: false, diff --git a/userland/capsule_desktop_shell/src/state/context.rs b/userland/capsule_desktop_shell/src/state/context.rs index 2645671508..65c38da17a 100644 --- a/userland/capsule_desktop_shell/src/state/context.rs +++ b/userland/capsule_desktop_shell/src/state/context.rs @@ -30,6 +30,8 @@ pub struct Context { pub tray: TrayTable, pub taskbar: TaskbarState, pub spotlight: SpotlightState, + /// Whether the full-screen Launchpad overlay is open. + pub launchpad: bool, pub last_notify_level: Option, pub toasts: ToastQueue, pub toast_layer_live: bool, diff --git a/userland/capsule_desktop_shell/src/state/mod.rs b/userland/capsule_desktop_shell/src/state/mod.rs index 71c2bf6bb1..38bea0e4b1 100644 --- a/userland/capsule_desktop_shell/src/state/mod.rs +++ b/userland/capsule_desktop_shell/src/state/mod.rs @@ -22,9 +22,11 @@ pub mod notify; pub mod spotlight; pub mod taskbar; pub mod toasts; +pub mod tool_apps; pub mod tray; pub use apps::LAUNCHER_APPS; +pub use tool_apps::TOOL_APPS; pub use chrome::TASKBAR_WINDOW_ID; pub use context::Context; pub use notify::NotifyLevel; diff --git a/userland/capsule_desktop_shell/src/state/tool_apps.rs b/userland/capsule_desktop_shell/src/state/tool_apps.rs new file mode 100644 index 0000000000..e75d3f64c8 --- /dev/null +++ b/userland/capsule_desktop_shell/src/state/tool_apps.rs @@ -0,0 +1,15 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Installed command-line tools shown in the Launchpad. Empty until the +//! nonos-app pipeline installs some; the Launchpad shows the desktop apps. + +/// One installed tool: the name on its tile and the service it launches under. +pub struct ToolApp { + pub label: &'static [u8], + pub service: &'static [u8], +} + +/// Every installed tool. None yet, so the Launchpad grid is the desktop apps. +pub const TOOL_APPS: [ToolApp; 0] = []; From 881a222e2191960453188618e5f2f180416f4045 Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 16:32:21 +0200 Subject: [PATCH 04/10] userland: install eight crates.io tools as attested capsules grex, dotenv-linter, pastel, jsonxf, choose, tokei, huniq and csview, cross-compiled unmodified for x86_64-nonos against the std PAL, each signed and enrolled under the transparent STARK policy root and spawned after the desktop. userland/apps.list is the single source of truth: tools/nonos-app cross-compiles a crate, mints its publisher keys, writes its Capsule.mk and regenerates both the kernel registry and the Launchpad tool table from it. The vendored shims under upstream-src carry the target arms these crates need on nonos: getrandom over the CRND syscall, byte-based OsStr, a thread-local errno, advisory fd-lock, HOME-based dirs. --- mk/20-build.mk | 16 + src/userspace/init/entry.rs | 10 + src/userspace/mod.rs | 1 + src/userspace/tool_capsules/embed_macro.rs | 36 + src/userspace/tool_capsules/mod.rs | 15 + src/userspace/tool_capsules/registry.rs | 50 + src/userspace/tool_capsules/spec.rs | 51 + tools/nonos-app | 287 +++ userland/apps.list | 11 + userland/capsule_choose/Capsule.mk | 21 + userland/capsule_csview/Capsule.mk | 21 + .../src/state/tool_apps.rs | 18 +- userland/capsule_dotenv-linter/Capsule.mk | 21 + userland/capsule_grex/Capsule.mk | 21 + userland/capsule_huniq/Capsule.mk | 21 + userland/capsule_jsonxf/Capsule.mk | 21 + userland/capsule_pastel/Capsule.mk | 21 + userland/capsule_tokei/Capsule.mk | 21 + userland/upstream-src/atty-0.2.14/.cargo-ok | 1 + .../atty-0.2.14/.cargo_vcs_info.json | 5 + userland/upstream-src/atty-0.2.14/.gitignore | 3 + .../upstream-src/atty-0.2.14/CHANGELOG.md | 73 + userland/upstream-src/atty-0.2.14/Cargo.toml | 34 + .../upstream-src/atty-0.2.14/Cargo.toml.orig | 26 + userland/upstream-src/atty-0.2.14/LICENSE | 20 + userland/upstream-src/atty-0.2.14/README.md | 74 + .../upstream-src/atty-0.2.14/examples/atty.rs | 9 + .../upstream-src/atty-0.2.14/rustfmt.toml | 4 + userland/upstream-src/atty-0.2.14/src/lib.rs | 218 ++ userland/upstream-src/choose/.cargo-ok | 1 + .../upstream-src/choose/.cargo_vcs_info.json | 6 + .../choose/.github/workflows/release.yml | 134 + .../choose/.github/workflows/rust.yml | 35 + userland/upstream-src/choose/.gitignore | 9 + userland/upstream-src/choose/Cargo.lock | 278 ++ userland/upstream-src/choose/Cargo.toml | 49 + userland/upstream-src/choose/Cargo.toml.orig | 22 + userland/upstream-src/choose/LICENSE | 674 +++++ userland/upstream-src/choose/Makefile | 27 + userland/upstream-src/choose/contributing.md | 116 + userland/upstream-src/choose/readme.md | 182 ++ .../upstream-src/choose/src/choice/mod.rs | 263 ++ .../src/choice/test/get_negative_start_end.rs | 193 ++ .../src/choice/test/is_reverse_range.rs | 31 + .../choose/src/choice/test/mod.rs | 55 + .../choose/src/choice/test/print_choice.rs | 1032 ++++++++ userland/upstream-src/choose/src/config.rs | 78 + userland/upstream-src/choose/src/error.rs | 63 + userland/upstream-src/choose/src/escape.rs | 1 + userland/upstream-src/choose/src/main.rs | 103 + userland/upstream-src/choose/src/opt.rs | 50 + userland/upstream-src/choose/src/parse.rs | 271 ++ .../upstream-src/choose/src/parse_error.rs | 14 + userland/upstream-src/choose/src/reader.rs | 25 + userland/upstream-src/choose/src/result.rs | 3 + userland/upstream-src/choose/src/writeable.rs | 20 + userland/upstream-src/choose/src/writer.rs | 27 + userland/upstream-src/ctrlc-3.5.2/.cargo-ok | 1 + .../ctrlc-3.5.2/.cargo_vcs_info.json | 6 + .../ctrlc-3.5.2/.github/workflows/ci.yml | 45 + userland/upstream-src/ctrlc-3.5.2/.gitignore | 2 + userland/upstream-src/ctrlc-3.5.2/Cargo.toml | 96 + .../upstream-src/ctrlc-3.5.2/Cargo.toml.orig | 46 + .../upstream-src/ctrlc-3.5.2/LICENSE-APACHE | 201 ++ userland/upstream-src/ctrlc-3.5.2/LICENSE-MIT | 23 + userland/upstream-src/ctrlc-3.5.2/README.md | 57 + .../ctrlc-3.5.2/examples/issue_46_example.rs | 26 + .../ctrlc-3.5.2/examples/readme_example.rs | 22 + .../upstream-src/ctrlc-3.5.2/src/error.rs | 54 + userland/upstream-src/ctrlc-3.5.2/src/lib.rs | 148 ++ .../ctrlc-3.5.2/src/platform/mod.rs | 26 + .../ctrlc-3.5.2/src/platform/nonos.rs | 48 + .../ctrlc-3.5.2/src/platform/unix/mod.rs | 142 ++ .../ctrlc-3.5.2/src/platform/windows/mod.rs | 81 + .../upstream-src/ctrlc-3.5.2/src/signal.rs | 23 + .../ctrlc-3.5.2/tests/main/harness.rs | 251 ++ .../ctrlc-3.5.2/tests/main/issue_97.rs | 32 + .../ctrlc-3.5.2/tests/main/mod.rs | 46 + .../tests/main/test_signal_hook.rs | 26 + userland/upstream-src/dirs-6.0.0/.cargo-ok | 1 + .../dirs-6.0.0/.cargo_vcs_info.json | 6 + .../dirs-6.0.0/.github/workflows/rust.yml | 27 + userland/upstream-src/dirs-6.0.0/.gitignore | 4 + userland/upstream-src/dirs-6.0.0/Cargo.toml | 39 + .../upstream-src/dirs-6.0.0/Cargo.toml.orig | 13 + .../upstream-src/dirs-6.0.0/LICENSE-APACHE | 174 ++ userland/upstream-src/dirs-6.0.0/LICENSE-MIT | 19 + userland/upstream-src/dirs-6.0.0/README.md | 226 ++ userland/upstream-src/dirs-6.0.0/src/lib.rs | 328 +++ userland/upstream-src/dirs-6.0.0/src/lin.rs | 49 + userland/upstream-src/dirs-6.0.0/src/mac.rs | 27 + userland/upstream-src/dirs-6.0.0/src/wasm.rs | 25 + userland/upstream-src/dirs-6.0.0/src/win.rs | 25 + userland/upstream-src/errno-0.3.14/.cargo-ok | 1 + .../errno-0.3.14/.cargo_vcs_info.json | 6 + .../errno-0.3.14/.github/dependabot.yml | 10 + .../errno-0.3.14/.github/workflows/main.yml | 89 + userland/upstream-src/errno-0.3.14/.gitignore | 2 + .../upstream-src/errno-0.3.14/CHANGELOG.md | 137 + userland/upstream-src/errno-0.3.14/Cargo.toml | 67 + .../upstream-src/errno-0.3.14/Cargo.toml.orig | 39 + .../upstream-src/errno-0.3.14/LICENSE-APACHE | 201 ++ .../upstream-src/errno-0.3.14/LICENSE-MIT | 25 + userland/upstream-src/errno-0.3.14/README.md | 62 + .../upstream-src/errno-0.3.14/clippy.toml | 1 + .../upstream-src/errno-0.3.14/src/hermit.rs | 32 + userland/upstream-src/errno-0.3.14/src/lib.rs | 160 ++ userland/upstream-src/errno-0.3.14/src/sys.rs | 40 + .../upstream-src/errno-0.3.14/src/unix.rs | 104 + .../upstream-src/errno-0.3.14/src/wasi.rs | 60 + .../upstream-src/errno-0.3.14/src/windows.rs | 81 + userland/upstream-src/fd-lock-4.0.4/.cargo-ok | 1 + .../fd-lock-4.0.4/.cargo_vcs_info.json | 6 + .../fd-lock-4.0.4/.github/CODE_OF_CONDUCT.md | 75 + .../fd-lock-4.0.4/.github/CONTRIBUTING.md | 55 + .../fd-lock-4.0.4/.github/workflows/ci.yaml | 92 + .../upstream-src/fd-lock-4.0.4/.gitignore | 7 + .../upstream-src/fd-lock-4.0.4/Cargo.toml | 67 + .../fd-lock-4.0.4/Cargo.toml.orig | 29 + .../upstream-src/fd-lock-4.0.4/LICENSE-APACHE | 190 ++ .../upstream-src/fd-lock-4.0.4/LICENSE-MIT | 21 + userland/upstream-src/fd-lock-4.0.4/README.md | 72 + .../upstream-src/fd-lock-4.0.4/src/lib.rs | 43 + .../fd-lock-4.0.4/src/read_guard.rs | 39 + .../upstream-src/fd-lock-4.0.4/src/rw_lock.rs | 126 + .../upstream-src/fd-lock-4.0.4/src/sys/mod.rs | 21 + .../fd-lock-4.0.4/src/sys/nonos.rs | 88 + .../fd-lock-4.0.4/src/sys/unix/mod.rs | 20 + .../fd-lock-4.0.4/src/sys/unix/read_guard.rs | 32 + .../fd-lock-4.0.4/src/sys/unix/rw_lock.rs | 58 + .../fd-lock-4.0.4/src/sys/unix/write_guard.rs | 39 + .../fd-lock-4.0.4/src/sys/unsupported/mod.rs | 9 + .../src/sys/unsupported/read_guard.rs | 31 + .../src/sys/unsupported/rw_lock.rs | 44 + .../src/sys/unsupported/utils.rs | 9 + .../src/sys/unsupported/write_guard.rs | 38 + .../fd-lock-4.0.4/src/sys/windows/mod.rs | 8 + .../src/sys/windows/read_guard.rs | 31 + .../fd-lock-4.0.4/src/sys/windows/rw_lock.rs | 81 + .../fd-lock-4.0.4/src/sys/windows/utils.rs | 33 + .../src/sys/windows/write_guard.rs | 38 + .../fd-lock-4.0.4/src/write_guard.rs | 46 + .../upstream-src/fd-lock-4.0.4/tests/test.rs | 94 + .../upstream-src/getrandom-0.2.17/.cargo-ok | 1 + .../getrandom-0.2.17/.cargo_vcs_info.json | 6 + .../getrandom-0.2.17/CHANGELOG.md | 508 ++++ .../upstream-src/getrandom-0.2.17/Cargo.lock | 413 +++ .../upstream-src/getrandom-0.2.17/Cargo.toml | 121 + .../getrandom-0.2.17/Cargo.toml.orig | 67 + .../getrandom-0.2.17/LICENSE-APACHE | 201 ++ .../upstream-src/getrandom-0.2.17/LICENSE-MIT | 26 + .../upstream-src/getrandom-0.2.17/README.md | 81 + .../upstream-src/getrandom-0.2.17/SECURITY.md | 13 + .../getrandom-0.2.17/benches/buffer.rs | 71 + .../getrandom-0.2.17/src/apple-other.rs | 24 + .../getrandom-0.2.17/src/custom.rs | 105 + .../getrandom-0.2.17/src/error.rs | 189 ++ .../getrandom-0.2.17/src/error_impls.rs | 15 + .../getrandom-0.2.17/src/espidf.rs | 18 + .../getrandom-0.2.17/src/fuchsia.rs | 13 + .../getrandom-0.2.17/src/getentropy.rs | 21 + .../getrandom-0.2.17/src/getrandom.rs | 25 + .../getrandom-0.2.17/src/hermit.rs | 29 + .../upstream-src/getrandom-0.2.17/src/js.rs | 155 ++ .../upstream-src/getrandom-0.2.17/src/lazy.rs | 56 + .../upstream-src/getrandom-0.2.17/src/lib.rs | 409 +++ .../getrandom-0.2.17/src/linux_android.rs | 7 + .../src/linux_android_with_fallback.rs | 33 + .../getrandom-0.2.17/src/netbsd.rs | 46 + .../getrandom-0.2.17/src/nonos.rs | 32 + .../getrandom-0.2.17/src/rdrand.rs | 121 + .../getrandom-0.2.17/src/solaris.rs | 34 + .../getrandom-0.2.17/src/solid.rs | 18 + .../getrandom-0.2.17/src/use_file.rs | 120 + .../upstream-src/getrandom-0.2.17/src/util.rs | 35 + .../getrandom-0.2.17/src/util_libc.rs | 162 ++ .../getrandom-0.2.17/src/vxworks.rs | 29 + .../upstream-src/getrandom-0.2.17/src/wasi.rs | 17 + .../getrandom-0.2.17/src/windows.rs | 60 + .../getrandom-0.2.17/tests/common/mod.rs | 100 + .../getrandom-0.2.17/tests/custom.rs | 54 + .../getrandom-0.2.17/tests/normal.rs | 11 + .../getrandom-0.2.17/tests/rdrand.rs | 22 + userland/upstream-src/home-0.5.12/.cargo-ok | 1 + .../home-0.5.12/.cargo_vcs_info.json | 6 + userland/upstream-src/home-0.5.12/Cargo.lock | 25 + userland/upstream-src/home-0.5.12/Cargo.toml | 70 + .../upstream-src/home-0.5.12/Cargo.toml.orig | 24 + .../upstream-src/home-0.5.12/LICENSE-APACHE | 201 ++ userland/upstream-src/home-0.5.12/LICENSE-MIT | 23 + userland/upstream-src/home-0.5.12/README.md | 38 + userland/upstream-src/home-0.5.12/src/env.rs | 114 + userland/upstream-src/home-0.5.12/src/lib.rs | 159 ++ .../upstream-src/home-0.5.12/src/windows.rs | 84 + userland/upstream-src/huniq/.cargo-ok | 1 + .../upstream-src/huniq/.cargo_vcs_info.json | 6 + userland/upstream-src/huniq/.gitignore | 2 + userland/upstream-src/huniq/.rustfmt.toml | 1 + userland/upstream-src/huniq/Cargo.lock | 314 +++ userland/upstream-src/huniq/Cargo.toml | 43 + userland/upstream-src/huniq/Cargo.toml.orig | 23 + userland/upstream-src/huniq/benchmark.sh | 112 + .../upstream-src/huniq/benchmark_results.txt | 106 + userland/upstream-src/huniq/readme.md | 180 ++ userland/upstream-src/huniq/src/main.rs | 217 ++ userland/upstream-src/huniq/test.sh | 33 + .../upstream-src/huniq/test/expect_count.txt | 3 + .../upstream-src/huniq/test/expect_uniq.txt | 3 + userland/upstream-src/huniq/test/input.txt | 6 + userland/upstream-src/huniq/tests/tests.rs | 35 + .../upstream-src/is-terminal-0.4.17/.cargo-ok | 1 + .../is-terminal-0.4.17/.cargo_vcs_info.json | 6 + .../is-terminal-0.4.17/Cargo.lock | 183 ++ .../is-terminal-0.4.17/Cargo.toml | 80 + .../is-terminal-0.4.17/Cargo.toml.orig | 43 + .../is-terminal-0.4.17/LICENSE-MIT | 23 + .../is-terminal-0.4.17/LICENSE-MIT-atty | 23 + .../upstream-src/is-terminal-0.4.17/README.md | 116 + .../is-terminal-0.4.17/src/lib.rs | 395 +++ .../upstream-src/os_str_bytes-6.6.1/.cargo-ok | 1 + .../os_str_bytes-6.6.1/.cargo_vcs_info.json | 6 + .../upstream-src/os_str_bytes-6.6.1/COPYRIGHT | 5 + .../os_str_bytes-6.6.1/Cargo.toml | 83 + .../os_str_bytes-6.6.1/Cargo.toml.orig | 38 + .../os_str_bytes-6.6.1/LICENSE-APACHE | 176 ++ .../os_str_bytes-6.6.1/LICENSE-MIT | 21 + .../upstream-src/os_str_bytes-6.6.1/README.md | 103 + .../os_str_bytes-6.6.1/src/common/mod.rs | 49 + .../os_str_bytes-6.6.1/src/common/raw.rs | 66 + .../os_str_bytes-6.6.1/src/iter.rs | 117 + .../os_str_bytes-6.6.1/src/lib.rs | 1189 +++++++++ .../os_str_bytes-6.6.1/src/pattern.rs | 71 + .../os_str_bytes-6.6.1/src/raw_str.rs | 2218 ++++++++++++++++ .../os_str_bytes-6.6.1/src/util.rs | 9 + .../os_str_bytes-6.6.1/src/wasm/mod.rs | 58 + .../os_str_bytes-6.6.1/src/wasm/raw.rs | 45 + .../os_str_bytes-6.6.1/src/windows/mod.rs | 113 + .../os_str_bytes-6.6.1/src/windows/raw.rs | 67 + .../src/windows/wtf8/code_points.rs | 129 + .../src/windows/wtf8/convert.rs | 181 ++ .../src/windows/wtf8/mod.rs | 18 + .../src/windows/wtf8/string.rs | 67 + userland/upstream-src/pastel/.cargo-ok | 1 + .../upstream-src/pastel/.cargo_vcs_info.json | 6 + .../pastel/.github/dependabot.yml | 14 + .../pastel/.github/workflows/CICD.yml | 352 +++ userland/upstream-src/pastel/.gitignore | 2 + userland/upstream-src/pastel/CHANGELOG.md | 168 ++ userland/upstream-src/pastel/Cargo.lock | 1058 ++++++++ userland/upstream-src/pastel/Cargo.toml | 113 + userland/upstream-src/pastel/Cargo.toml.orig | 58 + userland/upstream-src/pastel/LICENSE-APACHE | 201 ++ userland/upstream-src/pastel/LICENSE-MIT | 17 + userland/upstream-src/pastel/README.md | 245 ++ .../pastel/benches/parse_color.rs | 24 + userland/upstream-src/pastel/build.rs | 47 + .../upstream-src/pastel/doc/colorcheck.md | 21 + .../upstream-src/pastel/doc/colorcheck.png | Bin 0 -> 4152 bytes .../pastel/doc/demo-scripts/gradient.sh | 22 + userland/upstream-src/pastel/src/ansi.rs | 406 +++ userland/upstream-src/pastel/src/cli/cli.rs | 529 ++++ .../pastel/src/cli/colorpicker.rs | 97 + .../pastel/src/cli/colorpicker_tools.rs | 165 ++ .../upstream-src/pastel/src/cli/colorspace.rs | 17 + .../pastel/src/cli/commands/color_commands.rs | 203 ++ .../pastel/src/cli/commands/colorcheck.rs | 65 + .../pastel/src/cli/commands/distinct.rs | 195 ++ .../pastel/src/cli/commands/format.rs | 87 + .../pastel/src/cli/commands/gradient.rs | 57 + .../pastel/src/cli/commands/gray.rs | 13 + .../pastel/src/cli/commands/io.rs | 108 + .../pastel/src/cli/commands/list.rs | 42 + .../pastel/src/cli/commands/mod.rs | 89 + .../pastel/src/cli/commands/paint.rs | 70 + .../pastel/src/cli/commands/pick.rs | 32 + .../pastel/src/cli/commands/prelude.rs | 11 + .../pastel/src/cli/commands/random.rs | 35 + .../pastel/src/cli/commands/show.rs | 9 + .../pastel/src/cli/commands/sort.rs | 44 + .../pastel/src/cli/commands/traits.rs | 21 + .../upstream-src/pastel/src/cli/config.rs | 11 + userland/upstream-src/pastel/src/cli/error.rs | 77 + .../upstream-src/pastel/src/cli/hdcanvas.rs | 145 ++ userland/upstream-src/pastel/src/cli/main.rs | 133 + .../upstream-src/pastel/src/cli/output.rs | 119 + .../upstream-src/pastel/src/cli/utility.rs | 10 + .../upstream-src/pastel/src/colorspace.rs | 9 + userland/upstream-src/pastel/src/delta_e.rs | 353 +++ userland/upstream-src/pastel/src/distinct.rs | 466 ++++ userland/upstream-src/pastel/src/helper.rs | 106 + userland/upstream-src/pastel/src/lib.rs | 2244 +++++++++++++++++ userland/upstream-src/pastel/src/named.rs | 169 ++ userland/upstream-src/pastel/src/parser.rs | 843 +++++++ userland/upstream-src/pastel/src/random.rs | 54 + userland/upstream-src/pastel/src/types.rs | 45 + .../pastel/tests/integration_tests.rs | 125 + userland/upstream-src/tokei/.cargo-ok | 1 + .../upstream-src/tokei/.cargo_vcs_info.json | 6 + userland/upstream-src/tokei/Cargo.lock | 1871 ++++++++++++++ userland/upstream-src/tokei/Cargo.toml | 210 ++ userland/upstream-src/tokei/Cargo.toml.orig | 92 + userland/upstream-src/tokei/LICENCE-APACHE | 13 + userland/upstream-src/tokei/LICENCE-MIT | 21 + userland/upstream-src/tokei/README.md | 587 +++++ userland/upstream-src/tokei/build.rs | 166 ++ userland/upstream-src/tokei/languages.json | 2101 +++++++++++++++ userland/upstream-src/tokei/src/cli.rs | 478 ++++ userland/upstream-src/tokei/src/cli_utils.rs | 492 ++++ userland/upstream-src/tokei/src/config.rs | 188 ++ userland/upstream-src/tokei/src/consts.rs | 27 + userland/upstream-src/tokei/src/input.rs | 233 ++ .../tokei/src/language/embedding.rs | 223 ++ .../tokei/src/language/language_type.rs | 355 +++ .../tokei/src/language/language_type.tera.rs | 470 ++++ .../tokei/src/language/languages.rs | 166 ++ .../upstream-src/tokei/src/language/mod.rs | 176 ++ .../upstream-src/tokei/src/language/syntax.rs | 683 +++++ userland/upstream-src/tokei/src/lib.rs | 64 + userland/upstream-src/tokei/src/main.rs | 128 + userland/upstream-src/tokei/src/sort.rs | 61 + userland/upstream-src/tokei/src/stats.rs | 143 ++ userland/upstream-src/tokei/src/utils/ext.rs | 158 ++ userland/upstream-src/tokei/src/utils/fs.rs | 486 ++++ .../upstream-src/tokei/src/utils/macros.rs | 102 + userland/upstream-src/tokei/src/utils/mod.rs | 4 + 325 files changed, 40169 insertions(+), 4 deletions(-) create mode 100644 src/userspace/tool_capsules/embed_macro.rs create mode 100644 src/userspace/tool_capsules/mod.rs create mode 100644 src/userspace/tool_capsules/registry.rs create mode 100644 src/userspace/tool_capsules/spec.rs create mode 100755 tools/nonos-app create mode 100644 userland/apps.list create mode 100644 userland/capsule_choose/Capsule.mk create mode 100644 userland/capsule_csview/Capsule.mk create mode 100644 userland/capsule_dotenv-linter/Capsule.mk create mode 100644 userland/capsule_grex/Capsule.mk create mode 100644 userland/capsule_huniq/Capsule.mk create mode 100644 userland/capsule_jsonxf/Capsule.mk create mode 100644 userland/capsule_pastel/Capsule.mk create mode 100644 userland/capsule_tokei/Capsule.mk create mode 100644 userland/upstream-src/atty-0.2.14/.cargo-ok create mode 100644 userland/upstream-src/atty-0.2.14/.cargo_vcs_info.json create mode 100644 userland/upstream-src/atty-0.2.14/.gitignore create mode 100644 userland/upstream-src/atty-0.2.14/CHANGELOG.md create mode 100644 userland/upstream-src/atty-0.2.14/Cargo.toml create mode 100644 userland/upstream-src/atty-0.2.14/Cargo.toml.orig create mode 100644 userland/upstream-src/atty-0.2.14/LICENSE create mode 100644 userland/upstream-src/atty-0.2.14/README.md create mode 100644 userland/upstream-src/atty-0.2.14/examples/atty.rs create mode 100644 userland/upstream-src/atty-0.2.14/rustfmt.toml create mode 100644 userland/upstream-src/atty-0.2.14/src/lib.rs create mode 100644 userland/upstream-src/choose/.cargo-ok create mode 100644 userland/upstream-src/choose/.cargo_vcs_info.json create mode 100644 userland/upstream-src/choose/.github/workflows/release.yml create mode 100644 userland/upstream-src/choose/.github/workflows/rust.yml create mode 100644 userland/upstream-src/choose/.gitignore create mode 100644 userland/upstream-src/choose/Cargo.lock create mode 100644 userland/upstream-src/choose/Cargo.toml create mode 100644 userland/upstream-src/choose/Cargo.toml.orig create mode 100644 userland/upstream-src/choose/LICENSE create mode 100644 userland/upstream-src/choose/Makefile create mode 100644 userland/upstream-src/choose/contributing.md create mode 100644 userland/upstream-src/choose/readme.md create mode 100644 userland/upstream-src/choose/src/choice/mod.rs create mode 100644 userland/upstream-src/choose/src/choice/test/get_negative_start_end.rs create mode 100644 userland/upstream-src/choose/src/choice/test/is_reverse_range.rs create mode 100644 userland/upstream-src/choose/src/choice/test/mod.rs create mode 100644 userland/upstream-src/choose/src/choice/test/print_choice.rs create mode 100644 userland/upstream-src/choose/src/config.rs create mode 100644 userland/upstream-src/choose/src/error.rs create mode 100644 userland/upstream-src/choose/src/escape.rs create mode 100644 userland/upstream-src/choose/src/main.rs create mode 100644 userland/upstream-src/choose/src/opt.rs create mode 100644 userland/upstream-src/choose/src/parse.rs create mode 100644 userland/upstream-src/choose/src/parse_error.rs create mode 100644 userland/upstream-src/choose/src/reader.rs create mode 100644 userland/upstream-src/choose/src/result.rs create mode 100644 userland/upstream-src/choose/src/writeable.rs create mode 100644 userland/upstream-src/choose/src/writer.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/.cargo-ok create mode 100644 userland/upstream-src/ctrlc-3.5.2/.cargo_vcs_info.json create mode 100644 userland/upstream-src/ctrlc-3.5.2/.github/workflows/ci.yml create mode 100644 userland/upstream-src/ctrlc-3.5.2/.gitignore create mode 100644 userland/upstream-src/ctrlc-3.5.2/Cargo.toml create mode 100644 userland/upstream-src/ctrlc-3.5.2/Cargo.toml.orig create mode 100644 userland/upstream-src/ctrlc-3.5.2/LICENSE-APACHE create mode 100644 userland/upstream-src/ctrlc-3.5.2/LICENSE-MIT create mode 100644 userland/upstream-src/ctrlc-3.5.2/README.md create mode 100644 userland/upstream-src/ctrlc-3.5.2/examples/issue_46_example.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/examples/readme_example.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/error.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/lib.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/platform/mod.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/platform/nonos.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/platform/unix/mod.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/platform/windows/mod.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/src/signal.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/tests/main/harness.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/tests/main/issue_97.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/tests/main/mod.rs create mode 100644 userland/upstream-src/ctrlc-3.5.2/tests/main/test_signal_hook.rs create mode 100644 userland/upstream-src/dirs-6.0.0/.cargo-ok create mode 100644 userland/upstream-src/dirs-6.0.0/.cargo_vcs_info.json create mode 100644 userland/upstream-src/dirs-6.0.0/.github/workflows/rust.yml create mode 100644 userland/upstream-src/dirs-6.0.0/.gitignore create mode 100644 userland/upstream-src/dirs-6.0.0/Cargo.toml create mode 100644 userland/upstream-src/dirs-6.0.0/Cargo.toml.orig create mode 100644 userland/upstream-src/dirs-6.0.0/LICENSE-APACHE create mode 100644 userland/upstream-src/dirs-6.0.0/LICENSE-MIT create mode 100644 userland/upstream-src/dirs-6.0.0/README.md create mode 100644 userland/upstream-src/dirs-6.0.0/src/lib.rs create mode 100644 userland/upstream-src/dirs-6.0.0/src/lin.rs create mode 100644 userland/upstream-src/dirs-6.0.0/src/mac.rs create mode 100644 userland/upstream-src/dirs-6.0.0/src/wasm.rs create mode 100644 userland/upstream-src/dirs-6.0.0/src/win.rs create mode 100644 userland/upstream-src/errno-0.3.14/.cargo-ok create mode 100644 userland/upstream-src/errno-0.3.14/.cargo_vcs_info.json create mode 100644 userland/upstream-src/errno-0.3.14/.github/dependabot.yml create mode 100644 userland/upstream-src/errno-0.3.14/.github/workflows/main.yml create mode 100644 userland/upstream-src/errno-0.3.14/.gitignore create mode 100644 userland/upstream-src/errno-0.3.14/CHANGELOG.md create mode 100644 userland/upstream-src/errno-0.3.14/Cargo.toml create mode 100644 userland/upstream-src/errno-0.3.14/Cargo.toml.orig create mode 100644 userland/upstream-src/errno-0.3.14/LICENSE-APACHE create mode 100644 userland/upstream-src/errno-0.3.14/LICENSE-MIT create mode 100644 userland/upstream-src/errno-0.3.14/README.md create mode 100644 userland/upstream-src/errno-0.3.14/clippy.toml create mode 100644 userland/upstream-src/errno-0.3.14/src/hermit.rs create mode 100644 userland/upstream-src/errno-0.3.14/src/lib.rs create mode 100644 userland/upstream-src/errno-0.3.14/src/sys.rs create mode 100644 userland/upstream-src/errno-0.3.14/src/unix.rs create mode 100644 userland/upstream-src/errno-0.3.14/src/wasi.rs create mode 100644 userland/upstream-src/errno-0.3.14/src/windows.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/.cargo-ok create mode 100644 userland/upstream-src/fd-lock-4.0.4/.cargo_vcs_info.json create mode 100644 userland/upstream-src/fd-lock-4.0.4/.github/CODE_OF_CONDUCT.md create mode 100644 userland/upstream-src/fd-lock-4.0.4/.github/CONTRIBUTING.md create mode 100644 userland/upstream-src/fd-lock-4.0.4/.github/workflows/ci.yaml create mode 100644 userland/upstream-src/fd-lock-4.0.4/.gitignore create mode 100644 userland/upstream-src/fd-lock-4.0.4/Cargo.toml create mode 100644 userland/upstream-src/fd-lock-4.0.4/Cargo.toml.orig create mode 100644 userland/upstream-src/fd-lock-4.0.4/LICENSE-APACHE create mode 100644 userland/upstream-src/fd-lock-4.0.4/LICENSE-MIT create mode 100644 userland/upstream-src/fd-lock-4.0.4/README.md create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/lib.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/read_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/rw_lock.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/mod.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/nonos.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unix/mod.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unix/read_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unix/rw_lock.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unix/write_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/mod.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/read_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/rw_lock.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/utils.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/write_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/windows/mod.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/windows/read_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/windows/rw_lock.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/windows/utils.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/sys/windows/write_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/src/write_guard.rs create mode 100644 userland/upstream-src/fd-lock-4.0.4/tests/test.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/.cargo-ok create mode 100644 userland/upstream-src/getrandom-0.2.17/.cargo_vcs_info.json create mode 100644 userland/upstream-src/getrandom-0.2.17/CHANGELOG.md create mode 100644 userland/upstream-src/getrandom-0.2.17/Cargo.lock create mode 100644 userland/upstream-src/getrandom-0.2.17/Cargo.toml create mode 100644 userland/upstream-src/getrandom-0.2.17/Cargo.toml.orig create mode 100644 userland/upstream-src/getrandom-0.2.17/LICENSE-APACHE create mode 100644 userland/upstream-src/getrandom-0.2.17/LICENSE-MIT create mode 100644 userland/upstream-src/getrandom-0.2.17/README.md create mode 100644 userland/upstream-src/getrandom-0.2.17/SECURITY.md create mode 100644 userland/upstream-src/getrandom-0.2.17/benches/buffer.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/apple-other.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/custom.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/error.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/error_impls.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/espidf.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/fuchsia.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/getentropy.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/getrandom.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/hermit.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/js.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/lazy.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/lib.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/linux_android.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/linux_android_with_fallback.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/netbsd.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/nonos.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/rdrand.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/solaris.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/solid.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/use_file.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/util.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/util_libc.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/vxworks.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/wasi.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/src/windows.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/tests/common/mod.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/tests/custom.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/tests/normal.rs create mode 100644 userland/upstream-src/getrandom-0.2.17/tests/rdrand.rs create mode 100644 userland/upstream-src/home-0.5.12/.cargo-ok create mode 100644 userland/upstream-src/home-0.5.12/.cargo_vcs_info.json create mode 100644 userland/upstream-src/home-0.5.12/Cargo.lock create mode 100644 userland/upstream-src/home-0.5.12/Cargo.toml create mode 100644 userland/upstream-src/home-0.5.12/Cargo.toml.orig create mode 100644 userland/upstream-src/home-0.5.12/LICENSE-APACHE create mode 100644 userland/upstream-src/home-0.5.12/LICENSE-MIT create mode 100644 userland/upstream-src/home-0.5.12/README.md create mode 100644 userland/upstream-src/home-0.5.12/src/env.rs create mode 100644 userland/upstream-src/home-0.5.12/src/lib.rs create mode 100644 userland/upstream-src/home-0.5.12/src/windows.rs create mode 100644 userland/upstream-src/huniq/.cargo-ok create mode 100644 userland/upstream-src/huniq/.cargo_vcs_info.json create mode 100644 userland/upstream-src/huniq/.gitignore create mode 100644 userland/upstream-src/huniq/.rustfmt.toml create mode 100644 userland/upstream-src/huniq/Cargo.lock create mode 100644 userland/upstream-src/huniq/Cargo.toml create mode 100644 userland/upstream-src/huniq/Cargo.toml.orig create mode 100755 userland/upstream-src/huniq/benchmark.sh create mode 100644 userland/upstream-src/huniq/benchmark_results.txt create mode 100644 userland/upstream-src/huniq/readme.md create mode 100644 userland/upstream-src/huniq/src/main.rs create mode 100644 userland/upstream-src/huniq/test.sh create mode 100644 userland/upstream-src/huniq/test/expect_count.txt create mode 100644 userland/upstream-src/huniq/test/expect_uniq.txt create mode 100644 userland/upstream-src/huniq/test/input.txt create mode 100644 userland/upstream-src/huniq/tests/tests.rs create mode 100644 userland/upstream-src/is-terminal-0.4.17/.cargo-ok create mode 100644 userland/upstream-src/is-terminal-0.4.17/.cargo_vcs_info.json create mode 100644 userland/upstream-src/is-terminal-0.4.17/Cargo.lock create mode 100644 userland/upstream-src/is-terminal-0.4.17/Cargo.toml create mode 100644 userland/upstream-src/is-terminal-0.4.17/Cargo.toml.orig create mode 100644 userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT create mode 100644 userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT-atty create mode 100644 userland/upstream-src/is-terminal-0.4.17/README.md create mode 100644 userland/upstream-src/is-terminal-0.4.17/src/lib.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/.cargo-ok create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/.cargo_vcs_info.json create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/COPYRIGHT create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml.orig create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/LICENSE-APACHE create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/LICENSE-MIT create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/README.md create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/common/mod.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/common/raw.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/iter.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/lib.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/pattern.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/raw_str.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/util.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/wasm/mod.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/wasm/raw.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/mod.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/raw.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/code_points.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/convert.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/mod.rs create mode 100644 userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/string.rs create mode 100644 userland/upstream-src/pastel/.cargo-ok create mode 100644 userland/upstream-src/pastel/.cargo_vcs_info.json create mode 100644 userland/upstream-src/pastel/.github/dependabot.yml create mode 100644 userland/upstream-src/pastel/.github/workflows/CICD.yml create mode 100644 userland/upstream-src/pastel/.gitignore create mode 100644 userland/upstream-src/pastel/CHANGELOG.md create mode 100644 userland/upstream-src/pastel/Cargo.lock create mode 100644 userland/upstream-src/pastel/Cargo.toml create mode 100644 userland/upstream-src/pastel/Cargo.toml.orig create mode 100644 userland/upstream-src/pastel/LICENSE-APACHE create mode 100644 userland/upstream-src/pastel/LICENSE-MIT create mode 100644 userland/upstream-src/pastel/README.md create mode 100644 userland/upstream-src/pastel/benches/parse_color.rs create mode 100644 userland/upstream-src/pastel/build.rs create mode 100644 userland/upstream-src/pastel/doc/colorcheck.md create mode 100644 userland/upstream-src/pastel/doc/colorcheck.png create mode 100644 userland/upstream-src/pastel/doc/demo-scripts/gradient.sh create mode 100644 userland/upstream-src/pastel/src/ansi.rs create mode 100644 userland/upstream-src/pastel/src/cli/cli.rs create mode 100644 userland/upstream-src/pastel/src/cli/colorpicker.rs create mode 100644 userland/upstream-src/pastel/src/cli/colorpicker_tools.rs create mode 100644 userland/upstream-src/pastel/src/cli/colorspace.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/color_commands.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/colorcheck.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/distinct.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/format.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/gradient.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/gray.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/io.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/list.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/mod.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/paint.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/pick.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/prelude.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/random.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/show.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/sort.rs create mode 100644 userland/upstream-src/pastel/src/cli/commands/traits.rs create mode 100644 userland/upstream-src/pastel/src/cli/config.rs create mode 100644 userland/upstream-src/pastel/src/cli/error.rs create mode 100644 userland/upstream-src/pastel/src/cli/hdcanvas.rs create mode 100644 userland/upstream-src/pastel/src/cli/main.rs create mode 100644 userland/upstream-src/pastel/src/cli/output.rs create mode 100644 userland/upstream-src/pastel/src/cli/utility.rs create mode 100644 userland/upstream-src/pastel/src/colorspace.rs create mode 100644 userland/upstream-src/pastel/src/delta_e.rs create mode 100644 userland/upstream-src/pastel/src/distinct.rs create mode 100644 userland/upstream-src/pastel/src/helper.rs create mode 100644 userland/upstream-src/pastel/src/lib.rs create mode 100644 userland/upstream-src/pastel/src/named.rs create mode 100644 userland/upstream-src/pastel/src/parser.rs create mode 100644 userland/upstream-src/pastel/src/random.rs create mode 100644 userland/upstream-src/pastel/src/types.rs create mode 100644 userland/upstream-src/pastel/tests/integration_tests.rs create mode 100644 userland/upstream-src/tokei/.cargo-ok create mode 100644 userland/upstream-src/tokei/.cargo_vcs_info.json create mode 100644 userland/upstream-src/tokei/Cargo.lock create mode 100644 userland/upstream-src/tokei/Cargo.toml create mode 100644 userland/upstream-src/tokei/Cargo.toml.orig create mode 100644 userland/upstream-src/tokei/LICENCE-APACHE create mode 100644 userland/upstream-src/tokei/LICENCE-MIT create mode 100644 userland/upstream-src/tokei/README.md create mode 100644 userland/upstream-src/tokei/build.rs create mode 100644 userland/upstream-src/tokei/languages.json create mode 100644 userland/upstream-src/tokei/src/cli.rs create mode 100644 userland/upstream-src/tokei/src/cli_utils.rs create mode 100644 userland/upstream-src/tokei/src/config.rs create mode 100644 userland/upstream-src/tokei/src/consts.rs create mode 100644 userland/upstream-src/tokei/src/input.rs create mode 100644 userland/upstream-src/tokei/src/language/embedding.rs create mode 100644 userland/upstream-src/tokei/src/language/language_type.rs create mode 100644 userland/upstream-src/tokei/src/language/language_type.tera.rs create mode 100644 userland/upstream-src/tokei/src/language/languages.rs create mode 100644 userland/upstream-src/tokei/src/language/mod.rs create mode 100644 userland/upstream-src/tokei/src/language/syntax.rs create mode 100644 userland/upstream-src/tokei/src/lib.rs create mode 100644 userland/upstream-src/tokei/src/main.rs create mode 100644 userland/upstream-src/tokei/src/sort.rs create mode 100644 userland/upstream-src/tokei/src/stats.rs create mode 100644 userland/upstream-src/tokei/src/utils/ext.rs create mode 100644 userland/upstream-src/tokei/src/utils/fs.rs create mode 100644 userland/upstream-src/tokei/src/utils/macros.rs create mode 100644 userland/upstream-src/tokei/src/utils/mod.rs diff --git a/mk/20-build.mk b/mk/20-build.mk index cbe3c28cd8..c7a464f647 100644 --- a/mk/20-build.mk +++ b/mk/20-build.mk @@ -354,6 +354,14 @@ include userland/capsule_proof_io/Capsule.mk include userland/capsule_std_proof/Capsule.mk include userland/capsule_ripgrep/Capsule.mk include userland/capsule_sd/Capsule.mk +include userland/capsule_csview/Capsule.mk +include userland/capsule_huniq/Capsule.mk +include userland/capsule_tokei/Capsule.mk +include userland/capsule_choose/Capsule.mk +include userland/capsule_jsonxf/Capsule.mk +include userland/capsule_pastel/Capsule.mk +include userland/capsule_dotenv-linter/Capsule.mk +include userland/capsule_grex/Capsule.mk include userland/capsule_tokio_smoke/Capsule.mk include userland/capsule_ramfs/Capsule.mk include userland/capsule_keyring/Capsule.mk @@ -724,6 +732,14 @@ nonos-mk-image-viewer-test: $(proof-io_ARTIFACTS) \ # is what ships and what the ISO carries; do not fold this into it. nonos-mk-desktop-gui-prod: $(proof-io_ARTIFACTS) \ $(std-proof_ARTIFACTS) $(ripgrep_ARTIFACTS) $(sd_ARTIFACTS) \ + $(csview_ARTIFACTS) \ + $(huniq_ARTIFACTS) \ + $(tokei_ARTIFACTS) \ + $(choose_ARTIFACTS) \ + $(jsonxf_ARTIFACTS) \ + $(pastel_ARTIFACTS) \ + $(dotenv-linter_ARTIFACTS) \ + $(grex_ARTIFACTS) \ $(tokio-smoke_ARTIFACTS) $(ramfs_ARTIFACTS) \ $(keyring_ARTIFACTS) $(entropy_ARTIFACTS) $(crypto_ARTIFACTS) \ $(vfs_ARTIFACTS) $(driver-virtio-rng_ARTIFACTS) \ diff --git a/src/userspace/init/entry.rs b/src/userspace/init/entry.rs index e1dc87d52f..4f6c6feaff 100644 --- a/src/userspace/init/entry.rs +++ b/src/userspace/init/entry.rs @@ -32,6 +32,10 @@ pub fn run_init() -> ! { spawn_plan::spawn_desktop(); spawn_plan::spawn_market(); spawn_plan::spawn_apps(); + // Installed command-line tools have nothing to do until launched, so they + // spawn after the display and desktop are up; ahead of the GPU they starved + // the display bring-up. + run_tools(); run_tokio_smoke(); boot_log::ok("INIT", "Capsules spawned"); lower_init_priority(); @@ -81,6 +85,12 @@ fn run_sd() { #[cfg(not(feature = "nonos-capsule-sd"))] fn run_sd() {} +// Spawn every embedded tool capsule from the generated registry. Adding a tool +// is a registry line, not a new function here. +fn run_tools() { + crate::userspace::tool_capsules::spawn_all(); +} + #[cfg(feature = "nonos-capsule-tokio-smoke")] fn run_tokio_smoke() { match crate::userspace::capsule_tokio_smoke::spawn_tokio_smoke_capsule() { diff --git a/src/userspace/mod.rs b/src/userspace/mod.rs index c212aa4625..e9ccc2160c 100644 --- a/src/userspace/mod.rs +++ b/src/userspace/mod.rs @@ -78,5 +78,6 @@ pub mod capsule_wallpaper; pub mod capsule_wallpaper_catalog; pub mod capsule_wm; pub mod init; +pub mod tool_capsules; pub use init::run_init; diff --git a/src/userspace/tool_capsules/embed_macro.rs b/src/userspace/tool_capsules/embed_macro.rs new file mode 100644 index 0000000000..002e3c8b34 --- /dev/null +++ b/src/userspace/tool_capsules/embed_macro.rs @@ -0,0 +1,36 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! The `tool_capsule!` macro: bake one tool's signed artifacts into the image. + +/// Build a `ToolCapsule` from its endpoint identity and its binary name. The ELF +/// path is given explicitly; the cert, manifest, and attestation trailer are +/// found by binary name under the trust directory. All paths resolve relative to +/// the invoking file, which is the registry. +macro_rules! tool_capsule { + ($name:literal, $port:expr, $reply:literal, $reply_port:expr, $elf:literal, $bin:literal) => { + super::spec::ToolCapsule { + name: $name, + service_port: $port, + reply_inbox: $reply, + reply_port: $reply_port, + elf: include_bytes!($elf), + cert: include_bytes!(concat!( + "../../../nonos-data/trust/capsules/", + $bin, + ".nonos_id_cert.bin" + )), + manifest: include_bytes!(concat!( + "../../../nonos-data/trust/capsules/", + $bin, + ".manifest.bin" + )), + attestation: include_bytes!(concat!( + "../../../nonos-data/trust/capsules/", + $bin, + ".zk_trailer.bin" + )), + } + }; +} diff --git a/src/userspace/tool_capsules/mod.rs b/src/userspace/tool_capsules/mod.rs new file mode 100644 index 0000000000..e9bc5a2d82 --- /dev/null +++ b/src/userspace/tool_capsules/mod.rs @@ -0,0 +1,15 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Tool capsules are signed, STARK-attested crates.io utilities baked into the +//! kernel image from `userland/apps.list`. Each carries its ELF, NONOS-ID cert, +//! manifest, and STARK trailer; the spawner verifies all four under the baked +//! trust anchor before it runs. `spawn_all` brings them up after the desktop. + +#[macro_use] +mod embed_macro; +mod registry; +mod spec; + +pub use registry::spawn_all; diff --git a/src/userspace/tool_capsules/registry.rs b/src/userspace/tool_capsules/registry.rs new file mode 100644 index 0000000000..22edcf6ec2 --- /dev/null +++ b/src/userspace/tool_capsules/registry.rs @@ -0,0 +1,50 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! The list of embedded tool capsules. The block between the generated markers +//! is written by `tools/nonos-app` from `userland/apps.list`, the single +//! source of truth. Adding a tool is `nonos-app add `, not a hand edit. + +extern crate alloc; + +use alloc::vec; +use alloc::vec::Vec; + +use super::spec::ToolCapsule; +use crate::sys::boot_log; + +/// Every embedded tool capsule, generated from `userland/apps.list`. +fn embedded_tools() -> Vec { + vec![ + // nonos-app:begin (generated; do not edit by hand) + tool_capsule!("tool.grex", 4900, "endpoint.tool.grex.reply", 4901, + "../../../target/upstream-grex/bin/grex", "grex"), + tool_capsule!("tool.dotenv-linter", 4902, "endpoint.tool.dotenv-linter.reply", 4903, + "../../../target/upstream-dotenv-linter/bin/dotenv-linter", "dotenv-linter"), + tool_capsule!("tool.pastel", 4904, "endpoint.tool.pastel.reply", 4905, + "../../../target/upstream-pastel/bin/pastel", "pastel"), + tool_capsule!("tool.jsonxf", 4906, "endpoint.tool.jsonxf.reply", 4907, + "../../../target/upstream-jsonxf/bin/jsonxf", "jsonxf"), + tool_capsule!("tool.choose", 4908, "endpoint.tool.choose.reply", 4909, + "../../../target/upstream-choose/bin/choose", "choose"), + tool_capsule!("tool.tokei", 4910, "endpoint.tool.tokei.reply", 4911, + "../../../target/upstream-tokei/bin/tokei", "tokei"), + tool_capsule!("tool.huniq", 4912, "endpoint.tool.huniq.reply", 4913, + "../../../target/upstream-huniq/bin/huniq", "huniq"), + tool_capsule!("tool.csview", 4914, "endpoint.tool.csview.reply", 4915, + "../../../target/upstream-csview/bin/csview", "csview"), + // nonos-app:end + ] +} + +/// Spawn every embedded tool capsule, verified under the baked trust anchor. One +/// tool failing to spawn is logged and skipped so it never blocks the others. +pub fn spawn_all() { + for tool in embedded_tools() { + match tool.spawn() { + Ok(()) => boot_log::ok("TOOL", tool.name), + Err(_) => boot_log::error("tool capsule spawn failed"), + } + } +} diff --git a/src/userspace/tool_capsules/spec.rs b/src/userspace/tool_capsules/spec.rs new file mode 100644 index 0000000000..9edc7407db --- /dev/null +++ b/src/userspace/tool_capsules/spec.rs @@ -0,0 +1,51 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! One embedded tool capsule and how it spawns. The identity and the signed, +//! attested artifacts are data; the capability set and trust anchor are the same +//! for every tool, so a tool is a value, not a hand-written module. + +use crate::capabilities::Capability; +use crate::kernel_core::process_spawn::capsule_spawn::{self, CapsuleSpecVerified, SpawnError}; +use crate::security::nonos_id_cert::IdCertVerifyError; +use crate::security::nonos_trust_anchor::{decode as decode_trust_anchor, BAKED_TRUST_ANCHOR_POLICY}; + +/// A tool capsule baked into the kernel image: its endpoint identity and the +/// signed cert, manifest, and STARK attestation trailer the spawner verifies. +pub struct ToolCapsule { + pub name: &'static str, + pub service_port: u32, + pub reply_inbox: &'static str, + pub reply_port: u32, + pub elf: &'static [u8], + pub cert: &'static [u8], + pub manifest: &'static [u8], + pub attestation: &'static [u8], +} + +impl ToolCapsule { + /// Verify the artifacts under the baked trust anchor and spawn the tool with + /// the sandboxed capability set (execute, IPC, memory) every tool shares. + pub fn spawn(&self) -> Result<(), SpawnError> { + let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) + .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; + let spec = CapsuleSpecVerified { + name: self.name, + service_port: self.service_port, + reply_inbox: self.reply_inbox, + reply_port: self.reply_port, + elf: self.elf, + nonos_id_cert_bytes: self.cert, + manifest_bytes: self.manifest, + attestation_trailer: self.attestation, + target_triple: "x86_64-nonos-user", + requested_caps: Capability::CoreExec.bit() + | Capability::IPC.bit() + | Capability::Memory.bit(), + debug_tag: b"", + }; + capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + Ok(()) + } +} diff --git a/tools/nonos-app b/tools/nonos-app new file mode 100755 index 0000000000..1fc993a6c6 --- /dev/null +++ b/tools/nonos-app @@ -0,0 +1,287 @@ +#!/usr/bin/env bash +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# nonos-app: install a crates.io tool as a signed, STARK-attested NONOS capsule +# from one command. It cross-compiles the crate for x86_64-nonos, mints its +# publisher keys, signs it, records it in userland/apps.list (the single source +# of truth), and regenerates the kernel tool registry. No kernel file is edited +# by hand. After `add`, run `make nonos-mk-desktop-gui-prod` to enroll + build. +# +# nonos-app add [bin] install a crate (bin defaults to the crate name) +# nonos-app regen rewrite the registry block from apps.list +# nonos-app list show installed tool apps +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +APPS="$ROOT/userland/apps.list" +REGISTRY="$ROOT/src/userspace/tool_capsules/registry.rs" +BUILDMK="$ROOT/mk/20-build.mk" +TARGET="$ROOT/userland/x86_64-nonos-user.json" +RTOBJ="$ROOT/target/nonos_rt.o" +TC="nightly-2026-01-16" +NB="$(dirname "$(RUSTUP_TOOLCHAIN=$TC rustup which rustc)")" +CS="$ROOT/nonos-sign/target/release/capsule-sign" + +die() { echo "nonos-app: $*" >&2; exit 1; } + +# The highest service port currently in apps.list, or 4898 so the first app +# starts at 4900. The system capsules (desktop apps, compositor, drivers) hold +# ports up to 4865, so tools begin above that range to avoid endpoint clashes. +next_ports() { + local max=4898 + while read -r _ _ sp _; do [ -n "${sp:-}" ] && [ "$sp" -gt "$max" ] && max=$sp; done \ + < <(grep -vE '^\s*#|^\s*$' "$APPS" 2>/dev/null || true) + echo $((max + 2)) $((max + 3)) +} + +CC_FLAGS="-Zbuild-std=std,panic_abort -Zbuild-std-features=compiler-builtins-mem" + +# The vendored NONOS shim dir under upstream-src for a foundational crate that +# assumes unix, or empty if there is no shim for it yet. +shim_dir_for() { + case "$1" in + ctrlc) echo "ctrlc-3.5.2" ;; + getrandom) echo "getrandom-0.2.17" ;; + atty) echo "atty-0.2.14" ;; + home) echo "home-0.5.12" ;; + fd-lock) echo "fd-lock-4.0.4" ;; + dirs) echo "dirs-6.0.0" ;; + os_str_bytes) echo "os_str_bytes-6.6.1" ;; + errno) echo "errno-0.3.14" ;; + is-terminal) echo "is-terminal-0.4.17" ;; + *) echo "" ;; + esac +} + +# Copy a crate's fetched source from the registry cache into upstream-src so it +# can be built with --path and local [patch] overrides. +vendor_app() { + local crate="$1" src + # Match only -, never a sibling like -core, by + # requiring a digit right after the crate name. + src="$(ls -d "$HOME/.cargo/registry/src"/*/"$crate"-[0-9]* 2>/dev/null | sort -V | tail -1)" + [ -n "$src" ] || die "no fetched source for $crate in the registry cache" + rm -rf "$ROOT/userland/upstream-src/$crate" + cp -R "$src" "$ROOT/userland/upstream-src/$crate" + chmod -R u+w "$ROOT/userland/upstream-src/$crate" +} + +# Rewrite the [patch.crates-io] block in the vendored manifest for every shim +# accumulated so far, replacing any previous block. +write_patches() { + local crate="$1"; shift + local mk="$ROOT/userland/upstream-src/$crate/Cargo.toml" s + sed -i '' '/# nonos-app-patch/,$d' "$mk" + { printf '\n# nonos-app-patch\n[patch.crates-io]\n' + for s in "$@"; do printf '%s = { path = "../%s" }\n' "$s" "$(shim_dir_for "$s")"; done + } >> "$mk" +} + +# One build attempt (remote crate or vendored --path), log captured for blocker +# detection. Args: [extra cargo args, e.g. --features]. +build_once() { + local tgt="$1" log="$2" bin="$3"; shift 3 + PATH="$NB:$PATH" RUSTUP_TOOLCHAIN="$TC" \ + RUSTFLAGS="-Clink-arg=$RTOBJ --cfg getrandom_backend=\"rdrand\"" \ + CARGO_TARGET_DIR="$ROOT/target/nonos-app-target" \ + "$NB/cargo" install $tgt --no-default-features --target "$TARGET" $CC_FLAGS "$@" \ + --root "$ROOT/target/upstream-$bin" --no-track --force --bin "$bin" > "$log" 2>&1 +} + +# Cross-compile for x86_64-nonos, applying NONOS shims for known foundational +# crates and retrying until it builds or hits a blocker with no shim. +cross_compile() { + local crate="$1" bin="$2" log vendored="" attempt; log="$(mktemp)" + local shims=() feats=() feat_arg + if [ -f "$ROOT/target/upstream-$bin/bin/$bin" ]; then + echo " reusing prebuilt $bin"; return + fi + echo " cross-compiling $crate (bin $bin)..." + for attempt in 1 2 3 4 5 6 7 8; do + feat_arg="" + [ ${#feats[@]} -gt 0 ] && feat_arg="--features=$(IFS=,; echo "${feats[*]}")" + if [ -z "$vendored" ]; then + build_once "$crate" "$log" "$bin" $feat_arg && { echo " built clean (${feats[*]:-no extra features})"; return; } + else + write_patches "$crate" "${shims[@]}" + build_once "--path $ROOT/userland/upstream-src/$crate" "$log" "$bin" $feat_arg \ + && { echo " built with shims: ${shims[*]:-}"; return; } + fi + # A crate whose binary is gated behind a feature (commonly `cli`): enable it + # and retry rather than treat the missing bin as a dead end. + local need; need="$(grep -oE "requires the features: .*" "$log" | head -1 | grep -oE '\`[^\`]+\`' | tr -d '\`' | tr '\n' ' ')" || true + if [ -n "$need" ]; then + feats+=($need); echo " enabling features: $need, retrying..."; continue + fi + local blocker; blocker="$(grep -oE "could not compile \`[^\`]+\`" "$log" | head -1 | sed -E 's/.*`([^`]+)`.*/\1/')" || true + [ -n "$(shim_dir_for "$blocker")" ] \ + || die "$crate needs '${blocker:-unknown}', which has no NONOS shim yet (or is a hard C/GUI/net/spawn dep). Add a shim under userland/upstream-src and a shim_dir_for entry." + # If a shim is already applied yet the same crate still fails, the shim does + # not cover this crate's version. Stop rather than loop applying it forever. + case " ${shims[*]:-} " in + *" $blocker "*) die "$crate still fails on '$blocker' after its shim; the shim likely targets a different version." ;; + esac + [ -z "$vendored" ] && { vendor_app "$crate"; vendored=1; } + shims+=("$blocker") + echo " applying $blocker shim, retrying..." + done + die "$crate did not converge after shims: ${shims[*]:-} features: ${feats[*]:-}" +} + +# Mint the hybrid ed25519 + ML-DSA-65 publisher keypair and publish the pubs. +gen_keys() { + local bin="$1" alg + for alg in ed25519 mldsa65; do + "$CS" keygen --alg "$alg" --out "$ROOT/.keys/${bin}_publisher_$alg" >/dev/null + cp "$ROOT/.keys/${bin}_publisher_$alg.pub" "$ROOT/nonos-data/trust/keys/" + done +} + +# Write the capsule manifest fragment that names the endpoint and points at the +# cross-compiled binary. +gen_capsule_mk() { + local slug="$1" bin="$2" sp="$3" rp="$4" crate="$5" + mkdir -p "$ROOT/userland/capsule_$slug" + cat > "$ROOT/userland/capsule_$slug/Capsule.mk" < "$REGISTRY" <\`, not a hand edit. + +extern crate alloc; + +use alloc::vec; +use alloc::vec::Vec; + +use super::spec::ToolCapsule; +use crate::sys::boot_log; + +/// Every embedded tool capsule, generated from \`userland/apps.list\`. +fn embedded_tools() -> Vec { + vec![ + // nonos-app:begin (generated; do not edit by hand) +$body // nonos-app:end + ] +} + +/// Spawn every embedded tool capsule, verified under the baked trust anchor. One +/// tool failing to spawn is logged and skipped so it never blocks the others. +pub fn spawn_all() { + for tool in embedded_tools() { + match tool.spawn() { + Ok(()) => boot_log::ok("TOOL", tool.name), + Err(_) => boot_log::error("tool capsule spawn failed"), + } + } +} +EOF + echo " registry regenerated from apps.list" +} + +# Regenerate the desktop shell's tool app table so the Launchpad shows one tile +# per installed tool. The shell is a separate capsule and cannot include the +# kernel registry, so it gets its own generated table from the same apps.list. +regen_tool_apps() { + local table="$ROOT/userland/capsule_desktop_shell/src/state/tool_apps.rs" body="" slug bin sp rp n=0 + [ -d "$(dirname "$table")" ] || return 0 + while read -r slug bin sp rp; do + [ -z "${slug:-}" ] && continue + body+=" ToolApp { label: b\"$slug\", service: b\"tool.$slug\" },"$'\n' + n=$((n + 1)) + done < <(grep -vE '^\s*#|^\s*$' "$APPS") + cat > "$table" <\`. + +/// One installed tool: the name on its tile and the service it launches under. +pub struct ToolApp { + pub label: &'static [u8], + pub service: &'static [u8], +} + +/// Every installed tool, generated from \`userland/apps.list\`. +pub const TOOL_APPS: [ToolApp; $n] = [ +$body]; +EOF + echo " desktop tool_apps table regenerated ($n tools)" +} + +cmd_add() { + local crate="$1" bin="${2:-$1}" slug="${2:-$1}" + grep -qE "^$slug " "$APPS" && die "$slug is already installed" + read -r sp rp < <(next_ports) + cross_compile "$crate" "$bin" + gen_keys "$bin" + gen_capsule_mk "$slug" "$bin" "$sp" "$rp" "$crate" + wire_build "$slug" + printf '%s %s %s %s\n' "$slug" "$bin" "$sp" "$rp" >> "$APPS" + regen_registry + regen_tool_apps + echo "installed $slug (ports $sp/$rp). Run: make nonos-mk-desktop-gui-prod NONOS_DEV=1" +} + +cmd_list() { grep -vE '^\s*#|^\s*$' "$APPS" | awk '{printf " %-16s %s:%s\n",$1,$3,$4}'; } + +case "${1:-}" in + add) shift; [ $# -ge 1 ] || die "usage: nonos-app add [bin]"; cmd_add "$@" ;; + regen) regen_registry; regen_tool_apps ;; + list) cmd_list ;; + *) die "usage: nonos-app {add [bin] | regen | list}" ;; +esac diff --git a/userland/apps.list b/userland/apps.list new file mode 100644 index 0000000000..77de7e2ae7 --- /dev/null +++ b/userland/apps.list @@ -0,0 +1,11 @@ +# NONOS installed tool apps. One line per app: slug bin service_port reply_port. +# This is the single source of truth. `tools/nonos-app add ` appends here +# and regenerates the kernel registry; nothing else is hand-edited. +grex grex 4900 4901 +dotenv-linter dotenv-linter 4902 4903 +pastel pastel 4904 4905 +jsonxf jsonxf 4906 4907 +choose choose 4908 4909 +tokei tokei 4910 4911 +huniq huniq 4912 4913 +csview csview 4914 4915 diff --git a/userland/capsule_choose/Capsule.mk b/userland/capsule_choose/Capsule.mk new file mode 100644 index 0000000000..12cbde3707 --- /dev/null +++ b/userland/capsule_choose/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# choose: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := choose +CAPSULE_HANDLE := choose +CAPSULE_DIR := userland/capsule_choose +CAPSULE_BIN_NAME := choose +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.choose +CAPSULE_SERVICE_ENDPOINT := service:4908:tool.choose +CAPSULE_REPLY_ENDPOINT := reply:4909:endpoint.tool.choose.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-choose/bin/choose +CAPSULE_METADATA := crates.io choose publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_csview/Capsule.mk b/userland/capsule_csview/Capsule.mk new file mode 100644 index 0000000000..48d28b7639 --- /dev/null +++ b/userland/capsule_csview/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# csview: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := csview +CAPSULE_HANDLE := csview +CAPSULE_DIR := userland/capsule_csview +CAPSULE_BIN_NAME := csview +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.csview +CAPSULE_SERVICE_ENDPOINT := service:4914:tool.csview +CAPSULE_REPLY_ENDPOINT := reply:4915:endpoint.tool.csview.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-csview/bin/csview +CAPSULE_METADATA := crates.io csview publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_desktop_shell/src/state/tool_apps.rs b/userland/capsule_desktop_shell/src/state/tool_apps.rs index e75d3f64c8..3b4862220d 100644 --- a/userland/capsule_desktop_shell/src/state/tool_apps.rs +++ b/userland/capsule_desktop_shell/src/state/tool_apps.rs @@ -2,8 +2,9 @@ // Copyright (C) 2026 NONOS Contributors // SPDX-License-Identifier: AGPL-3.0-or-later -//! Installed command-line tools shown in the Launchpad. Empty until the -//! nonos-app pipeline installs some; the Launchpad shows the desktop apps. +//! The installed command-line tools shown in the Launchpad, generated by +//! `tools/nonos-app` from `userland/apps.list`. Each tile launches the tool +//! capsule by its service. Do not edit by hand: run `nonos-app add `. /// One installed tool: the name on its tile and the service it launches under. pub struct ToolApp { @@ -11,5 +12,14 @@ pub struct ToolApp { pub service: &'static [u8], } -/// Every installed tool. None yet, so the Launchpad grid is the desktop apps. -pub const TOOL_APPS: [ToolApp; 0] = []; +/// Every installed tool, generated from `userland/apps.list`. +pub const TOOL_APPS: [ToolApp; 8] = [ + ToolApp { label: b"grex", service: b"tool.grex" }, + ToolApp { label: b"dotenv-linter", service: b"tool.dotenv-linter" }, + ToolApp { label: b"pastel", service: b"tool.pastel" }, + ToolApp { label: b"jsonxf", service: b"tool.jsonxf" }, + ToolApp { label: b"choose", service: b"tool.choose" }, + ToolApp { label: b"tokei", service: b"tool.tokei" }, + ToolApp { label: b"huniq", service: b"tool.huniq" }, + ToolApp { label: b"csview", service: b"tool.csview" }, +]; diff --git a/userland/capsule_dotenv-linter/Capsule.mk b/userland/capsule_dotenv-linter/Capsule.mk new file mode 100644 index 0000000000..f07a040faf --- /dev/null +++ b/userland/capsule_dotenv-linter/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# dotenv-linter: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := dotenv-linter +CAPSULE_HANDLE := dotenv-linter +CAPSULE_DIR := userland/capsule_dotenv-linter +CAPSULE_BIN_NAME := dotenv-linter +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.dotenv-linter +CAPSULE_SERVICE_ENDPOINT := service:4902:tool.dotenv-linter +CAPSULE_REPLY_ENDPOINT := reply:4903:endpoint.tool.dotenv-linter.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-dotenv-linter/bin/dotenv-linter +CAPSULE_METADATA := crates.io dotenv-linter publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_grex/Capsule.mk b/userland/capsule_grex/Capsule.mk new file mode 100644 index 0000000000..9c43c90443 --- /dev/null +++ b/userland/capsule_grex/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# grex: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := grex +CAPSULE_HANDLE := grex +CAPSULE_DIR := userland/capsule_grex +CAPSULE_BIN_NAME := grex +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.grex +CAPSULE_SERVICE_ENDPOINT := service:4900:tool.grex +CAPSULE_REPLY_ENDPOINT := reply:4901:endpoint.tool.grex.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-grex/bin/grex +CAPSULE_METADATA := crates.io grex publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_huniq/Capsule.mk b/userland/capsule_huniq/Capsule.mk new file mode 100644 index 0000000000..456b94bd89 --- /dev/null +++ b/userland/capsule_huniq/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# huniq: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := huniq +CAPSULE_HANDLE := huniq +CAPSULE_DIR := userland/capsule_huniq +CAPSULE_BIN_NAME := huniq +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.huniq +CAPSULE_SERVICE_ENDPOINT := service:4912:tool.huniq +CAPSULE_REPLY_ENDPOINT := reply:4913:endpoint.tool.huniq.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-huniq/bin/huniq +CAPSULE_METADATA := crates.io huniq publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_jsonxf/Capsule.mk b/userland/capsule_jsonxf/Capsule.mk new file mode 100644 index 0000000000..8eefb6c7d3 --- /dev/null +++ b/userland/capsule_jsonxf/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# jsonxf: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := jsonxf +CAPSULE_HANDLE := jsonxf +CAPSULE_DIR := userland/capsule_jsonxf +CAPSULE_BIN_NAME := jsonxf +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.jsonxf +CAPSULE_SERVICE_ENDPOINT := service:4906:tool.jsonxf +CAPSULE_REPLY_ENDPOINT := reply:4907:endpoint.tool.jsonxf.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-jsonxf/bin/jsonxf +CAPSULE_METADATA := crates.io jsonxf publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_pastel/Capsule.mk b/userland/capsule_pastel/Capsule.mk new file mode 100644 index 0000000000..ec8801769a --- /dev/null +++ b/userland/capsule_pastel/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# pastel: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := pastel +CAPSULE_HANDLE := pastel +CAPSULE_DIR := userland/capsule_pastel +CAPSULE_BIN_NAME := pastel +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.pastel +CAPSULE_SERVICE_ENDPOINT := service:4904:tool.pastel +CAPSULE_REPLY_ENDPOINT := reply:4905:endpoint.tool.pastel.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-pastel/bin/pastel +CAPSULE_METADATA := crates.io pastel publisher + +include nonos-mk/capsule.mk diff --git a/userland/capsule_tokei/Capsule.mk b/userland/capsule_tokei/Capsule.mk new file mode 100644 index 0000000000..0a2f9bfff1 --- /dev/null +++ b/userland/capsule_tokei/Capsule.mk @@ -0,0 +1,21 @@ +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# tokei: unmodified crates.io source cross-compiled for x86_64-nonos and +# installed as a signed, STARK-attested capsule. Generated by tools/nonos-app. + +CAPSULE_SLUG := tokei +CAPSULE_HANDLE := tokei +CAPSULE_DIR := userland/capsule_tokei +CAPSULE_BIN_NAME := tokei +CAPSULE_DOMAIN := crates.io +CAPSULE_NAMESPACE := systems.nonos.tool.tokei +CAPSULE_SERVICE_ENDPOINT := service:4910:tool.tokei +CAPSULE_REPLY_ENDPOINT := reply:4911:endpoint.tool.tokei.reply +CAPSULE_REQUIRED_CAPS := 0x19 +CAPSULE_CAPS_CEILING := 0x19 +CAPSULE_PREBUILT_BIN := target/upstream-tokei/bin/tokei +CAPSULE_METADATA := crates.io tokei publisher + +include nonos-mk/capsule.mk diff --git a/userland/upstream-src/atty-0.2.14/.cargo-ok b/userland/upstream-src/atty-0.2.14/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/atty-0.2.14/.cargo_vcs_info.json b/userland/upstream-src/atty-0.2.14/.cargo_vcs_info.json new file mode 100644 index 0000000000..b4096169ee --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "7b5df17888997d57c2c1c8f91da1db5691f49953" + } +} diff --git a/userland/upstream-src/atty-0.2.14/.gitignore b/userland/upstream-src/atty-0.2.14/.gitignore new file mode 100644 index 0000000000..afc9901273 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +*.bk diff --git a/userland/upstream-src/atty-0.2.14/CHANGELOG.md b/userland/upstream-src/atty-0.2.14/CHANGELOG.md new file mode 100644 index 0000000000..4e9673f0de --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/CHANGELOG.md @@ -0,0 +1,73 @@ +# 0.2.14 + +* add support for [RustyHermit](https://github.com/hermitcore/libhermit-rs), a Rust-based unikernel [#41](https://github.com/softprops/atty/pull/41) + +# 0.2.13 + +* support older versions of rust that do now support 2018 edition + +# 0.2.12 + +* Redox is now in the unix family so redox cfg is no longer needed [#35](https://github.com/softprops/atty/pull/35) + +# 0.2.11 + +* fix msys detection with `winapi@0.3.5` [#28](https://github.com/softprops/atty/pull/28) + +# 0.2.10 + +* fix wasm regression [#27](https://github.com/softprops/atty/pull/27) + +# 0.2.9 + +* Fix fix pty detection [#25](https://github.com/softprops/atty/pull/25) + +# 0.2.8 + +* Fix an inverted condition on MinGW [#22](https://github.com/softprops/atty/pull/22) + +# 0.2.7 + +* Change `||` to `&&` for whether MSYS is a tty [#24](https://github.com/softprops/atty/pull/24/) + +# 0.2.6 + +* updated winapi dependency to [0.3](https://retep998.github.io/blog/winapi-0.3/) [#18](https://github.com/softprops/atty/pull/18) + +# 0.2.5 + +* added support for Wasm compile targets [#17](https://github.com/softprops/atty/pull/17) + +# 0.2.4 + +* added support for Wasm compile targets [#17](https://github.com/softprops/atty/pull/17) + +# 0.2.3 + +* added support for Redox OS [#14](https://github.com/softprops/atty/pull/14) + +# 0.2.2 + +* use target specific dependencies [#11](https://github.com/softprops/atty/pull/11) +* Add tty detection for MSYS terminals [#12](https://github.com/softprops/atty/pull/12) + +# 0.2.1 + +* fix windows bug + +# 0.2.0 + +* support for various stream types + +# 0.1.2 + +* windows support (with automated testing) +* automated code coverage + +# 0.1.1 + +* bumped libc dep from `0.1` to `0.2` + +# 0.1.0 + +* initial release diff --git a/userland/upstream-src/atty-0.2.14/Cargo.toml b/userland/upstream-src/atty-0.2.14/Cargo.toml new file mode 100644 index 0000000000..d6bf2d03b3 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/Cargo.toml @@ -0,0 +1,34 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "atty" +version = "0.2.14" +authors = ["softprops "] +exclude = ["/.travis.yml", "/appveyor.yml"] +description = "A simple interface for querying atty" +homepage = "https://github.com/softprops/atty" +documentation = "http://softprops.github.io/atty" +readme = "README.md" +keywords = ["terminal", "tty", "isatty"] +license = "MIT" +repository = "https://github.com/softprops/atty" +[target."cfg(target_os = \"hermit\")".dependencies.hermit-abi] +version = "0.1.6" +[target."cfg(unix)".dependencies.libc] +version = "0.2" +default-features = false +[target."cfg(windows)".dependencies.winapi] +version = "0.3" +features = ["consoleapi", "processenv", "minwinbase", "minwindef", "winbase"] +[badges.travis-ci] +repository = "softprops/atty" diff --git a/userland/upstream-src/atty-0.2.14/Cargo.toml.orig b/userland/upstream-src/atty-0.2.14/Cargo.toml.orig new file mode 100644 index 0000000000..aa2cbb77e1 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/Cargo.toml.orig @@ -0,0 +1,26 @@ +[package] +name = "atty" +version = "0.2.14" +authors = ["softprops "] +description = "A simple interface for querying atty" +documentation = "http://softprops.github.io/atty" +homepage = "https://github.com/softprops/atty" +repository = "https://github.com/softprops/atty" +keywords = ["terminal", "tty", "isatty"] +license = "MIT" +readme = "README.md" +exclude = ["/.travis.yml", "/appveyor.yml"] + +[badges] +travis-ci = { repository = "softprops/atty" } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2", default-features = false } + +[target.'cfg(target_os = "hermit")'.dependencies] +hermit-abi = "0.1.6" + +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = ["consoleapi", "processenv", "minwinbase", "minwindef", "winbase"] + diff --git a/userland/upstream-src/atty-0.2.14/LICENSE b/userland/upstream-src/atty-0.2.14/LICENSE new file mode 100644 index 0000000000..b9da76b735 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-2019 Doug Tangren + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/atty-0.2.14/README.md b/userland/upstream-src/atty-0.2.14/README.md new file mode 100644 index 0000000000..cd85593d6e --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/README.md @@ -0,0 +1,74 @@ +# atty + +[![Build Status](https://travis-ci.org/softprops/atty.svg?branch=master)](https://travis-ci.org/softprops/atty) [![Build status](https://ci.appveyor.com/api/projects/status/geggrsnsjsuse8cv?svg=true)](https://ci.appveyor.com/project/softprops/atty) [![Coverage Status](https://coveralls.io/repos/softprops/atty/badge.svg?branch=master&service=github)](https://coveralls.io/github/softprops/atty?branch=master) [![crates.io](https://img.shields.io/crates/v/atty.svg)](https://crates.io/crates/atty) [![Released API docs](https://docs.rs/atty/badge.svg)](http://docs.rs/atty) [![Master API docs](https://img.shields.io/badge/docs-master-green.svg)](https://softprops.github.io/atty) + +> are you or are you not a tty? + + +## install + +Add the following to your `Cargo.toml` + +```toml +[dependencies] +atty = "0.2" +``` + +## usage + +```rust +use atty::Stream; + +fn main() { + if atty::is(Stream::Stdout) { + println!("I'm a terminal"); + } else { + println!("I'm not"); + } +} +``` + +## testing + +This library has been unit tested on both unix and windows platforms (via appveyor). + + +A simple example program is provided in this repo to test various tty's. By default. + +It prints + +```bash +$ cargo run --example atty +stdout? true +stderr? true +stdin? true +``` + +To test std in, pipe some text to the program + +```bash +$ echo "test" | cargo run --example atty +stdout? true +stderr? true +stdin? false +``` + +To test std out, pipe the program to something + +```bash +$ cargo run --example atty | grep std +stdout? false +stderr? true +stdin? true +``` + +To test std err, pipe the program to something redirecting std err + +```bash +$ cargo run --example atty 2>&1 | grep std +stdout? false +stderr? false +stdin? true +``` + +Doug Tangren (softprops) 2015-2019 diff --git a/userland/upstream-src/atty-0.2.14/examples/atty.rs b/userland/upstream-src/atty-0.2.14/examples/atty.rs new file mode 100644 index 0000000000..3b3635e59c --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/examples/atty.rs @@ -0,0 +1,9 @@ +extern crate atty; + +use atty::{is, Stream}; + +fn main() { + println!("stdout? {}", is(Stream::Stdout)); + println!("stderr? {}", is(Stream::Stderr)); + println!("stdin? {}", is(Stream::Stdin)); +} diff --git a/userland/upstream-src/atty-0.2.14/rustfmt.toml b/userland/upstream-src/atty-0.2.14/rustfmt.toml new file mode 100644 index 0000000000..899a094cd5 --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/rustfmt.toml @@ -0,0 +1,4 @@ +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_args_layout +fn_args_layout = "Vertical" +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports +merge_imports = true \ No newline at end of file diff --git a/userland/upstream-src/atty-0.2.14/src/lib.rs b/userland/upstream-src/atty-0.2.14/src/lib.rs new file mode 100644 index 0000000000..dd8b652cff --- /dev/null +++ b/userland/upstream-src/atty-0.2.14/src/lib.rs @@ -0,0 +1,218 @@ +//! atty is a simple utility that answers one question +//! > is this a tty? +//! +//! usage is just as simple +//! +//! ``` +//! if atty::is(atty::Stream::Stdout) { +//! println!("i'm a tty") +//! } +//! ``` +//! +//! ``` +//! if atty::isnt(atty::Stream::Stdout) { +//! println!("i'm not a tty") +//! } +//! ``` + +#![cfg_attr(unix, no_std)] + +#[cfg(unix)] +extern crate libc; +#[cfg(windows)] +extern crate winapi; + +#[cfg(windows)] +use winapi::shared::minwindef::DWORD; +#[cfg(windows)] +use winapi::shared::ntdef::WCHAR; + +/// possible stream sources +#[derive(Clone, Copy, Debug)] +pub enum Stream { + Stdout, + Stderr, + Stdin, +} + +/// returns true if this is a tty +#[cfg(all(unix, not(target_arch = "wasm32")))] +pub fn is(stream: Stream) -> bool { + extern crate libc; + + let fd = match stream { + Stream::Stdout => libc::STDOUT_FILENO, + Stream::Stderr => libc::STDERR_FILENO, + Stream::Stdin => libc::STDIN_FILENO, + }; + unsafe { libc::isatty(fd) != 0 } +} + +/// returns true if this is a tty +#[cfg(target_os = "hermit")] +pub fn is(stream: Stream) -> bool { + extern crate hermit_abi; + + let fd = match stream { + Stream::Stdout => hermit_abi::STDOUT_FILENO, + Stream::Stderr => hermit_abi::STDERR_FILENO, + Stream::Stdin => hermit_abi::STDIN_FILENO, + }; + hermit_abi::isatty(fd) +} + +/// returns true if this is a tty +#[cfg(windows)] +pub fn is(stream: Stream) -> bool { + use winapi::um::winbase::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, + }; + + let (fd, others) = match stream { + Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]), + Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]), + Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]), + }; + if unsafe { console_on_any(&[fd]) } { + // False positives aren't possible. If we got a console then + // we definitely have a tty on stdin. + return true; + } + + // At this point, we *could* have a false negative. We can determine that + // this is true negative if we can detect the presence of a console on + // any of the other streams. If another stream has a console, then we know + // we're in a Windows console and can therefore trust the negative. + if unsafe { console_on_any(&others) } { + return false; + } + + // Otherwise, we fall back to a very strange msys hack to see if we can + // sneakily detect the presence of a tty. + unsafe { msys_tty_on(fd) } +} + +/// returns true if this is _not_ a tty +pub fn isnt(stream: Stream) -> bool { + !is(stream) +} + +/// Returns true if any of the given fds are on a console. +#[cfg(windows)] +unsafe fn console_on_any(fds: &[DWORD]) -> bool { + use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; + + for &fd in fds { + let mut out = 0; + let handle = GetStdHandle(fd); + if GetConsoleMode(handle, &mut out) != 0 { + return true; + } + } + false +} + +/// Returns true if there is an MSYS tty on the given handle. +#[cfg(windows)] +unsafe fn msys_tty_on(fd: DWORD) -> bool { + use std::{mem, slice}; + + use winapi::{ + ctypes::c_void, + shared::minwindef::MAX_PATH, + um::{ + fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle, + winbase::GetFileInformationByHandleEx, + }, + }; + + let size = mem::size_of::(); + let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; + let res = GetFileInformationByHandleEx( + GetStdHandle(fd), + FileNameInfo, + &mut *name_info_bytes as *mut _ as *mut c_void, + name_info_bytes.len() as u32, + ); + if res == 0 { + return false; + } + let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); + let s = slice::from_raw_parts( + name_info.FileName.as_ptr(), + name_info.FileNameLength as usize / 2, + ); + let name = String::from_utf16_lossy(s); + // This checks whether 'pty' exists in the file name, which indicates that + // a pseudo-terminal is attached. To mitigate against false positives + // (e.g., an actual file name that contains 'pty'), we also require that + // either the strings 'msys-' or 'cygwin-' are in the file name as well.) + let is_msys = name.contains("msys-") || name.contains("cygwin-"); + let is_pty = name.contains("-pty"); + is_msys && is_pty +} + +/// returns true if this is a tty +#[cfg(target_arch = "wasm32")] +pub fn is(_stream: Stream) -> bool { + false +} + +/// returns true if this is a tty. On NONOS a capsule's stdio is wired to the +/// nox terminal, so it is always a tty and tools enable color and interactive +/// output. +#[cfg(target_vendor = "nonos")] +pub fn is(_stream: Stream) -> bool { + true +} + +#[cfg(test)] +mod tests { + use super::{is, Stream}; + + #[test] + #[cfg(windows)] + fn is_err() { + // appveyor pipes its output + assert!(!is(Stream::Stderr)) + } + + #[test] + #[cfg(windows)] + fn is_out() { + // appveyor pipes its output + assert!(!is(Stream::Stdout)) + } + + #[test] + #[cfg(windows)] + fn is_in() { + assert!(is(Stream::Stdin)) + } + + #[test] + #[cfg(unix)] + fn is_err() { + assert!(is(Stream::Stderr)) + } + + #[test] + #[cfg(unix)] + fn is_out() { + assert!(is(Stream::Stdout)) + } + + #[test] + #[cfg(target_os = "macos")] + fn is_in() { + // macos on travis seems to pipe its input + assert!(is(Stream::Stdin)) + } + + #[test] + #[cfg(all(not(target_os = "macos"), unix))] + fn is_in() { + assert!(is(Stream::Stdin)) + } +} diff --git a/userland/upstream-src/choose/.cargo-ok b/userland/upstream-src/choose/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/choose/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/choose/.cargo_vcs_info.json b/userland/upstream-src/choose/.cargo_vcs_info.json new file mode 100644 index 0000000000..192a227c55 --- /dev/null +++ b/userland/upstream-src/choose/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "8d64b136406da28fb9b48db8506578aef1c221b3" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/choose/.github/workflows/release.yml b/userland/upstream-src/choose/.github/workflows/release.yml new file mode 100644 index 0000000000..9c59e0b16b --- /dev/null +++ b/userland/upstream-src/choose/.github/workflows/release.yml @@ -0,0 +1,134 @@ +name: Create Releases + +on: + push: + tags: + - v* + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Check style + run: cargo fmt -- --check + + - name: Run unit tests + run: cargo test --verbose + + - name: Rust end-to-end test + run: test/e2e_test.sh + + - name: Build x86_64-unknown-linux-gnu + run: cargo build --verbose --release --target x86_64-unknown-linux-gnu + + - name: Build x86_64-unknown-linux-musl + run: | + rustup target add x86_64-unknown-linux-musl + cargo build --verbose --release --target x86_64-unknown-linux-musl + + - name: Build aarch64-unknown-linux-gnu + run: | + sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu + rustup target add aarch64-unknown-linux-gnu + cargo build --verbose --release --target aarch64-unknown-linux-gnu --config target.aarch64-unknown-linux-gnu.linker=\"aarch64-linux-gnu-gcc\" + + - name: Build aarch64-apple-darwin + run: | + curl -L https://github.com/roblabla/MacOSX-SDKs/releases/download/13.3/MacOSX13.3.sdk.tar.xz | tar xJ + export SDKROOT=$(pwd)/MacOSX13.3.sdk/ + export PATH=$PATH:~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/ + export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=rust-lld + rustup target add aarch64-apple-darwin + cargo build --verbose --release --target aarch64-apple-darwin + rm -rf $SDKROOT + + - name: Build x86_64-pc-windows-gnu + run: | + sudo apt-get update && sudo apt-get install -y mingw-w64 + rustup target add x86_64-pc-windows-gnu + cargo build --verbose --release --target x86_64-pc-windows-gnu + + - name: Create GitHub release + id: create-release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: '' + draft: false + prerelease: false + + - name: Upload binary to GitHub release + id: upload-x86_64-unknown-linux-gnu-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/x86_64-unknown-linux-gnu/release/choose + asset_name: choose-x86_64-unknown-linux-gnu + asset_content_type: application/raw + + - name: Upload musl binary to GitHub release + id: upload-x86_64-unknown-linux-musl-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/x86_64-unknown-linux-musl/release/choose + asset_name: choose-x86_64-unknown-linux-musl + asset_content_type: application/raw + + - name: Upload aarch64 linux binary to GitHub release + id: upload-aarch64-unknown-linux-gnu-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/aarch64-unknown-linux-gnu/release/choose + asset_name: choose-aarch64-unknown-linux-gnu + asset_content_type: application/raw + + - name: Upload aarch64 darwin binary to GitHub release + id: upload-aarch64-apple-darwin-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/aarch64-apple-darwin/release/choose + asset_name: choose-aarch64-apple-darwin + asset_content_type: application/raw + + - name: Upload mingw binary to GitHub release + id: upload-x86_64-pc-windows-gnu-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/x86_64-pc-windows-gnu/release/choose.exe + asset_name: choose-x86_64-pc-windows-gnu + asset_content_type: application/raw + + - name: Upload mingw .exe binary to GitHub release + id: upload-x86_64-pc-windows-gnu-release-asset-exe + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: target/x86_64-pc-windows-gnu/release/choose.exe + asset_name: choose-x86_64-pc-windows-gnu.exe + asset_content_type: application/raw + + - name: Create crates.io release + run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} diff --git a/userland/upstream-src/choose/.github/workflows/rust.yml b/userland/upstream-src/choose/.github/workflows/rust.yml new file mode 100644 index 0000000000..af1c9d9a9d --- /dev/null +++ b/userland/upstream-src/choose/.github/workflows/rust.yml @@ -0,0 +1,35 @@ +name: Rust + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Check style + run: cargo fmt -- --check + + # this will report success even if there are warnings, and should be manually checked for now + - name: Lint + run: cargo clippy + + - name: Build + run: cargo build --verbose + + - name: Run unit tests + run: cargo test --verbose + + - name: Run end-to-end tests + run: test/e2e_test.sh + diff --git a/userland/upstream-src/choose/.gitignore b/userland/upstream-src/choose/.gitignore new file mode 100644 index 0000000000..24e2899400 --- /dev/null +++ b/userland/upstream-src/choose/.gitignore @@ -0,0 +1,9 @@ +/target +**/*.rs.bk +tags +todo +bench_output/ +**/*.bench +**/*.svg +test/long*txt +perf.data* diff --git a/userland/upstream-src/choose/Cargo.lock b/userland/upstream-src/choose/Cargo.lock new file mode 100644 index 0000000000..c01b6dd18a --- /dev/null +++ b/userland/upstream-src/choose/Cargo.lock @@ -0,0 +1,278 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "backslash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9fc0782bff70e4ea109606bc4e37162191a39a513416ae04c8fcadc983e42d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "choose" +version = "1.3.7" +dependencies = [ + "backslash", + "lazy_static", + "regex", + "structopt", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/userland/upstream-src/choose/Cargo.toml b/userland/upstream-src/choose/Cargo.toml new file mode 100644 index 0000000000..e7fa3d9814 --- /dev/null +++ b/userland/upstream-src/choose/Cargo.toml @@ -0,0 +1,49 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "choose" +version = "1.3.7" +authors = ["Ryan Geary "] +build = false +exclude = ["test/*"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A human-friendly and fast alternative to cut and (sometimes) awk" +homepage = "https://github.com/theryangeary/choose" +documentation = "https://github.com/theryangeary/choose" +readme = "readme.md" +license = "GPL-3.0-or-later" +repository = "https://github.com/theryangeary/choose" + +[[bin]] +name = "choose" +path = "src/main.rs" + +[dependencies.backslash] +version = "0" + +[dependencies.lazy_static] +version = "1" + +[dependencies.regex] +version = "1" + +[dependencies.structopt] +version = "0.3" + +# nonos-app-patch +[patch.crates-io] +atty = { path = "../atty-0.2.14" } diff --git a/userland/upstream-src/choose/Cargo.toml.orig b/userland/upstream-src/choose/Cargo.toml.orig new file mode 100644 index 0000000000..d8bd015bad --- /dev/null +++ b/userland/upstream-src/choose/Cargo.toml.orig @@ -0,0 +1,22 @@ +[package] +name = "choose" +version = "1.3.7" +authors = ["Ryan Geary "] +edition = "2021" +license = "GPL-3.0-or-later" +description = "A human-friendly and fast alternative to cut and (sometimes) awk" +homepage = "https://github.com/theryangeary/choose" +documentation = "https://github.com/theryangeary/choose" +repository = "https://github.com/theryangeary/choose" +readme = "readme.md" +exclude = [ + "test/*", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +structopt = "0.3" +regex = "1" +lazy_static = "1" +backslash = "0" diff --git a/userland/upstream-src/choose/LICENSE b/userland/upstream-src/choose/LICENSE new file mode 100644 index 0000000000..f288702d2f --- /dev/null +++ b/userland/upstream-src/choose/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/userland/upstream-src/choose/Makefile b/userland/upstream-src/choose/Makefile new file mode 100644 index 0000000000..bed925d4ca --- /dev/null +++ b/userland/upstream-src/choose/Makefile @@ -0,0 +1,27 @@ +.PHONY: release +release: + cargo build --release + +.PHONY: release-debug +release-debug: + RUSTFLAGS=-g cargo build --release + +flamegraph: release-debug + perf record --call-graph dwarf,16384 -e cpu-clock -F 997 target/release/choose -i test/long_long_long_long.txt 3:5 + perf script | stackcollapse-perf.pl | stackcollapse-recursive.pl | c++filt | flamegraph.pl > flamegraphs/working.svg + +flamegraph_commit: release-debug + perf record --call-graph dwarf,16384 -e cpu-clock -F 997 target/release/choose -i test/long_long_long_long.txt 3:5 + perf script | stackcollapse-perf.pl | stackcollapse-recursive.pl | c++filt | flamegraph.pl > flamegraphs/`git log -n 1 --pretty=format:"%h"`.svg + +.PHONY: test +test: + cargo test + test/e2e_test.sh + +bench: release + test/bench.sh working + +bench_commit: release + test/bench.sh `git log -n 1 --pretty=format:"%h"` + diff --git a/userland/upstream-src/choose/contributing.md b/userland/upstream-src/choose/contributing.md new file mode 100644 index 0000000000..0e63d0fcdf --- /dev/null +++ b/userland/upstream-src/choose/contributing.md @@ -0,0 +1,116 @@ +# Contributing + +Thank you for considering contributing to `choose`! + +To save your time and mine, I will attempt to maintain brevity in this +document, adding more details where there are common questions or +misunderstandings. + +## Where To Start + +If you have found a bug or would like to request a feature, [open an +issue](https://github.com/theryangeary/choose/issues/new). + +It is best if you get confirmation of your bug or approval for a feature +request before taking the time to write the code. + +## Fork && Create Branch + +If you have confirmation/approval and would like to try your hand at making the +change, [fork `choose`](https://help.github.com/articles/fork-a-repo) and create +a branch with a descriptive name. + +Branch off of `develop`. Bug fix branches should be named +`hotfix/` and feature branches should be named +`feature/`. **Any hotfix or feature branch should only address +one issue/feature**. + +```sh +git checkout -b develop +``` + +## Check The Test Suite + +Before making any changes, make sure that both the unit tests and the end-to-end +tests all work. + +```sh +cargo test +test/e2e_test.sh +``` + +If you are planning on making changes that may affect performance, consider +using the benchmark script `test/bench.sh` as well. + +## Implement Your Fix/Feature + +### Write tests + +Tests are important. + +If you are fixing a bug, add tests that identify that bug and any permutations +of it you can find, so we can ensure it doesn't come back. + +If you are creating a feature, add tests that will comprehensively ensure the +feature works as expected, in conjunction with all other features, switches, +options, etc. + +### Write code + +It should be correct. It should be fast. It should be idiomatic. Ask for help if +you need it, don't be shy. + +### Write documentation (if needed) + +If your feature adds a new command line switch or option, add that info to the +readme or any other relevant locations. + +## Make a Pull Request + +Once you've finished your changes, make sure that your develop branch is up to +date. + +```sh +git remote add upstream git@github.com:theryangeary/choose.git +git checkout develop +git pull upstream develop +``` + +Check that your code is all formatted correctly. If not, commit any changes. + +```sh +git checkout +cargo fmt +git status +``` + +Rebase and squash your branch on develop. This will prompt you with a list of +your commits. Change all but the first commit to "squash". Write a nice +changelog message in the resulting commit. + +```sh +git rebase -i develop +``` + +Push to your fork. + +```sh +git push --set-upstream origin +``` + +Go to GitHub and [make a Pull +Request](https://help.github.com/articles/creating-a-pull-request)! Make sure +that your Pull Request is against `develop` and not `master`! + +## Keep Your Pull Request Updated + +After making your Pull Request, you may be asked to make some changes. After +completing and commiting your changes, you will need to rebase and resquash your +commits. Each Pull Request will effectively be a single commit added to the +`develop` branch. + +After changing and committing, push like this: + +```sh +git push --force-with-lease +``` diff --git a/userland/upstream-src/choose/readme.md b/userland/upstream-src/choose/readme.md new file mode 100644 index 0000000000..9d4b4503d5 --- /dev/null +++ b/userland/upstream-src/choose/readme.md @@ -0,0 +1,182 @@ +# Choose + +This is `choose`, a human-friendly and fast alternative to `cut` and (sometimes) `awk` + +[![`choose` demo](https://asciinema.org/a/315932.png)](https://asciinema.org/a/315932?autoplay=1) + +## Features + +- terse field selection syntax similar to Python's list slices +- negative indexing from end of line +- optional start/end index +- zero-indexed +- reverse ranges +- slightly faster than `cut` for sufficiently long inputs, much faster than + `awk` +- regular expression field separators using Rust's regex syntax + +## Rationale + +The AWK programming language is designed for text processing and is extremely +capable in this endeavor. However, the `awk` command is not ideal for rapid +shell use, with its requisite quoting of a line wrapped in curly braces, even +for the simplest of programs: + +```bash +awk '{print $1}' +``` + +Likewise, `cut` is far from ideal for rapid shell use, because of its confusing +syntax. Field separators and ranges are just plain difficult to get right on the +first try. + +It is for these reasons that I present to you `choose`. It is not meant to be a +drop-in or complete replacement for either of the aforementioned tools, but +rather a simple and intuitive tool to reach for when the basics of `awk` or +`cut` will do, but the overhead of getting them to behave should not be +necessary. + +## Contributing + +Please see our guidelines in [contributing.md](contributing.md). + +## Usage + +``` +$ choose --help +choose 1.2.0 +`choose` sections from each line of files + +USAGE: + choose [FLAGS] [OPTIONS] ... + +FLAGS: + -c, --character-wise Choose fields by character number + -d, --debug Activate debug mode + -x, --exclusive Use exclusive ranges, similar to array indexing in many programming languages + -h, --help Prints help information + -n, --non-greedy Use non-greedy field separators + -V, --version Prints version information + +OPTIONS: + -f, --field-separator + Specify field separator other than whitespace, using Rust `regex` syntax + + -i, --input Input file + -o, --output-field-separator Specify output field separator + +ARGS: + ... Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning or end + of a range can be omitted, resulting in including the beginning or end of the line, + respectively. a:b is inclusive of b (unless overridden by -x). a..b is exclusive of b and a..=b + is inclusive of b +``` + +### Examples + +```bash +choose 5 # print the 5th item from a line (zero indexed) + +choose -f ':' 0 3 5 # print the 0th, 3rd, and 5th item from a line, where + # items are separated by ':' instead of whitespace + +choose 2:5 # print everything from the 2nd to 5th item on the line, + # inclusive of the 5th + +choose -x 2:5 # print everything from the 2nd to 5th item on the line, + # exclusive of the 5th + +choose :3 # print the beginning of the line to the 3rd item + +choose -x :3 # print the beginning of the line to the 3rd item, + # exclusive + +choose 3: # print the third item to the end of the line + +choose -1 # print the last item from a line + +choose -3:-1 # print the last three items from a line +``` + +## Compilation and Installation + +### Installing From Source + +In order to build `choose` you will need the rust toolchain installed. You can +find instructions [here](https://www.rust-lang.org/tools/install). + +Then, to install: + +```bash +git clone https://github.com/theryangeary/choose.git +cd choose +cargo build --release +install target/release/choose +``` + +Just make sure DESTDIR is in your path. + +### Installing From Package Managers + +Cargo: + +```sh +cargo install choose +``` + +Arch Linux: + +```sh +yay -S choose-rust-git +``` + +Fedora/CentOS [COPR](https://copr.fedorainfracloud.org/coprs/atim/choose/): + +```sh +dnf copr enable atim/choose +dnf install choose +``` + +Homebrew: + +```sh +brew install choose-rust +``` + +MacPorts: + +```sh +sudo port install choose +``` + +### Benchmarking + +Benchmarking is performed using the [`bench` utility](https://github.com/Gabriel439/bench). + +Benchmarking is based on the assumption that there are five files in `test/` +that match the glob "long*txt". GitHub doesn't support files big enough in +normal repos, but for reference the files I'm working with have lengths like +these: + +```sh + 1000 test/long.txt + 19272 test/long_long.txt + 96360 test/long_long_long.txt + 963600 test/long_long_long_long.txt + 10599600 test/long_long_long_long_long.txt +``` + +and content generally like this: + +``` +Those an equal point no years do. Depend warmth fat but her but played. Shy and +subjects wondered trifling pleasant. Prudent cordial comfort do no on colonel as +assured chicken. Smart mrs day which begin. Snug do sold mr it if such. +Terminated uncommonly at at estimating. Man behaviour met moonlight extremity +acuteness direction. + +Ignorant branched humanity led now marianne too strongly entrance. Rose to shew +bore no ye of paid rent form. Old design are dinner better nearer silent excuse. +She which are maids boy sense her shade. Considered reasonable we affronting on +expression in. So cordial anxious mr delight. Shot his has must wish from sell +``` diff --git a/userland/upstream-src/choose/src/choice/mod.rs b/userland/upstream-src/choose/src/choice/mod.rs new file mode 100644 index 0000000000..c80bf4bf1c --- /dev/null +++ b/userland/upstream-src/choose/src/choice/mod.rs @@ -0,0 +1,263 @@ +use std::convert::TryInto; + +use crate::config::Config; +use crate::error::Error; +use crate::result::Result; +use crate::writeable::Writeable; +use crate::writer::WriteReceiver; + +#[cfg(test)] +mod test; + +#[derive(Debug)] +pub struct Choice { + pub start: isize, + pub end: isize, + pub kind: ChoiceKind, + negative_index: bool, + reversed: bool, +} + +#[derive(Debug, PartialEq)] +pub enum ChoiceKind { + Single, + RustExclusiveRange, + RustInclusiveRange, + ColonRange, +} + +impl Choice { + pub fn new(start: isize, end: isize, kind: ChoiceKind) -> Self { + let negative_index = start < 0 || end < 0; + let reversed = end < start && !(start >= 0 && end < 0); + Choice { + start, + end, + kind, + negative_index, + reversed, + } + } + + pub fn print_choice( + &self, + line: &str, + config: &Config, + handle: &mut W, + ) -> Result<()> { + if config.opt.character_wise { + self.print_choice_generic(line.chars(), config, handle) + } else { + let line_iter = config + .separator + .split(line) + .filter(|s| !s.is_empty() || config.opt.non_greedy); + self.print_choice_generic(line_iter, config, handle) + } + } + + pub fn is_reverse_range(&self) -> bool { + self.reversed + } + + pub fn has_negative_index(&self) -> bool { + self.negative_index + } + + fn print_choice_generic( + &self, + mut iter: I, + config: &Config, + handle: &mut W, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator, + { + if self.is_reverse_range() && !self.has_negative_index() { + self.print_choice_reverse(iter, config, handle)?; + } else if self.has_negative_index() { + self.print_choice_negative(iter, config, handle)?; + } else { + if self.start > 0 { + iter.nth((self.start - 1).try_into()?); + } + let range = self + .end + .checked_sub(self.start) + .ok_or_else(|| Error::Config("expected end > start".into()))?; + Choice::print_choice_loop_max_items(iter, config, handle, range)?; + } + + Ok(()) + } + + fn print_choice_loop_max_items( + iter: I, + config: &Config, + handle: &mut W, + max_items: isize, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator, + { + let mut peek_iter = iter.peekable(); + for i in 0..=max_items { + match peek_iter.next() { + Some(s) => { + handle.write_choice(s, config, peek_iter.peek().is_some() && i != max_items)?; + } + None => break, + }; + } + + Ok(()) + } + + /// Print choices that include at least one negative index + fn print_choice_negative(&self, iter: I, config: &Config, handle: &mut W) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator, + { + let vec = iter.collect::>(); + + if let Some((start, end)) = self.get_negative_start_end(&vec)? { + if end > start { + for word in vec[start..std::cmp::min(end, vec.len() - 1)].iter() { + handle.write_choice(*word, config, true)?; + } + handle.write_choice(vec[std::cmp::min(end, vec.len() - 1)], config, false)?; + } else if self.start < 0 { + for word in vec[end + 1..=std::cmp::min(start, vec.len() - 1)] + .iter() + .rev() + { + handle.write_choice(*word, config, true)?; + } + handle.write_choice(vec[end], config, false)?; + } else if start == end && self.start < vec.len().try_into()? { + handle.write_choice(vec[start], config, false)?; + } + } + + Ok(()) + } + + fn print_choice_reverse( + &self, + mut iter: I, + config: &Config, + handle: &mut W, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator, + { + if self.end > 0 { + iter.nth((self.end - 1).try_into()?); + } + + let mut stack = Vec::new(); + for i in 0..=(self.start - self.end) { + match iter.next() { + Some(s) => stack.push(s), + None => break, + } + + if self.start <= self.end + i { + break; + } + } + + let mut peek_iter = stack.iter().rev().peekable(); + while let Some(s) = peek_iter.next() { + handle.write_choice(*s, config, peek_iter.peek().is_some())?; + } + + Ok(()) + } + + /// Get the absolute indexes of a choice range based on the slice length + /// + /// N.B. that this assumes that at least one index is negative - do not try to call this + /// function with a purely positive range. + /// + /// Returns Ok(None) if the resulting choice range would not include any item in the slice. + fn get_negative_start_end(&self, slice: &[T]) -> Result> { + if slice.is_empty() { + return Ok(None); + } + + let start_abs = self.start.checked_abs().ok_or_else(|| { + Error::Config(format!( + "Minimum index value supported is isize::MIN + 1 ({})", + isize::MIN + 1 + )) + })?; + + let slice_len_as_isize = slice.len().try_into()?; + + if self.kind == ChoiceKind::Single { + if start_abs <= slice_len_as_isize { + let idx = (slice_len_as_isize - start_abs).try_into()?; + Ok(Some((idx, idx))) + } else { + Ok(None) + } + } else { + let end_abs = self.end.checked_abs().ok_or_else(|| { + Error::Config(format!( + "Minimum index value supported is isize::MIN + 1 ({})", + isize::MIN + 1 + )) + })?; + + if self.start >= 0 { + // then we assume self.end is negative + let start = self.start.try_into()?; + + if end_abs <= slice_len_as_isize || start <= slice.len() || start > slice.len() { + let end = slice.len().saturating_sub(end_abs.try_into()?); + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } else if self.end >= 0 { + // then we assume self.start is negative + let end = self.end.try_into()?; + + if start_abs <= slice_len_as_isize || end <= slice.len() || end > slice.len() { + let start = slice.len().saturating_sub(start_abs.try_into()?); + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } else { + // both indices are negative + let start = slice.len().saturating_sub(start_abs.try_into()?); + let end = slice.len().saturating_sub(end_abs.try_into()?); + + if start_abs <= slice_len_as_isize || end_abs <= slice_len_as_isize { + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } + } + } +} diff --git a/userland/upstream-src/choose/src/choice/test/get_negative_start_end.rs b/userland/upstream-src/choose/src/choice/test/get_negative_start_end.rs new file mode 100644 index 0000000000..edbc5bb680 --- /dev/null +++ b/userland/upstream-src/choose/src/choice/test/get_negative_start_end.rs @@ -0,0 +1,193 @@ +use super::*; +use crate::Error; + +#[test] +fn positive_negative_1() { + let config = Config::from_iter(vec!["choose", "2:-1"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_gt1() { + let config = Config::from_iter(vec!["choose", "1:-3"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((1, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_positive() { + let config = Config::from_iter(vec!["choose", "-3:4"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_negative() { + let config = Config::from_iter(vec!["choose", "-3:-4"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 1)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative1_negative1() { + let config = Config::from_iter(vec!["choose", "-1:-1"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((4, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_nonexisting_positive() { + let config = Config::from_iter(vec!["choose", "-3:9"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_negative_on_empty() { + let config = Config::from_iter(vec!["choose", "-3:-1"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn negative_positive_on_empty() { + let config = Config::from_iter(vec!["choose", "-3:5"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn positive_negative_on_empty() { + let config = Config::from_iter(vec!["choose", "2:-1"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn negative_positive_all() { + let config = Config::from_iter(vec!["choose", "-5:9"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((0, 3)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_positive_some() { + let config = Config::from_iter(vec!["choose", "-5:2"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((0, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_all() { + let config = Config::from_iter(vec!["choose", "9:-5"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((3, 0)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_some() { + let config = Config::from_iter(vec!["choose", "9:-2"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((3, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_same() { + let config = Config::from_iter(vec!["choose", "1:-3"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((1, 1)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn error_when_choice_is_isize_min() { + let isize_min = format!("{}", isize::MIN); + let config = Config::from_iter(vec!["choose", &isize_min]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} + +#[test] +fn error_when_choice_start_is_isize_min() { + let choice = format!("{}:4", isize::MIN); + let config = Config::from_iter(vec!["choose", &choice]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} + +#[test] +fn error_when_choice_end_is_isize_min() { + let choice = format!("4:{}", isize::MIN); + let config = Config::from_iter(vec!["choose", &choice]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} diff --git a/userland/upstream-src/choose/src/choice/test/is_reverse_range.rs b/userland/upstream-src/choose/src/choice/test/is_reverse_range.rs new file mode 100644 index 0000000000..ae4d64ba2c --- /dev/null +++ b/userland/upstream-src/choose/src/choice/test/is_reverse_range.rs @@ -0,0 +1,31 @@ +use super::*; + +#[test] +fn is_field_reversed() { + let config = Config::from_iter(vec!["choose", "0"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_start_reversed() { + let config = Config::from_iter(vec!["choose", ":2"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_end_reversed() { + let config = Config::from_iter(vec!["choose", "2:"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_start_or_end_reversed() { + let config = Config::from_iter(vec!["choose", ":"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_reversed_field_range_reversed() { + let config = Config::from_iter(vec!["choose", "4:2"]); + assert_eq!(true, config.opt.choices[0].is_reverse_range()); +} diff --git a/userland/upstream-src/choose/src/choice/test/mod.rs b/userland/upstream-src/choose/src/choice/test/mod.rs new file mode 100644 index 0000000000..762804c73b --- /dev/null +++ b/userland/upstream-src/choose/src/choice/test/mod.rs @@ -0,0 +1,55 @@ +use crate::config::Config; +use crate::opt::Opt; +use std::ffi::OsString; +use std::io::{self, BufWriter, Write}; +use structopt::StructOpt; + +mod get_negative_start_end; +mod is_reverse_range; +mod print_choice; + +impl Config { + pub fn from_iter(iter: I) -> Self + where + I: IntoIterator, + I::Item: Into + Clone, + { + Config::new(Opt::from_iter(iter)) + } +} + +struct MockStdout { + pub buffer: String, +} + +impl MockStdout { + fn new() -> Self { + MockStdout { + buffer: String::new(), + } + } + + fn str_from_buf_writer(b: BufWriter) -> String { + match b.into_inner() { + Ok(b) => b.buffer, + Err(_) => panic!("Failed to access BufWriter inner writer"), + } + .trim_end() + .to_string() + } +} + +impl Write for MockStdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut bytes_written = 0; + for i in buf { + self.buffer.push(*i as char); + bytes_written += 1; + } + Ok(bytes_written) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/userland/upstream-src/choose/src/choice/test/print_choice.rs b/userland/upstream-src/choose/src/choice/test/print_choice.rs new file mode 100644 index 0000000000..63a63b5a28 --- /dev/null +++ b/userland/upstream-src/choose/src/choice/test/print_choice.rs @@ -0,0 +1,1032 @@ +use super::*; + +fn test_fn(vec: Vec<&str>, input: &str, output: &str) { + let config = Config::from_iter(vec); + let mut handle = BufWriter::new(MockStdout::new()); + + config.opt.choices[0] + .print_choice(&String::from(input), &config, &mut handle) + .unwrap(); + + assert_eq!( + String::from(output), + MockStdout::str_from_buf_writer(handle) + ); +} + +#[test] +fn print_0() { + test_fn(vec!["choose", "0"], "rust is pretty cool", "rust"); +} + +#[test] +fn print_after_end() { + test_fn(vec!["choose", "10"], "rust is pretty cool", ""); +} + +#[test] +fn print_out_of_order() { + let config = Config::from_iter(vec!["choose", "3", "1"]); + let mut handle = BufWriter::new(MockStdout::new()); + let mut handle1 = BufWriter::new(MockStdout::new()); + + config.opt.choices[0] + .print_choice(&String::from("rust is pretty cool"), &config, &mut handle) + .unwrap(); + + assert_eq!( + String::from("cool"), + MockStdout::str_from_buf_writer(handle) + ); + + config.opt.choices[1] + .print_choice(&String::from("rust is pretty cool"), &config, &mut handle1) + .unwrap(); + + assert_eq!(String::from("is"), MockStdout::str_from_buf_writer(handle1)); +} + +#[test] +fn print_1_to_3_exclusive() { + test_fn( + vec!["choose", "1:3", "-x"], + "rust is pretty cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3() { + test_fn( + vec!["choose", "1:3"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_hashtag() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive() { + test_fn( + vec!["choose", "1:3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_exclusive() { + test_fn( + vec!["choose", "1:3", "-f", "[aeiou]", "-x"], + "the quick brown fox jumped over the lazy dog", + " q ck br", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels() { + test_fn( + vec!["choose", "1:3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_3_to_1() { + test_fn( + vec!["choose", "3:1"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_3_to_1_exclusive() { + test_fn( + vec!["choose", "3:1", "-x"], + "rust lang is pretty darn cool", + "is lang", + ); +} + +#[test] +fn print_1_to_3_nonexistant_field_separator() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_nonexistant_field_separator() { + test_fn( + vec!["choose", "0", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_0_to_3_nonexistant_field_separator() { + test_fn( + vec!["choose", "0:3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_0_with_preceding_separator() { + test_fn( + vec!["choose", "0"], + " rust lang is pretty darn cool", + "rust", + ); +} + +#[test] +fn print_neg3_to_neg1() { + test_fn( + vec!["choose", "-3:-1"], + "rust lang is pretty darn cool", + "pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg3() { + test_fn( + vec!["choose", "-1:-3"], + "rust lang is pretty darn cool", + "cool darn pretty", + ); +} + +#[test] +fn print_neg2_to_end() { + test_fn( + vec!["choose", "-2:"], + "rust lang is pretty darn cool", + "darn cool", + ); +} + +#[test] +fn print_start_to_neg3() { + test_fn( + vec!["choose", ":-3"], + "rust lang is pretty darn cool", + "rust lang is pretty", + ); +} + +#[test] +fn print_1_to_neg3() { + test_fn( + vec!["choose", "1:-3"], + "rust lang is pretty darn cool", + "lang is pretty", + ); +} + +#[test] +fn print_5_to_neg3_empty() { + test_fn(vec!["choose", "5:-3"], "rust lang is pretty darn cool", ""); +} + +#[test] +fn print_0_to_2_greedy() { + test_fn(vec!["choose", "0:2", "-f", ":"], "a:b::c:::d", "a b c"); +} + +#[test] +fn print_0_to_2_non_greedy() { + test_fn(vec!["choose", "0:2", "-n", "-f", ":"], "a:b::c:::d", "a b"); +} + +#[test] +fn print_2_to_neg_1_non_greedy_negative() { + test_fn(vec!["choose", "2:-1", "-n", "-f", ":"], "a:b::c:::d", "c d"); +} + +#[test] +fn print_2_to_0_non_greedy_reversed() { + test_fn(vec!["choose", "2:0", "-n", "-f", ":"], "a:b::c:::d", "b a"); +} + +#[test] +fn print_neg_1_to_neg_3_non_greedy_negative_reversed() { + test_fn(vec!["choose", "-1:-3", "-n", "-f", ":"], "a:b::c:::d", "d"); +} + +#[test] +fn print_1_to_3_with_output_field_separator() { + test_fn(vec!["choose", "1:3", "-o", "#"], "a b c d", "b#c#d"); +} + +#[test] +fn print_1_and_3_with_output_field_separator() { + test_fn(vec!["choose", "1", "3", "-o", "#"], "a b c d", "b"); +} + +#[test] +fn print_2_to_4_with_output_field_separator() { + test_fn( + vec!["choose", "2:4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit%amet,", + ); +} + +#[test] +fn print_3_to_1_with_output_field_separator() { + test_fn(vec!["choose", "3:1", "-o", "#"], "a b c d", "d#c#b"); +} + +#[test] +fn print_0_to_neg_2_with_output_field_separator() { + test_fn(vec!["choose", "0:-2", "-o", "#"], "a b c d", "a#b#c"); +} + +#[test] +fn print_0_to_2_with_empty_output_field_separator() { + test_fn(vec!["choose", "0:2", "-o", ""], "a b c d", "abc"); +} + +#[test] +fn print_0_to_2_character_wise() { + test_fn(vec!["choose", "0:2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_2_to_end_character_wise() { + test_fn(vec!["choose", "2:", "-c"], "abcd", "cd"); +} + +#[test] +fn print_start_to_2_character_wise() { + test_fn(vec!["choose", ":2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_exclusive() { + test_fn(vec!["choose", "0:2", "-c", "-x"], "abcd", "ab"); +} + +#[test] +fn print_0_to_2_character_wise_with_output_delimeter() { + test_fn(vec!["choose", "0:2", "-c", "-o", ":"], "abcd", "a:b:c"); +} + +#[test] +fn print_after_end_character_wise() { + test_fn(vec!["choose", "0:9", "-c"], "abcd", "abcd"); +} + +#[test] +fn print_2_to_0_character_wise() { + test_fn(vec!["choose", "2:0", "-c"], "abcd", "cba"); +} + +#[test] +fn print_neg_2_to_end_character_wise() { + test_fn(vec!["choose", "-2:", "-c"], "abcd", "cd"); +} + +#[test] +fn print_1_to_3_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-x"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_hashtag_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "[aeiou]", "-x"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_3_to_1_rust_syntax_inclusive() { + test_fn( + vec!["choose", "3..=1"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_3_to_1_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "3..=1", "-x"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_1_to_3_nonexistant_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_to_3_nonexistant_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "0..=3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg1_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-3..=-1"], + "rust lang is pretty darn cool", + "pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-1..=-3"], + "rust lang is pretty darn cool", + "cool darn pretty", + ); +} + +#[test] +fn print_neg2_to_end_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-2..="], + "rust lang is pretty darn cool", + "darn cool", + ); +} + +#[test] +fn print_start_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "..=-3"], + "rust lang is pretty darn cool", + "rust lang is pretty", + ); +} + +#[test] +fn print_1_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=-3"], + "rust lang is pretty darn cool", + "lang is pretty", + ); +} + +#[test] +fn print_5_to_neg3_empty_rust_syntax_inclusive() { + test_fn( + vec!["choose", "5..=-3"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_to_2_greedy_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-f", ":"], "a:b::c:::d", "a b c"); +} + +#[test] +fn print_0_to_2_non_greedy_rust_syntax_inclusive() { + test_fn( + vec!["choose", "0..=2", "-n", "-f", ":"], + "a:b::c:::d", + "a b", + ); +} + +#[test] +fn print_2_to_neg_1_non_greedy_negative_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=-1", "-n", "-f", ":"], + "a:b::c:::d", + "c d", + ); +} + +#[test] +fn print_2_to_0_non_greedy_reversed_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=0", "-n", "-f", ":"], + "a:b::c:::d", + "b a", + ); +} + +#[test] +fn print_neg_1_to_neg_3_non_greedy_negative_reversed_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-1..=-3", "-n", "-f", ":"], + "a:b::c:::d", + "d", + ); +} + +#[test] +fn print_1_to_3_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "1..=3", "-o", "#"], "a b c d", "b#c#d"); +} + +#[test] +fn print_1_and_3_with_output_field_separator_rust_syntax_inclusive() { + let config = Config::from_iter(vec!["choose", "1", "3", "-o", "#"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choices[0] + .print_choice(&String::from("a b c d"), &config, &mut handle) + .unwrap(); + handle.write(&config.output_separator).unwrap(); + config.opt.choices[1] + .print_choice(&String::from("a b c d"), &config, &mut handle) + .unwrap(); + assert_eq!(String::from("b#d"), MockStdout::str_from_buf_writer(handle)); +} + +#[test] +fn print_2_to_4_with_output_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit%amet,", + ); +} + +#[test] +fn print_3_to_1_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "3..=1", "-o", "#"], "a b c d", "d#c#b"); +} + +#[test] +fn print_0_to_neg_2_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=-2", "-o", "#"], "a b c d", "a#b#c"); +} + +#[test] +fn print_0_to_2_with_empty_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-o", ""], "a b c d", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=", "-c"], "abcd", "cd"); +} + +#[test] +fn print_start_to_2_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "..=2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_exclusive_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-x"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_with_output_delimeter_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-o", ":"], "abcd", "a:b:c"); +} + +#[test] +fn print_after_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=9", "-c"], "abcd", "abcd"); +} + +#[test] +fn print_2_to_0_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=0", "-c"], "abcd", "cba"); +} + +#[test] +fn print_neg_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "-2..=", "-c"], "abcd", "cd"); +} + +#[test] +fn print_1_to_3_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-x"], + "rust is pretty cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_rust_syntax_exclusive() { + test_fn(vec!["choose", "1..3"], "rust is pretty cool", "is pretty"); +} + +#[test] +fn print_1_to_3_separated_by_hashtag_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "[aeiou]", "-x"], + "the quick brown fox jumped over the lazy dog", + " q ck br", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br", + ); +} + +#[test] +fn print_3_to_1_rust_syntax_exclusive() { + test_fn( + vec!["choose", "3..1"], + "rust lang is pretty darn cool", + "is lang", + ); +} + +#[test] +fn print_3_to_1_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "3..1", "-x"], + "rust lang is pretty darn cool", + "is lang", + ); +} + +#[test] +fn print_1_to_3_nonexistant_field_separator_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_to_3_nonexistant_field_separator_rust_syntax_exclusive() { + test_fn( + vec!["choose", "0..3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_neg3_to_neg1_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-3..-1"], + "rust lang is pretty darn cool", + "pretty darn", + ); +} + +#[test] +fn print_neg1_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-1..-3"], + "rust lang is pretty darn cool", + "darn pretty", + ); +} + +#[test] +fn print_neg2_to_end_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-2.."], + "rust lang is pretty darn cool", + "darn cool", + ); +} + +#[test] +fn print_start_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "..-3"], + "rust lang is pretty darn cool", + "rust lang is", + ); +} + +#[test] +fn print_1_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..-3"], + "rust lang is pretty darn cool", + "lang is", + ); +} + +#[test] +fn print_5_to_neg3_empty_rust_syntax_exclusive() { + test_fn(vec!["choose", "5..-3"], "rust lang is pretty darn cool", ""); +} + +#[test] +fn print_0_to_2_greedy_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-f", ":"], "a:b::c:::d", "a b"); +} + +#[test] +fn print_0_to_2_non_greedy_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-n", "-f", ":"], "a:b::c:::d", "a b"); +} + +#[test] +fn print_2_to_neg_1_non_greedy_negative_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..-1", "-n", "-f", ":"], "a:b::c:::d", "c"); +} + +#[test] +fn print_2_to_0_non_greedy_reversed_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..0", "-n", "-f", ":"], "a:b::c:::d", "b a"); +} + +#[test] +fn print_neg_1_to_neg_3_non_greedy_negative_reversed_rust_syntax_exclusive() { + test_fn(vec!["choose", "-1..-3", "-n", "-f", ":"], "a:b::c:::d", ""); +} + +#[test] +fn print_1_to_3_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "1..3", "-o", "#"], "a b c d", "b#c"); +} + +#[test] +fn print_2_to_4_with_output_field_separator_rust_syntax_exclusive() { + test_fn( + vec!["choose", "2..4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit", + ); +} + +#[test] +fn print_3_to_1_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "3..1", "-o", "#"], "a b c d", "c#b"); +} + +#[test] +fn print_0_to_neg_2_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..-2", "-o", "#"], "a b c d", "a#b"); +} + +#[test] +fn print_0_to_2_with_empty_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-o", ""], "a b c d", "ab"); +} + +#[test] +fn print_0_to_2_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c"], "abcd", "ab"); +} + +#[test] +fn print_2_to_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..", "-c"], "abcd", "cd"); +} + +#[test] +fn print_start_to_2_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "..2", "-c"], "abcd", "ab"); +} + +#[test] +fn print_0_to_2_character_wise_exclusive_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c", "-x"], "abcd", "ab"); +} + +#[test] +fn print_0_to_2_character_wise_with_output_delimeter_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c", "-o", ":"], "abcd", "a:b"); +} + +#[test] +fn print_after_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..9", "-c"], "abcd", "abcd"); +} + +#[test] +fn print_2_to_0_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..0", "-c"], "abcd", "ba"); +} + +#[test] +fn print_neg_2_to_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "-2..", "-c"], "abcd", "cd"); +} + +#[test] +fn print_2_exclusive() { + test_fn(vec!["choose", "2", "-x"], "a b c d", "c"); +} + +#[test] +fn print_2_one_indexed() { + test_fn(vec!["choose", "2", "--one-indexed"], "a b c d", "b"); +} + +#[test] +fn print_2_to_4_one_indexed() { + test_fn(vec!["choose", "2:4", "--one-indexed"], "a b c d", "b c d"); +} + +#[test] +fn print_2_to_end_one_indexed() { + test_fn(vec!["choose", "2:", "--one-indexed"], "a b c d", "b c d"); +} + +#[test] +fn print_start_to_2_one_indexed() { + test_fn(vec!["choose", ":2", "--one-indexed"], "a b c d", "a b"); +} + +#[test] +fn print_2_to_4_one_indexed_exclusive() { + test_fn( + vec!["choose", "2:4", "--one-indexed", "-x"], + "a b c d", + "b c", + ); +} + +#[test] +fn print_4_to_2_one_indexed() { + test_fn(vec!["choose", "4:2", "--one-indexed"], "a b c d", "d c b"); +} + +#[test] +fn print_neg_4_to_2_one_indexed() { + test_fn(vec!["choose", "-4:2", "--one-indexed"], "a b c d", "a b"); +} + +#[test] +fn print_2_to_4_newline_ofs() { + test_fn( + vec!["choose", "2:4", "-o", r#"\n"#], + "a b c d e f", + "c\nd\ne", + ); +} + +#[test] +fn print_negative_on_empty_line() { + test_fn(vec!["choose", "-1"], "", ""); +} + +#[test] +fn print_neg_2_when_field_empty() { + test_fn(vec!["choose", "-2"], "a", ""); +} + +#[test] +fn print_neg_4_to_neg_2_when_fields_empty() { + test_fn(vec!["choose", "-4:-2"], "a", ""); +} + +#[test] +fn print_neg_1_when_field_not_empty() { + test_fn(vec!["choose", "-1"], "a", "a"); +} + +#[test] +fn print_neg_2_when_field_not_empty() { + test_fn(vec!["choose", "-2"], "a b", "a"); +} + +#[test] +fn print_neg_4_to_neg_2_when_fields_not_empty() { + test_fn(vec!["choose", "-4:-2"], "a b c d e", "b c d"); +} + +#[test] +fn print_before_to_before_negative() { + test_fn(vec!["choose", "-8:-6"], "a b c d e", ""); +} + +#[test] +fn print_before_to_0() { + test_fn(vec!["choose", "-8:0"], "a b c d e", "a"); +} + +#[test] +fn print_before_to_middle() { + test_fn(vec!["choose", "-8:2"], "a b c d e", "a b c"); +} + +#[test] +fn print_before_to_after() { + test_fn(vec!["choose", "-6:10"], "a b c d e", "a b c d e"); +} + +#[test] +fn print_before_to_end_negative() { + test_fn(vec!["choose", "-6:-1"], "a b c d e", "a b c d e"); +} + +#[test] +fn print_middle_to_end_negative() { + test_fn(vec!["choose", "2:-1"], "a b c d e", "c d e"); +} + +#[test] +fn print_middle_to_after() { + test_fn(vec!["choose", "-3:10"], "a b c d e", "c d e"); +} + +#[test] +fn print_after_to_after() { + test_fn(vec!["choose", "10:10"], "a b c d e", ""); +} + +#[test] +fn print_negative_end_to_negative_end() { + test_fn(vec!["choose", "-1:-1"], "a b c d e", "e"); +} +////// +#[test] +fn print_before_to_before_negative_empty() { + test_fn(vec!["choose", "-8:-6"], "", ""); +} + +#[test] +fn print_before_to_0_empty() { + test_fn(vec!["choose", "-8:0"], "", ""); +} + +#[test] +fn print_before_to_middle_empty() { + test_fn(vec!["choose", "-8:2"], "", ""); +} + +#[test] +fn print_before_to_after_empty() { + test_fn(vec!["choose", "-6:10"], "", ""); +} + +#[test] +fn print_before_to_end_negative_empty() { + test_fn(vec!["choose", "-6:-1"], "", ""); +} + +#[test] +fn print_middle_to_end_negative_empty() { + test_fn(vec!["choose", "2:-1"], "", ""); +} + +#[test] +fn print_middle_to_after_empty() { + test_fn(vec!["choose", "-3:10"], "", ""); +} + +#[test] +fn print_after_to_after_empty() { + test_fn(vec!["choose", "10:10"], "", ""); +} + +#[test] +fn print_negative_end_to_negative_end_empty() { + test_fn(vec!["choose", "-1:-1"], "", ""); +} + +#[test] +fn print_positive_to_following_negative() { + test_fn(vec!["choose", "1:-3"], "a b c d e", "b c"); +} + +#[test] +fn print_positive_to_same_as_negative() { + test_fn(vec!["choose", "1:-4"], "a b c d e", "b"); +} + +#[test] +fn print_positive_to_preceding_negative() { + test_fn(vec!["choose", "1:-5"], "a b c d e", ""); +} + +#[test] +fn print_end_to_last_negative_is_last() { + test_fn(vec!["choose", "4:-1"], "a b c d e", "e"); +} + +#[test] +fn print_after_end_to_last_negative_is_empty() { + test_fn(vec!["choose", "5:-1"], "a b c d e", ""); +} + +#[test] +fn print_after_end_to_second_to_last_negative_is_empty() { + test_fn(vec!["choose", "5:-2"], "a b c d e", ""); +} diff --git a/userland/upstream-src/choose/src/config.rs b/userland/upstream-src/choose/src/config.rs new file mode 100644 index 0000000000..c2455c5900 --- /dev/null +++ b/userland/upstream-src/choose/src/config.rs @@ -0,0 +1,78 @@ +use regex::Regex; +use std::process; + +use crate::choice::ChoiceKind; +use crate::opt::Opt; + +pub struct Config { + pub opt: Opt, + pub separator: Regex, + pub output_separator: Box<[u8]>, +} + +impl Config { + pub fn new(mut opt: Opt) -> Self { + for choice in &mut opt.choices { + if (opt.exclusive && choice.kind == ChoiceKind::ColonRange) + || choice.kind == ChoiceKind::RustExclusiveRange + { + if choice.is_reverse_range() { + choice.start -= 1; + } else { + choice.end -= 1; + } + } + + if opt.one_indexed { + if choice.start > 0 { + choice.start -= 1; + } + + if choice.end > 0 { + choice.end -= 1; + } + } + } + + let separator = match Regex::new(match &opt.field_separator { + Some(s) => s, + None => "[[:space:]]", + }) { + Ok(r) => r, + Err(e) => { + // Exit code of 2 means failed to compile field_separator regex + match e { + regex::Error::Syntax(e) => { + eprintln!("Syntax error compiling regular expression: {}", e); + process::exit(2); + } + regex::Error::CompiledTooBig(e) => { + eprintln!("Compiled regular expression too big: compiled size cannot exceed {} bytes", e); + process::exit(2); + } + _ => { + eprintln!("Error compiling regular expression: {}", e); + process::exit(2); + } + } + } + }; + + let output_separator = match opt.character_wise { + false => match opt.output_field_separator.clone() { + Some(s) => s.into_boxed_str().into_boxed_bytes(), + None => Box::new([0x20; 1]), + }, + true => match opt.output_field_separator.clone() { + Some(s) => s.into_boxed_str().into_boxed_bytes(), + None => Box::new([]), + }, + }; + + Config { + opt, + separator, + output_separator, + } + } +} diff --git a/userland/upstream-src/choose/src/error.rs b/userland/upstream-src/choose/src/error.rs new file mode 100644 index 0000000000..f24fa3d8da --- /dev/null +++ b/userland/upstream-src/choose/src/error.rs @@ -0,0 +1,63 @@ +use std::error::Error as StdError; +use std::fmt; +use std::num::TryFromIntError; + +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + ParseRange(ParseRangeError), + TryFromInt(TryFromIntError), + Config(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Io(io) => write!(f, "{}", io), + Self::ParseRange(pr) => write!(f, "{}", pr), + Self::TryFromInt(tfi) => write!(f, "{}", tfi), + Self::Config(c) => write!(f, "{}", c), + } + } +} + +impl StdError for Error {} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Self::Io(e) + } +} + +impl From for Error { + fn from(e: ParseRangeError) -> Self { + Self::ParseRange(e) + } +} + +impl From for Error { + fn from(e: TryFromIntError) -> Self { + Self::TryFromInt(e) + } +} + +#[derive(Debug)] +pub struct ParseRangeError { + source_str: String, +} + +impl ParseRangeError { + pub fn new(source_str: &str) -> Self { + ParseRangeError { + source_str: String::from(source_str), + } + } +} + +impl fmt::Display for ParseRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.source_str) + } +} + +impl StdError for ParseRangeError {} diff --git a/userland/upstream-src/choose/src/escape.rs b/userland/upstream-src/choose/src/escape.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/userland/upstream-src/choose/src/escape.rs @@ -0,0 +1 @@ + diff --git a/userland/upstream-src/choose/src/main.rs b/userland/upstream-src/choose/src/main.rs new file mode 100644 index 0000000000..2139df57a1 --- /dev/null +++ b/userland/upstream-src/choose/src/main.rs @@ -0,0 +1,103 @@ +use std::fs::File; +use std::io::{self, Read}; +use std::process; +use structopt::StructOpt; + +#[macro_use] +extern crate lazy_static; + +mod choice; +mod config; +mod error; +mod escape; +mod opt; +mod parse; +mod parse_error; +mod reader; +mod result; +mod writeable; +mod writer; + +use config::Config; +use error::Error; +use opt::Opt; +use result::Result; +use writer::WriteReceiver; + +fn main() { + let opt = Opt::from_args(); + + let stdout = io::stdout(); + let lock = stdout.lock(); + let exit_result = match opt.input { + Some(_) => main_generic(opt, &mut io::BufWriter::new(lock)), + None => main_generic(opt, &mut io::LineWriter::new(lock)), + }; + + match exit_result { + Ok(_) => (), + Err(err) => { + match err { + Error::Io(e) => { + if e.kind() == io::ErrorKind::BrokenPipe { + // BrokenPipe means whoever is reading the output hung up, we should + // gracefully exit + } else { + eprintln!("Failed to write to output: {}", e) + } + } + e => eprintln!("Error: {}", e), + } + } + } +} + +fn main_generic(opt: Opt, handle: &mut W) -> Result<()> { + let config = Config::new(opt); + + let read = match &config.opt.input { + Some(f) => match File::open(f) { + Ok(fh) => Box::new(fh) as Box, + Err(e) => { + eprintln!("Failed to open file: {}", e); + // exit code of 3 means failure to open input file + process::exit(3); + } + }, + None => Box::new(io::stdin()) as Box, + }; + + let mut reader = reader::BufReader::new(read); + let mut buffer = String::new(); + + while let Some(line) = reader.read_line(&mut buffer) { + match line { + Ok(l) => { + let l = if (config.opt.character_wise || config.opt.field_separator.is_some()) + && l.ends_with('\n') + { + &l[0..l.len().saturating_sub(1)] + } else { + l + }; + + // trim end to remove newline or CRLF on windows + let l = l.trim_end(); + + let choice_iter = &mut config.opt.choices.iter().peekable(); + + while let Some(choice) = choice_iter.next() { + choice.print_choice(l, &config, handle)?; + if choice_iter.peek().is_some() { + handle.write_separator(&config)?; + } + } + + handle.write(b"\n").map(|_| ())? + } + Err(e) => println!("Failed to read line: {}", e), + } + } + + Ok(()) +} diff --git a/userland/upstream-src/choose/src/opt.rs b/userland/upstream-src/choose/src/opt.rs new file mode 100644 index 0000000000..3ce66f194c --- /dev/null +++ b/userland/upstream-src/choose/src/opt.rs @@ -0,0 +1,50 @@ +use std::path::PathBuf; +use structopt::StructOpt; + +use crate::choice::Choice; +use crate::parse; + +#[derive(Debug, StructOpt)] +#[structopt(name = "choose", about = "`choose` sections from each line of files")] +#[structopt(setting = structopt::clap::AppSettings::AllowLeadingHyphen)] +pub struct Opt { + /// Choose fields by character number + #[structopt(short, long)] + pub character_wise: bool, + + /// Activate debug mode + #[structopt(short, long)] + #[allow(unused)] + pub debug: bool, + + /// Use exclusive ranges, similar to array indexing in many programming languages + #[structopt(short = "x", long)] + pub exclusive: bool, + + /// Specify field separator other than whitespace, using Rust `regex` syntax + #[structopt(short, long)] + pub field_separator: Option, + + /// Input file + #[structopt(short, long, parse(from_os_str))] + pub input: Option, + + /// Use non-greedy field separators + #[structopt(short, long)] + pub non_greedy: bool, + + /// Index from 1 instead of 0 + #[structopt(long)] + pub one_indexed: bool, + + /// Specify output field separator + #[structopt(short, long, parse(from_str = parse::output_field_separator))] + pub output_field_separator: Option, + + /// Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning + /// or end of a range can be omitted, resulting in including the beginning or end of the line, + /// respectively. a:b is inclusive of b (unless overridden by -x). a..b is + /// exclusive of b and a..=b is inclusive of b. + #[structopt(required = true, min_values = 1, parse(try_from_str = parse::choice))] + pub choices: Vec, +} diff --git a/userland/upstream-src/choose/src/parse.rs b/userland/upstream-src/choose/src/parse.rs new file mode 100644 index 0000000000..520d09c435 --- /dev/null +++ b/userland/upstream-src/choose/src/parse.rs @@ -0,0 +1,271 @@ +use backslash::escape_ascii; +use regex::Regex; + +use crate::choice::{Choice, ChoiceKind}; +use crate::error::ParseRangeError; +use crate::parse_error::ParseError; + +lazy_static! { + static ref PARSE_CHOICE_RE: Regex = Regex::new(r"^(-?\d*)(:|\.\.=?)(-?\d*)$").unwrap(); +} + +pub fn choice(src: &str) -> Result { + let cap = match PARSE_CHOICE_RE.captures_iter(src).next() { + Some(v) => v, + None => match src.parse() { + Ok(x) => return Ok(Choice::new(x, x, ChoiceKind::Single)), + Err(e) => { + eprintln!("failed to parse choice argument: {}", src); + return Err(ParseError::ParseIntError(e)); + } + }, + }; + + let start = if cap[1].is_empty() { + 0 + } else { + match cap[1].parse() { + Ok(x) => x, + Err(e) => { + eprintln!("failed to parse range start: {}", &cap[1]); + return Err(ParseError::ParseIntError(e)); + } + } + }; + + let kind = match &cap[2] { + ":" => ChoiceKind::ColonRange, + ".." => ChoiceKind::RustExclusiveRange, + "..=" => ChoiceKind::RustInclusiveRange, + _ => { + eprintln!( + "failed to parse range: not a valid range separator: {}", + &cap[2] + ); + return Err(ParseError::ParseRangeError(ParseRangeError::new(&cap[2]))); + } + }; + + let end = if cap[3].is_empty() { + isize::max_value() + } else { + match cap[3].parse() { + Ok(x) => x, + Err(e) => { + eprintln!("failed to parse range end: {}", &cap[3]); + return Err(ParseError::ParseIntError(e)); + } + } + }; + + Ok(Choice::new(start, end, kind)) +} + +pub fn output_field_separator(src: &str) -> String { + escape_ascii(src).unwrap() +} + +#[cfg(test)] +mod tests { + use crate::parse; + + mod parse_choice_tests { + use super::*; + + #[test] + fn parse_single_choice_start() { + let result = parse::choice("6").unwrap(); + assert_eq!(6, result.start) + } + + #[test] + fn parse_single_choice_end() { + let result = parse::choice("6").unwrap(); + assert_eq!(6, result.end) + } + + #[test] + fn parse_none_started_range() { + let result = parse::choice(":5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_none_terminated_range() { + let result = parse::choice("5:").unwrap(); + assert_eq!((5, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos() { + let result = parse::choice("5:7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg() { + let result = parse::choice("-3:-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended() { + let result = parse::choice("-3:").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended() { + let result = parse::choice(":-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg() { + let result = parse::choice("5:-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos() { + let result = parse::choice("-3:5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + + #[test] + fn parse_beginning_to_end_range() { + let result = parse::choice(":").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_bad_choice() { + assert!(parse::choice("d").is_err()); + } + + #[test] + fn parse_bad_range() { + assert!(parse::choice("d:i").is_err()); + } + + #[test] + fn parse_rust_inclusive_range() { + let result = parse::choice("3..=5").unwrap(); + assert_eq!((3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_start() { + let result = parse::choice("..=5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_end() { + let result = parse::choice("3..=").unwrap(); + assert_eq!((3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_start_or_end() { + let result = parse::choice("..=").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos_rust_exclusive() { + let result = parse::choice("5..7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg_rust_exclusive() { + let result = parse::choice("-3..-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended_rust_exclusive() { + let result = parse::choice("-3..").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended_rust_exclusive() { + let result = parse::choice("..-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg_rust_exclusive() { + let result = parse::choice("5..-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos_rust_exclusive() { + let result = parse::choice("-3..5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range() { + let result = parse::choice("3..5").unwrap(); + assert_eq!((3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_start() { + let result = parse::choice("..5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_end() { + let result = parse::choice("3..").unwrap(); + assert_eq!((3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_start_or_end() { + let result = parse::choice("..").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos_rust_inclusive() { + let result = parse::choice("5..=7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg_rust_inclusive() { + let result = parse::choice("-3..=-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended_rust_inclusive() { + let result = parse::choice("-3..=").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended_rust_inclusive() { + let result = parse::choice("..=-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg_rust_inclusive() { + let result = parse::choice("5..=-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos_rust_inclusive() { + let result = parse::choice("-3..=5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + } +} diff --git a/userland/upstream-src/choose/src/parse_error.rs b/userland/upstream-src/choose/src/parse_error.rs new file mode 100644 index 0000000000..b49b4430d7 --- /dev/null +++ b/userland/upstream-src/choose/src/parse_error.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +pub enum ParseError { + ParseIntError(std::num::ParseIntError), + ParseRangeError(crate::error::ParseRangeError), +} + +impl ToString for ParseError { + fn to_string(&self) -> String { + match self { + ParseError::ParseIntError(e) => e.to_string(), + ParseError::ParseRangeError(e) => e.to_string(), + } + } +} diff --git a/userland/upstream-src/choose/src/reader.rs b/userland/upstream-src/choose/src/reader.rs new file mode 100644 index 0000000000..06349b7ab6 --- /dev/null +++ b/userland/upstream-src/choose/src/reader.rs @@ -0,0 +1,25 @@ +use std::io::{self, prelude::*}; + +pub struct BufReader { + reader: io::BufReader, +} + +impl BufReader { + pub fn new(f: R) -> Self { + Self { + reader: io::BufReader::new(f), + } + } + + pub fn read_line<'buf>( + &mut self, + buffer: &'buf mut String, + ) -> Option> { + buffer.clear(); + + self.reader + .read_line(buffer) + .map(|u| if u == 0 { None } else { Some(buffer) }) + .transpose() + } +} diff --git a/userland/upstream-src/choose/src/result.rs b/userland/upstream-src/choose/src/result.rs new file mode 100644 index 0000000000..8207680639 --- /dev/null +++ b/userland/upstream-src/choose/src/result.rs @@ -0,0 +1,3 @@ +use crate::error::Error; + +pub type Result = std::result::Result; diff --git a/userland/upstream-src/choose/src/writeable.rs b/userland/upstream-src/choose/src/writeable.rs new file mode 100644 index 0000000000..658b086cff --- /dev/null +++ b/userland/upstream-src/choose/src/writeable.rs @@ -0,0 +1,20 @@ +pub trait Writeable: Copy { + fn to_byte_buf(&self) -> Box<[u8]>; +} + +impl Writeable for &str { + fn to_byte_buf(&self) -> Box<[u8]> { + return Box::from(self.as_bytes()); + } +} + +impl Writeable for char { + fn to_byte_buf(&self) -> Box<[u8]> { + let mut buf = [0; 4]; + return self + .encode_utf8(&mut buf) + .to_owned() + .into_boxed_str() + .into_boxed_bytes(); + } +} diff --git a/userland/upstream-src/choose/src/writer.rs b/userland/upstream-src/choose/src/writer.rs new file mode 100644 index 0000000000..3b9e710248 --- /dev/null +++ b/userland/upstream-src/choose/src/writer.rs @@ -0,0 +1,27 @@ +use std::io::{self, BufWriter, LineWriter, Write}; + +use crate::config::Config; +use crate::writeable::Writeable; + +pub trait WriteReceiver: Write { + fn write_choice( + &mut self, + b: Wa, + config: &Config, + print_separator: bool, + ) -> io::Result<()> { + let num_bytes_written = self.write(&b.to_byte_buf())?; + if num_bytes_written > 0 && print_separator { + self.write_separator(config)?; + }; + Ok(()) + } + + fn write_separator(&mut self, config: &Config) -> io::Result<()> { + self.write(&config.output_separator).map(|_| ()) + } +} + +impl WriteReceiver for BufWriter {} + +impl WriteReceiver for LineWriter {} diff --git a/userland/upstream-src/ctrlc-3.5.2/.cargo-ok b/userland/upstream-src/ctrlc-3.5.2/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/ctrlc-3.5.2/.cargo_vcs_info.json b/userland/upstream-src/ctrlc-3.5.2/.cargo_vcs_info.json new file mode 100644 index 0000000000..e16c8336a8 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "0aed47c35355ab7de53fa281201b8b924c2cfcb3" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/ctrlc-3.5.2/.github/workflows/ci.yml b/userland/upstream-src/ctrlc-3.5.2/.github/workflows/ci.yml new file mode 100644 index 0000000000..34f92c1689 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [stable, beta, nightly] + include: + - os: windows-latest + rust: stable-x86_64-pc-windows-gnu + - os: windows-latest + rust: nightly-x86_64-pc-windows-gnu + - os: windows-latest + rust: stable-i686-pc-windows-msvc + - os: windows-latest + rust: nightly-i686-pc-windows-msvc + - os: windows-latest + rust: stable-i686-pc-windows-gnu + - os: windows-latest + rust: nightly-i686-pc-windows-gnu + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - run: cargo build + - run: cargo build --features termination + - run: cargo test + - run: cargo test --features termination + + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo fmt --check diff --git a/userland/upstream-src/ctrlc-3.5.2/.gitignore b/userland/upstream-src/ctrlc-3.5.2/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/userland/upstream-src/ctrlc-3.5.2/Cargo.toml b/userland/upstream-src/ctrlc-3.5.2/Cargo.toml new file mode 100644 index 0000000000..d1a41326bb --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/Cargo.toml @@ -0,0 +1,96 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.69.0" +name = "ctrlc" +version = "3.5.2" +authors = ["Antti Keränen "] +build = false +exclude = [ + "/.travis.yml", + "/appveyor.yml", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Easy Ctrl-C handler for Rust projects" +homepage = "https://github.com/Detegr/rust-ctrlc" +documentation = "https://detegr.github.io/doc/ctrlc" +readme = "README.md" +keywords = [ + "ctrlc", + "signal", + "SIGINT", +] +categories = ["os"] +license = "MIT/Apache-2.0" +repository = "https://github.com/Detegr/rust-ctrlc.git" + +[badges.maintenance] +status = "passively-maintained" + +[features] +termination = [] + +[lib] +name = "ctrlc" +path = "src/lib.rs" + +[[example]] +name = "issue_46_example" +path = "examples/issue_46_example.rs" + +[[example]] +name = "readme_example" +path = "examples/readme_example.rs" + +[[test]] +name = "issue_97" +path = "tests/main/issue_97.rs" +harness = false + +[[test]] +name = "main" +path = "tests/main/mod.rs" +harness = false + +[dev-dependencies.signal-hook] +version = "0.3" + +[target.'cfg(target_vendor = "apple")'.dependencies.dispatch2] +version = "0.3" + +[target."cfg(unix)".dependencies.nix] +version = "0.31" +features = ["signal"] +default-features = false + +[target."cfg(windows)".dependencies.windows-sys] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_Security", + "Win32_System_Console", +] + +[target."cfg(windows)".dev-dependencies.windows-sys] +version = "0.61" +features = [ + "Win32_Storage_FileSystem", + "Win32_Foundation", + "Win32_System_IO", + "Win32_System_Console", +] diff --git a/userland/upstream-src/ctrlc-3.5.2/Cargo.toml.orig b/userland/upstream-src/ctrlc-3.5.2/Cargo.toml.orig new file mode 100644 index 0000000000..08d77a2931 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/Cargo.toml.orig @@ -0,0 +1,46 @@ +[package] +name = "ctrlc" +version = "3.5.2" +authors = ["Antti Keränen "] +description = "Easy Ctrl-C handler for Rust projects" +documentation = "https://detegr.github.io/doc/ctrlc" +homepage = "https://github.com/Detegr/rust-ctrlc" +keywords = ["ctrlc", "signal", "SIGINT"] +categories = ["os"] +license = "MIT/Apache-2.0" +repository = "https://github.com/Detegr/rust-ctrlc.git" +exclude = ["/.travis.yml", "/appveyor.yml"] +edition = "2021" +readme = "README.md" +rust-version = "1.69.0" + +[target.'cfg(unix)'.dependencies] +nix = { version = "0.31", default-features = false, features = ["signal"]} + +[target.'cfg(target_vendor = "apple")'.dependencies] +dispatch2 = "0.3" + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Security", "Win32_System_Console"] } + +[target.'cfg(windows)'.dev-dependencies] +windows-sys = { version = "0.61", features = ["Win32_Storage_FileSystem", "Win32_Foundation", "Win32_System_IO", "Win32_System_Console"] } + +[features] +termination = [] + +[[test]] +harness = false +name = "main" +path = "tests/main/mod.rs" + +[[test]] +harness = false +name = "issue_97" +path = "tests/main/issue_97.rs" + +[dev-dependencies] +signal-hook = "0.3" + +[badges] +maintenance = { status = "passively-maintained" } diff --git a/userland/upstream-src/ctrlc-3.5.2/LICENSE-APACHE b/userland/upstream-src/ctrlc-3.5.2/LICENSE-APACHE new file mode 100644 index 0000000000..667557c8be --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2017 CtrlC developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/userland/upstream-src/ctrlc-3.5.2/LICENSE-MIT b/userland/upstream-src/ctrlc-3.5.2/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/ctrlc-3.5.2/README.md b/userland/upstream-src/ctrlc-3.5.2/README.md new file mode 100644 index 0000000000..97c6d737f1 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/README.md @@ -0,0 +1,57 @@ +# CtrlC +A simple easy to use wrapper around Ctrl-C signal. + +[Documentation](http://detegr.github.io/doc/ctrlc/) + +## Example usage + +In `cargo.toml`: + +```toml +[dependencies] +ctrlc = "3.5" +``` + +then, in `main.rs` + +```rust +use std::sync::mpsc::channel; +use ctrlc; + +fn main() { + let (tx, rx) = channel(); + + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Error setting Ctrl-C handler"); + + println!("Waiting for Ctrl-C..."); + rx.recv().expect("Could not receive from channel."); + println!("Got it! Exiting..."); +} +``` + +#### Try the example yourself +`cargo build --examples && target/debug/examples/readme_example` + +## Handling SIGTERM and SIGHUP +Add CtrlC to Cargo.toml using `termination` feature and CtrlC will handle SIGINT, SIGTERM and SIGHUP. + +## License + +Licensed under either of + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be dual licensed as above, without any +additional terms or conditions. + +## Similar crates + +There are alternatives that give you more control over the different signals and/or add async support. + +- [signal-hook](https://github.com/vorner/signal-hook) +- [tokio::signal](https://docs.rs/tokio/latest/tokio/signal/index.html) diff --git a/userland/upstream-src/ctrlc-3.5.2/examples/issue_46_example.rs b/userland/upstream-src/ctrlc-3.5.2/examples/issue_46_example.rs new file mode 100644 index 0000000000..646c6b014b --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/examples/issue_46_example.rs @@ -0,0 +1,26 @@ +use std::process; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +fn main() { + let running = Arc::new(AtomicUsize::new(0)); + let r = running.clone(); + ctrlc::set_handler(move || { + let prev = r.fetch_add(1, Ordering::SeqCst); + if prev == 0 { + println!("Exiting..."); + } else { + process::exit(0); + } + }) + .expect("Error setting Ctrl-C handler"); + println!("Running..."); + for _ in 1..6 { + thread::sleep(Duration::from_secs(5)); + if running.load(Ordering::SeqCst) > 0 { + break; + } + } +} diff --git a/userland/upstream-src/ctrlc-3.5.2/examples/readme_example.rs b/userland/upstream-src/ctrlc-3.5.2/examples/readme_example.rs new file mode 100644 index 0000000000..f6f8fd4bee --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/examples/readme_example.rs @@ -0,0 +1,22 @@ +// Copyright (c) 2015 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use ctrlc; +use std::sync::mpsc::channel; + +fn main() { + let (tx, rx) = channel(); + + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Error setting Ctrl-C handler"); + + println!("Waiting for Ctrl-C..."); + rx.recv().expect("Could not receive from channel."); + println!("Got it! Exiting..."); +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/error.rs b/userland/upstream-src/ctrlc-3.5.2/src/error.rs new file mode 100644 index 0000000000..f13625aee7 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/error.rs @@ -0,0 +1,54 @@ +use crate::platform; +use std::fmt; + +/// Ctrl-C error. +#[derive(Debug)] +pub enum Error { + /// Signal could not be found from the system. + NoSuchSignal(crate::SignalType), + /// Ctrl-C signal handler already registered. + MultipleHandlers, + /// Unexpected system error. + System(std::io::Error), +} + +impl Error { + fn describe(&self) -> &str { + match *self { + Error::NoSuchSignal(_) => "Signal could not be found from the system", + Error::MultipleHandlers => "Ctrl-C signal handler already registered", + Error::System(_) => "Unexpected system error", + } + } +} + +impl From for Error { + fn from(e: platform::Error) -> Error { + #[cfg(not(windows))] + if e == platform::Error::EEXIST { + return Error::MultipleHandlers; + } + + let system_error = std::io::Error::new(std::io::ErrorKind::Other, e); + Error::System(system_error) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Ctrl-C error: {}", self.describe()) + } +} + +impl std::error::Error for Error { + fn description(&self) -> &str { + self.describe() + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + match *self { + Error::System(ref e) => Some(e), + _ => None, + } + } +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/lib.rs b/userland/upstream-src/ctrlc-3.5.2/src/lib.rs new file mode 100644 index 0000000000..613e4ee812 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/lib.rs @@ -0,0 +1,148 @@ +// Copyright (c) 2017 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +#![warn(missing_docs)] + +//! Cross platform handling of Ctrl-C signals. +//! +//! [HandlerRoutine]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms683242.aspx +//! +//! [set_handler()](fn.set_handler.html) allows setting a handler closure which is executed on +//! `Ctrl+C`. On Unix, this corresponds to a `SIGINT` signal. On windows, `Ctrl+C` corresponds to +//! [`CTRL_C_EVENT`][HandlerRoutine] or [`CTRL_BREAK_EVENT`][HandlerRoutine]. +//! +//! Setting a handler will start a new dedicated signal handling thread where we +//! execute the handler each time we receive a `Ctrl+C` signal. There can only be +//! one handler, you would typically set one at the start of your program. +//! +//! # Example +//! ```no_run +//! # #[allow(clippy::needless_doctest_main)] +//! use std::sync::atomic::{AtomicBool, Ordering}; +//! use std::sync::Arc; +//! +//! fn main() { +//! let running = Arc::new(AtomicBool::new(true)); +//! let r = running.clone(); +//! +//! ctrlc::set_handler(move || { +//! r.store(false, Ordering::SeqCst); +//! }).expect("Error setting Ctrl-C handler"); +//! +//! println!("Waiting for Ctrl-C..."); +//! while running.load(Ordering::SeqCst) {} +//! println!("Got it! Exiting..."); +//! } +//! ``` +//! +//! # Handling SIGTERM and SIGHUP +//! Handling of `SIGTERM` and `SIGHUP` can be enabled with `termination` feature. If this is enabled, +//! the handler specified by `set_handler()` will be executed for `SIGINT`, `SIGTERM` and `SIGHUP`. +//! + +#[macro_use] + +mod error; +mod platform; +pub use platform::Signal; +mod signal; +pub use signal::*; + +pub use error::Error; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; +use std::thread; + +static INIT: AtomicBool = AtomicBool::new(false); +static INIT_LOCK: Mutex<()> = Mutex::new(()); + +/// Register signal handler for Ctrl-C. +/// +/// Starts a new dedicated signal handling thread. Should only be called once, +/// typically at the start of your program. +/// +/// # Example +/// ```no_run +/// ctrlc::set_handler(|| println!("Hello world!")).expect("Error setting Ctrl-C handler"); +/// ``` +/// +/// # Warning +/// On Unix, the handler registration for `SIGINT`, (`SIGTERM` and `SIGHUP` if termination feature +/// is enabled) or `SA_SIGINFO` posix signal handlers will be overwritten. On Windows, multiple +/// handler routines are allowed, but they are called on a last-registered, first-called basis +/// until the signal is handled. +/// +/// ctrlc::try_set_handler will error (on Unix) if another signal handler exists for the same +/// signal(s) that ctrlc is trying to attach the handler to. +/// +/// On Unix, signal dispositions and signal handlers are inherited by child processes created via +/// `fork(2)` on, but not by child processes created via `execve(2)`. +/// Signal handlers are not inherited on Windows. +/// +/// # Errors +/// Will return an error if a system error occurred while setting the handler. +/// +/// # Panics +/// Any panic in the handler will not be caught and will cause the signal handler thread to stop. +pub fn set_handler(user_handler: F) -> Result<(), Error> +where + F: FnMut() + 'static + Send, +{ + init_and_set_handler(user_handler, true) +} + +/// The same as ctrlc::set_handler but errors if a handler already exists for the signal(s). +/// +/// # Errors +/// Will return an error if another handler exists or if a system error occurred while setting the +/// handler. +pub fn try_set_handler(user_handler: F) -> Result<(), Error> +where + F: FnMut() + 'static + Send, +{ + init_and_set_handler(user_handler, false) +} + +fn init_and_set_handler(user_handler: F, overwrite: bool) -> Result<(), Error> +where + F: FnMut() + 'static + Send, +{ + if !INIT.load(Ordering::Acquire) { + let _guard = INIT_LOCK.lock().unwrap(); + + if !INIT.load(Ordering::Relaxed) { + set_handler_inner(user_handler, overwrite)?; + INIT.store(true, Ordering::Release); + return Ok(()); + } + } + + Err(Error::MultipleHandlers) +} + +fn set_handler_inner(mut user_handler: F, overwrite: bool) -> Result<(), Error> +where + F: FnMut() + 'static + Send, +{ + unsafe { + platform::init_os_handler(overwrite)?; + } + + thread::Builder::new() + .name("ctrl-c".into()) + .spawn(move || loop { + unsafe { + platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C"); + } + user_handler(); + }) + .map_err(Error::System)?; + + Ok(()) +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/platform/mod.rs b/userland/upstream-src/ctrlc-3.5.2/src/platform/mod.rs new file mode 100644 index 0000000000..d1cc1040b0 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/platform/mod.rs @@ -0,0 +1,26 @@ +// Copyright (c) 2017 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +#[cfg(all(unix, not(target_vendor = "nonos")))] +mod unix; + +#[cfg(windows)] +mod windows; + +#[cfg(target_vendor = "nonos")] +mod nonos; + +#[cfg(all(unix, not(target_vendor = "nonos")))] +pub use self::unix::*; + +#[cfg(windows)] +pub use self::windows::*; + +#[cfg(target_vendor = "nonos")] +pub use self::nonos::*; diff --git a/userland/upstream-src/ctrlc-3.5.2/src/platform/nonos.rs b/userland/upstream-src/ctrlc-3.5.2/src/platform/nonos.rs new file mode 100644 index 0000000000..a40ecd4489 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/platform/nonos.rs @@ -0,0 +1,48 @@ +// NONOS platform for ctrlc. On NONOS the terminal (nox) owns Ctrl-C and +// delivers interrupts to the foreground job itself, so a capsule installs no OS +// signal handler. init_os_handler is a no-op and the crate's waiter thread +// parks here, which lets ctrlc build and link for x86_64-nonos without the unix +// signal syscalls the target does not have. + +/// A platform error. NONOS raises none of these, but the crate wraps it into an +/// io::Error and compares it to EEXIST, so it carries an errno-like code and +/// implements the traits that requires. +#[derive(Debug, PartialEq, Eq)] +pub struct Error(pub i32); + +impl Error { + /// The "already installed" code the crate checks to detect a second handler. + pub const EEXIST: Error = Error(17); +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "nonos ctrlc error {}", self.0) + } +} + +impl std::error::Error for Error {} + +/// A platform signal value, carried in `SignalType::Other`. NONOS has no OS +/// signal numbers, so a plain integer stands in. +pub type Signal = i32; + +/// Install the OS Ctrl-C handler. On NONOS the terminal delivers interrupts, so +/// there is nothing to install. +/// +/// # Safety +/// Matches the other platforms' `unsafe` contract; this touches no OS state. +pub unsafe fn init_os_handler(_overwrite: bool) -> Result<(), Error> { + Ok(()) +} + +/// Block until Ctrl-C. On NONOS the signal never reaches the capsule, so the +/// caller's waiter thread parks here indefinitely instead of spinning. +/// +/// # Safety +/// Matches the other platforms' `unsafe` contract; this only parks the thread. +pub unsafe fn block_ctrl_c() -> Result<(), Error> { + loop { + std::thread::park(); + } +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/platform/unix/mod.rs b/userland/upstream-src/ctrlc-3.5.2/src/platform/unix/mod.rs new file mode 100644 index 0000000000..38e627f8da --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/platform/unix/mod.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2017 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::error::Error as CtrlcError; + +#[cfg(not(target_vendor = "apple"))] +#[allow(static_mut_refs)] // rust-version = "1.69.0" +mod implementation { + static mut SEMAPHORE: nix::libc::sem_t = unsafe { std::mem::zeroed() }; + const SEM_THREAD_SHARED: nix::libc::c_int = 0; + + pub unsafe fn sem_init() { + nix::libc::sem_init(&mut SEMAPHORE as *mut _, SEM_THREAD_SHARED, 0); + } + + pub unsafe fn sem_post() { + // No errors apply. EOVERFLOW is hypothetically possible but it's equivalent to a success for our oneshot use-case. + let _ = nix::libc::sem_post(&mut SEMAPHORE as *mut _); + } + + pub unsafe fn sem_wait_forever() { + // The only realistic error is EINTR, which is restartable. + while nix::libc::sem_wait(&mut SEMAPHORE as *mut _) == -1 {} + } +} + +#[cfg(target_vendor = "apple")] +mod implementation { + use dispatch2::{DispatchRetained, DispatchSemaphore, DispatchTime}; + + static mut SEMAPHORE: Option> = None; + + pub unsafe fn sem_init() { + SEMAPHORE = Some(DispatchSemaphore::new(0)); + } + + #[allow(static_mut_refs)] + pub unsafe fn sem_post() { + SEMAPHORE.as_deref().unwrap().signal(); + } + + #[allow(static_mut_refs)] + pub unsafe fn sem_wait_forever() { + SEMAPHORE.as_deref().unwrap().wait(DispatchTime::FOREVER); + } +} + +/// Platform specific error type +pub type Error = nix::Error; + +/// Platform specific signal type +pub type Signal = nix::sys::signal::Signal; + +extern "C" fn os_handler(_: nix::libc::c_int) { + unsafe { + implementation::sem_post(); + } +} + +/// Register os signal handler. +/// +/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html) +/// and should only be called once. +/// +/// # Errors +/// Will return an error if a system error occurred. +/// +#[inline] +pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> { + use nix::sys::signal; + + implementation::sem_init(); + + let handler = signal::SigHandler::Handler(os_handler); + #[cfg(not(target_os = "nto"))] + let new_action = signal::SigAction::new( + handler, + signal::SaFlags::SA_RESTART, + signal::SigSet::empty(), + ); + // SA_RESTART is not supported on QNX Neutrino 7.1 and before + #[cfg(target_os = "nto")] + let new_action = + signal::SigAction::new(handler, signal::SaFlags::empty(), signal::SigSet::empty()); + + let sigint_old = signal::sigaction(signal::Signal::SIGINT, &new_action)?; + if !overwrite && !matches!(sigint_old.handler(), signal::SigHandler::SigDfl) { + signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); + return Err(nix::Error::EEXIST); + } + + #[cfg(feature = "termination")] + { + let sigterm_old = match signal::sigaction(signal::Signal::SIGTERM, &new_action) { + Ok(old) => old, + Err(e) => { + signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); + return Err(e); + } + }; + if !overwrite && !matches!(sigterm_old.handler(), signal::SigHandler::SigDfl) { + signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); + signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); + return Err(nix::Error::EEXIST); + } + let sighup_old = match signal::sigaction(signal::Signal::SIGHUP, &new_action) { + Ok(old) => old, + Err(e) => { + signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); + signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); + return Err(e); + } + }; + if !overwrite && !matches!(sighup_old.handler(), signal::SigHandler::SigDfl) { + signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); + signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); + signal::sigaction(signal::Signal::SIGHUP, &sighup_old).unwrap(); + return Err(nix::Error::EEXIST); + } + } + + Ok(()) +} + +/// Blocks until a Ctrl-C signal is received. +/// +/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html). +/// +/// # Errors +/// None. +/// +#[inline] +pub unsafe fn block_ctrl_c() -> Result<(), CtrlcError> { + implementation::sem_wait_forever(); + Ok(()) +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/platform/windows/mod.rs b/userland/upstream-src/ctrlc-3.5.2/src/platform/windows/mod.rs new file mode 100644 index 0000000000..6cd8cfc26f --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/platform/windows/mod.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2017 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::io; +use std::ptr; +use windows_sys::core::BOOL; +use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, WAIT_FAILED, WAIT_OBJECT_0}; +use windows_sys::Win32::System::Console::SetConsoleCtrlHandler; +use windows_sys::Win32::System::Threading::{ + CreateSemaphoreA, ReleaseSemaphore, WaitForSingleObject, INFINITE, +}; + +/// Platform specific error type +pub type Error = io::Error; + +/// Platform specific signal type +pub type Signal = u32; + +const MAX_SEM_COUNT: i32 = 255; +static mut SEMAPHORE: HANDLE = 0 as HANDLE; +const TRUE: BOOL = 1; +const FALSE: BOOL = 0; + +unsafe extern "system" fn os_handler(_: u32) -> BOOL { + // Assuming this always succeeds. Can't really handle errors in any meaningful way. + ReleaseSemaphore(SEMAPHORE, 1, ptr::null_mut()); + TRUE +} + +/// Register os signal handler. +/// +/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html) +/// and should only be called once. +/// +/// # Errors +/// Will return an error if a system error occurred. +/// +#[inline] +pub unsafe fn init_os_handler(_overwrite: bool) -> Result<(), Error> { + SEMAPHORE = CreateSemaphoreA(ptr::null_mut(), 0, MAX_SEM_COUNT, ptr::null()); + if SEMAPHORE.is_null() { + return Err(io::Error::last_os_error()); + } + + if SetConsoleCtrlHandler(Some(os_handler), TRUE) == FALSE { + let e = io::Error::last_os_error(); + CloseHandle(SEMAPHORE); + SEMAPHORE = 0 as HANDLE; + return Err(e); + } + + Ok(()) +} + +/// Blocks until a Ctrl-C signal is received. +/// +/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html). +/// +/// # Errors +/// Will return an error if a system error occurred. +/// +#[inline] +pub unsafe fn block_ctrl_c() -> Result<(), Error> { + match WaitForSingleObject(SEMAPHORE, INFINITE) { + WAIT_OBJECT_0 => Ok(()), + WAIT_FAILED => Err(io::Error::last_os_error()), + ret => Err(io::Error::new( + io::ErrorKind::Other, + format!( + "WaitForSingleObject(), unexpected return value \"{:x}\"", + ret + ), + )), + } +} diff --git a/userland/upstream-src/ctrlc-3.5.2/src/signal.rs b/userland/upstream-src/ctrlc-3.5.2/src/signal.rs new file mode 100644 index 0000000000..5b6a64ef45 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/src/signal.rs @@ -0,0 +1,23 @@ +// Copyright (c) 2017 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::platform; + +/// A cross-platform way to represent Ctrl-C or program termination signal. Other +/// signals/events are supported via `Other`-variant. +#[derive(Debug)] +pub enum SignalType { + /// Ctrl-C + Ctrlc, + /// Program termination + /// Maps to `SIGTERM` and `SIGHUP` on *nix, `CTRL_CLOSE_EVENT` on Windows. + Termination, + /// Other signal/event using platform-specific data + Other(platform::Signal), +} diff --git a/userland/upstream-src/ctrlc-3.5.2/tests/main/harness.rs b/userland/upstream-src/ctrlc-3.5.2/tests/main/harness.rs new file mode 100644 index 0000000000..24113f8f56 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/tests/main/harness.rs @@ -0,0 +1,251 @@ +// Copyright (c) 2023 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +#[cfg(unix)] +pub mod platform { + use std::io; + + pub unsafe fn setup() -> io::Result<()> { + Ok(()) + } + + pub unsafe fn cleanup() -> io::Result<()> { + Ok(()) + } + + pub unsafe fn raise_ctrl_c() { + nix::sys::signal::raise(nix::sys::signal::SIGINT).unwrap(); + } + + pub unsafe fn print(fmt: ::std::fmt::Arguments) { + use self::io::Write; + let stdout = ::std::io::stdout(); + stdout.lock().write_fmt(fmt).unwrap(); + } +} + +#[cfg(windows)] +pub mod platform { + use std::io; + use std::ptr; + use windows_sys::Win32::Foundation::{ + GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, + }; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileA, WriteFile, FILE_SHARE_WRITE, OPEN_EXISTING, + }; + use windows_sys::Win32::System::Console::{ + AllocConsole, AttachConsole, FreeConsole, GenerateConsoleCtrlEvent, GetConsoleMode, + GetStdHandle, SetStdHandle, ATTACH_PARENT_PROCESS, CTRL_C_EVENT, STD_ERROR_HANDLE, + STD_OUTPUT_HANDLE, + }; + + /// Stores a piped stdout handle or a cache that gets + /// flushed when we reattached to the old console. + enum Output { + Pipe(HANDLE), + Cached(Vec), + } + + static mut OLD_OUT: *mut Output = 0 as *mut Output; + + impl io::Write for Output { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + Output::Pipe(handle) => unsafe { + let mut n = 0u32; + if WriteFile( + handle, + buf.as_ptr(), + buf.len() as u32, + &mut n as *mut u32, + ptr::null_mut(), + ) == 0 + { + Err(io::Error::last_os_error()) + } else { + Ok(n as usize) + } + }, + Output::Cached(ref mut s) => s.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl Output { + /// Stores current piped stdout or creates a new output cache that will + /// be written to stdout at a later time. + fn new() -> io::Result { + unsafe { + let stdout = GetStdHandle(STD_OUTPUT_HANDLE); + if stdout.is_null() || stdout == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let mut out = 0u32; + match GetConsoleMode(stdout, &mut out as *mut u32) { + 0 => Ok(Output::Pipe(stdout)), + _ => Ok(Output::Cached(Vec::new())), + } + } + } + + /// Set stdout/stderr and flush cache. + unsafe fn set_as_std(self) -> io::Result<()> { + let stdout = match self { + Output::Pipe(h) => h, + Output::Cached(_) => get_stdout()?, + }; + + if SetStdHandle(STD_OUTPUT_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + if SetStdHandle(STD_ERROR_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + match self { + Output::Pipe(_) => Ok(()), + Output::Cached(ref s) => { + // Write cached output + use self::io::Write; + let out = io::stdout(); + out.lock().write_all(&s[..])?; + Ok(()) + } + } + } + } + + unsafe fn get_stdout() -> io::Result { + let stdout = CreateFileA( + "CONOUT$\0".as_ptr(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, + ptr::null_mut(), + OPEN_EXISTING, + 0, + 0 as HANDLE, + ); + + if stdout.is_null() || stdout == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(stdout) + } + } + + /// Detach from the current console and create a new one, + /// We do this because GenerateConsoleCtrlEvent() sends ctrl-c events + /// to all processes on the same console. We want events to be received + /// only by our process. + /// + /// This breaks rust's stdout pre 1.18.0. Rust used to + /// [cache the std handles](https://github.com/rust-lang/rust/pull/40516) + /// + pub unsafe fn setup() -> io::Result<()> { + let old_out = Output::new()?; + + if FreeConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + if AllocConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + // AllocConsole will not always set stdout/stderr to the to the console buffer + // of the new terminal. + + let stdout = get_stdout()?; + if SetStdHandle(STD_OUTPUT_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + if SetStdHandle(STD_ERROR_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + OLD_OUT = Box::into_raw(Box::new(old_out)); + + Ok(()) + } + + /// Reattach to the old console. + pub unsafe fn cleanup() -> io::Result<()> { + if FreeConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + if AttachConsole(ATTACH_PARENT_PROCESS) == 0 { + return Err(io::Error::last_os_error()); + } + + Box::from_raw(OLD_OUT).set_as_std()?; + + Ok(()) + } + + /// This will signal the whole process group. + pub unsafe fn raise_ctrl_c() { + assert!(GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) != 0); + } + + /// Print to both consoles, this is not thread safe. + pub unsafe fn print(fmt: ::std::fmt::Arguments) { + use self::io::Write; + { + let stdout = io::stdout(); + stdout.lock().write_fmt(fmt).unwrap(); + } + { + assert!(!OLD_OUT.is_null()); + (*OLD_OUT).write_fmt(fmt).unwrap(); + } + } +} + +macro_rules! run_tests { + ( $($test_fn:ident),* ) => { + unsafe { + $( + harness::platform::print(format_args!("test {} ... ", stringify!($test_fn))); + $test_fn(); + harness::platform::print(format_args!("ok\n")); + )* + } + } +} + +pub fn run_harness(f: fn()) { + unsafe { + platform::setup().unwrap(); + } + + let default = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + unsafe { + platform::cleanup().unwrap(); + } + (default)(info); + })); + + println!(""); + f(); + println!(""); + + unsafe { + platform::cleanup().unwrap(); + } +} diff --git a/userland/upstream-src/ctrlc-3.5.2/tests/main/issue_97.rs b/userland/upstream-src/ctrlc-3.5.2/tests/main/issue_97.rs new file mode 100644 index 0000000000..97917327d0 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/tests/main/issue_97.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2023 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +#[macro_use] +mod harness; +use harness::{platform, run_harness}; + +mod test_signal_hook; +use test_signal_hook::run_signal_hook; + +fn expect_multiple_handlers() { + #[cfg(not(windows))] + match ctrlc::try_set_handler(|| {}) { + Err(ctrlc::Error::MultipleHandlers) => {} + _ => panic!("Expected Error::MultipleHandlers"), + } +} + +fn tests() { + run_tests!(run_signal_hook); + run_tests!(expect_multiple_handlers); +} + +fn main() { + run_harness(tests); +} diff --git a/userland/upstream-src/ctrlc-3.5.2/tests/main/mod.rs b/userland/upstream-src/ctrlc-3.5.2/tests/main/mod.rs new file mode 100644 index 0000000000..94914d45fb --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/tests/main/mod.rs @@ -0,0 +1,46 @@ +// Copyright (c) 2023 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +#[macro_use] +mod harness; +use harness::{platform, run_harness}; + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +fn test_set_handler() { + let flag = Arc::new(AtomicBool::new(false)); + let flag_handler = Arc::clone(&flag); + ctrlc::set_handler(move || { + flag_handler.store(true, Ordering::SeqCst); + }) + .unwrap(); + + unsafe { + platform::raise_ctrl_c(); + } + + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(flag.load(Ordering::SeqCst)); + + match ctrlc::set_handler(|| {}) { + Err(ctrlc::Error::MultipleHandlers) => {} + ret => panic!("{:?}", ret), + } +} + +fn tests() { + run_tests!(test_set_handler); +} + +fn main() { + run_harness(tests); +} diff --git a/userland/upstream-src/ctrlc-3.5.2/tests/main/test_signal_hook.rs b/userland/upstream-src/ctrlc-3.5.2/tests/main/test_signal_hook.rs new file mode 100644 index 0000000000..98d8736697 --- /dev/null +++ b/userland/upstream-src/ctrlc-3.5.2/tests/main/test_signal_hook.rs @@ -0,0 +1,26 @@ +// Copyright (c) 2023 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +pub fn run_signal_hook() { + let hook = Arc::new(AtomicBool::new(false)); + + signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&hook)).unwrap(); + + unsafe { + super::platform::raise_ctrl_c(); + } + + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(hook.load(Ordering::SeqCst)); +} diff --git a/userland/upstream-src/dirs-6.0.0/.cargo-ok b/userland/upstream-src/dirs-6.0.0/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/dirs-6.0.0/.cargo_vcs_info.json b/userland/upstream-src/dirs-6.0.0/.cargo_vcs_info.json new file mode 100644 index 0000000000..7ea09117b1 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "00511bdc252f491a72ac89f6d6a2463406266fb2" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/dirs-6.0.0/.github/workflows/rust.yml b/userland/upstream-src/dirs-6.0.0/.github/workflows/rust.yml new file mode 100644 index 0000000000..09321aed65 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/.github/workflows/rust.yml @@ -0,0 +1,27 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + RUST_BACKTRACE: full + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Test + run: cargo test --verbose -- --nocapture diff --git a/userland/upstream-src/dirs-6.0.0/.gitignore b/userland/upstream-src/dirs-6.0.0/.gitignore new file mode 100644 index 0000000000..010758b716 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/.gitignore @@ -0,0 +1,4 @@ +Cargo.lock +/target +**/*.rs.bk +.idea diff --git a/userland/upstream-src/dirs-6.0.0/Cargo.toml b/userland/upstream-src/dirs-6.0.0/Cargo.toml new file mode 100644 index 0000000000..f18a737544 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/Cargo.toml @@ -0,0 +1,39 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +name = "dirs" +version = "6.0.0" +authors = ["Simon Ochsenreither "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A tiny low-level library that provides platform-specific standard locations of directories for config, cache and other data on Linux, Windows, macOS and Redox by leveraging the mechanisms defined by the XDG base/user directory specifications on Linux, the Known Folder API on Windows, and the Standard Directory guidelines on macOS." +readme = "README.md" +keywords = [ + "xdg", + "basedir", + "app_dirs", + "path", + "folder", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/soc/dirs-rs" + +[lib] +name = "dirs" +path = "src/lib.rs" + +[dependencies.dirs-sys] +version = "0.5.0" diff --git a/userland/upstream-src/dirs-6.0.0/Cargo.toml.orig b/userland/upstream-src/dirs-6.0.0/Cargo.toml.orig new file mode 100644 index 0000000000..1fea42df5c --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/Cargo.toml.orig @@ -0,0 +1,13 @@ +[package] +name = "dirs" +version = "6.0.0" +authors = ["Simon Ochsenreither "] +description = "A tiny low-level library that provides platform-specific standard locations of directories for config, cache and other data on Linux, Windows, macOS and Redox by leveraging the mechanisms defined by the XDG base/user directory specifications on Linux, the Known Folder API on Windows, and the Standard Directory guidelines on macOS." +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/soc/dirs-rs" +maintenance = { status = "actively-developed" } +keywords = ["xdg", "basedir", "app_dirs", "path", "folder"] + +[dependencies] +dirs-sys = "0.5.0" diff --git a/userland/upstream-src/dirs-6.0.0/LICENSE-APACHE b/userland/upstream-src/dirs-6.0.0/LICENSE-APACHE new file mode 100644 index 0000000000..91e18a62b6 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/LICENSE-APACHE @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/userland/upstream-src/dirs-6.0.0/LICENSE-MIT b/userland/upstream-src/dirs-6.0.0/LICENSE-MIT new file mode 100644 index 0000000000..1452dc1432 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 dirs-rs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/userland/upstream-src/dirs-6.0.0/README.md b/userland/upstream-src/dirs-6.0.0/README.md new file mode 100644 index 0000000000..fefcd3c481 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/README.md @@ -0,0 +1,226 @@ +[![crates.io](https://img.shields.io/crates/v/dirs.svg?style=for-the-badge)](https://crates.io/crates/dirs) +[![API documentation](https://img.shields.io/docsrs/dirs/latest?style=for-the-badge)](https://docs.rs/dirs/) +![actively developed](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg?style=for-the-badge) +![License: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-orange.svg?style=for-the-badge) + +# `dirs` + +## Introduction + +- a tiny low-level library with a minimal API +- that provides the platform-specific, user-accessible locations +- for retrieving and storing configuration, cache and other data +- on Linux, Redox, Windows (≥ Vista), macOS and other platforms. + +The library provides the location of these directories by leveraging the mechanisms defined by +- the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and + the [XDG user directory](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/) specifications on Linux and Redox +- the [Known Folder](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx) API on Windows +- the [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6) + guidelines on macOS + +## Platforms + +This library is written in Rust, and supports Linux, Redox, macOS and Windows. +Other platforms are also supported; they use the Linux conventions. + +The minimal required version of Rust is 1.13 except for Redox, where the minimum Rust version +depends on the [`redox_users`](https://crates.io/crates/redox_users) crate. + +It's mid-level sister library, _directories_, is available for Rust ([directories-rs](https://github.com/dirs-dev/directories-rs)) +and on the JVM ([directories-jvm](https://github.com/dirs-dev/directories-jvm)). + +## Usage + +#### Dependency + +Add the library as a dependency to your project by inserting + +```toml +dirs = "5.0" +``` + +into the `[dependencies]` section of your Cargo.toml file. + +If you are upgrading from version 2, please read the [section on breaking changes](#3) first. + +#### Example + +Library run by user Alice: + +```rust +extern crate dirs; + +dirs::home_dir(); +// Lin: Some(/home/alice) +// Win: Some(C:\Users\Alice) +// Mac: Some(/Users/Alice) + +dirs::audio_dir(); +// Lin: Some(/home/alice/Music) +// Win: Some(C:\Users\Alice\Music) +// Mac: Some(/Users/Alice/Music) + +dirs::config_dir(); +// Lin: Some(/home/alice/.config) +// Win: Some(C:\Users\Alice\AppData\Roaming) +// Mac: Some(/Users/Alice/Library/Application Support) + +dirs::executable_dir(); +// Lin: Some(/home/alice/.local/bin) +// Win: None +// Mac: None +``` + +## Design Goals + +- The _dirs_ library is a low-level crate designed to provide the paths to standard directories + as defined by operating systems rules or conventions.
+ If your requirements are more complex, e. g. computing cache, config, etc. paths for specific + applications or projects, consider using [directories](https://github.com/dirs-dev/directories-rs) + instead. +- This library does not create directories or check for their existence. The library only provides + information on what the path to a certain directory _should_ be.
+ How this information is used is a decision that developers need to make based on the requirements + of each individual application. +- This library is intentionally focused on providing information on user-writable directories only, + as there is no discernible benefit in returning a path that points to a user-level, writable + directory on one operating system, but a system-level, read-only directory on another.
+ The confusion and unexpected failure modes of such an approach would be immense. + - `executable_dir` is specified to provide the path to a user-writable directory for binaries.
+ As such a directory only commonly exists on Linux, it returns `None` on macOS and Windows. + - `font_dir` is specified to provide the path to a user-writable directory for fonts.
+ As such a directory only exists on Linux and macOS, it returns `None` on Windows. + - `runtime_dir` is specified to provide the path to a directory for non-essential runtime data. + It is required that this directory is created when the user logs in, is only accessible by the + user itself, is deleted when the user logs out, and supports all filesystem features of the + operating system.
+ As such a directory only commonly exists on Linux, it returns `None` on macOS and Windows. + +## Features + +**If you want to compute the location of cache, config or data directories for your own application or project, +use `ProjectDirs` of the [directories](https://github.com/dirs-dev/directories-rs) project instead.** + +| Function name | Value on Linux/Redox | Value on Windows | Value on macOS | +|--------------------| ---------------------------------------------------------------------- |-----------------------------------| ------------------------------------------- | +| `home_dir` | `Some($HOME)` | `Some({FOLDERID_Profile})` | `Some($HOME)` | +| `cache_dir` | `Some($XDG_CACHE_HOME)` or `Some($HOME`/.cache`)` | `Some({FOLDERID_LocalAppData})` | `Some($HOME`/Library/Caches`)` | +| `config_dir` | `Some($XDG_CONFIG_HOME)` or `Some($HOME`/.config`)` | `Some({FOLDERID_RoamingAppData})` | `Some($HOME`/Library/Application Support`)` | +| `config_local_dir` | `Some($XDG_CONFIG_HOME)` or `Some($HOME`/.config`)` | `Some({FOLDERID_LocalAppData})` | `Some($HOME`/Library/Application Support`)` | +| `data_dir` | `Some($XDG_DATA_HOME)` or `Some($HOME`/.local/share`)` | `Some({FOLDERID_RoamingAppData})` | `Some($HOME`/Library/Application Support`)` | +| `data_local_dir` | `Some($XDG_DATA_HOME)` or `Some($HOME`/.local/share`)` | `Some({FOLDERID_LocalAppData})` | `Some($HOME`/Library/Application Support`)` | +| `executable_dir` | `Some($XDG_BIN_HOME)` or `Some($HOME`/.local/bin`)` | `None` | `None` | +| `preference_dir` | `Some($XDG_CONFIG_HOME)` or `Some($HOME`/.config`)` | `Some({FOLDERID_RoamingAppData})` | `Some($HOME`/Library/Preferences`)` | +| `runtime_dir` | `Some($XDG_RUNTIME_DIR)` or `None` | `None` | `None` | +| `state_dir` | `Some($XDG_STATE_HOME)` or `Some($HOME`/.local/state`)` | `None` | `None` | +| `audio_dir` | `Some(XDG_MUSIC_DIR)` or `None` | `Some({FOLDERID_Music})` | `Some($HOME`/Music/`)` | +| `desktop_dir` | `Some(XDG_DESKTOP_DIR)` or `None` | `Some({FOLDERID_Desktop})` | `Some($HOME`/Desktop/`)` | +| `document_dir` | `Some(XDG_DOCUMENTS_DIR)` or `None` | `Some({FOLDERID_Documents})` | `Some($HOME`/Documents/`)` | +| `download_dir` | `Some(XDG_DOWNLOAD_DIR)` or `None` | `Some({FOLDERID_Downloads})` | `Some($HOME`/Downloads/`)` | +| `font_dir` | `Some($XDG_DATA_HOME`/fonts/`)` or `Some($HOME`/.local/share/fonts/`)` | `None` | `Some($HOME`/Library/Fonts/`)` | +| `picture_dir` | `Some(XDG_PICTURES_DIR)` or `None` | `Some({FOLDERID_Pictures})` | `Some($HOME`/Pictures/`)` | +| `public_dir` | `Some(XDG_PUBLICSHARE_DIR)` or `None` | `Some({FOLDERID_Public})` | `Some($HOME`/Public/`)` | +| `template_dir` | `Some(XDG_TEMPLATES_DIR)` or `None` | `Some({FOLDERID_Templates})` | `None` | +| `video_dir` | `Some(XDG_VIDEOS_DIR)` or `None` | `Some({FOLDERID_Videos})` | `Some($HOME`/Movies/`)` | + +## Comparison + +There are other crates in the Rust ecosystem that try similar or related things. +Here is an overview of them, combined with ratings on properties that guided the design of this crate. + +Please take this table with a grain of salt: a different crate might very well be more suitable for your specific use case. +(Of course _my_ crate achieves _my_ design goals better than other crates, which might have had different design goals.) + +| Library | Status | Lin | Mac | Win |Base|User|Proj|Conv| +| --------------------------------------------------------- | -------------- |:---:|:---:|:---:|:--:|:--:|:--:|:--:| +| [app_dirs](https://crates.io/crates/app_dirs) | Unmaintained | ✔ | ✔ | ✔ | 🞈 | ✖ | ✔ | ✖ | +| [app_dirs2](https://crates.io/crates/app_dirs2) | Maintained | ✔ | ✔ | ✔ | 🞈 | ✖ | ✔ | ✖ | +| **dirs** | **Developed** | ✔ | ✔ | ✔ | ✔ | ✔ | ✖ | ✔ | +| [directories](https://crates.io/crates/directories) | Developed | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| [s_app_dir](https://crates.io/crates/s_app_dir) | Unmaintained? | ✔ | ✖ | 🞈 | ✖ | ✖ | 🞈 | ✖ | +| [standard_paths](https://crates.io/crates/standard_paths) | Maintained | ✔ | ✖ | ✔ | ✔ | ✔ | ✔ | ✖ | +| [xdg](https://crates.io/crates/xdg) | Maintained | ✔ | ✖ | ✖ | ✔ | ✖ | ✔ | 🞈 | +| [xdg-basedir](https://crates.io/crates/xdg-basedir) | Unmaintained? | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | +| [xdg-rs](https://crates.io/crates/xdg-rs) | Obsolete | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | + +- Lin: Linux support +- Mac: macOS support +- Win: Windows support +- Base: Supports [generic base directories](https://github.com/dirs-dev/directories-rs#basedirs) +- User: Supports [user directories](https://github.com/dirs-dev/directories-rs#userdirs) +- Proj: Supports [project-specific base directories](https://github.com/dirs-dev/directories-rs#projectdirs) +- Conv: Follows naming conventions of the operating system it runs on + +## Build + +It's possible to cross-compile this library if the necessary toolchains are installed with rustup. +This is helpful to ensure a change hasn't broken code on a different platform. + +The following commands will build this library on Linux, macOS and Windows: + +``` +cargo build --target=x86_64-unknown-linux-gnu +cargo build --target=x86_64-pc-windows-gnu +cargo build --target=x86_64-apple-darwin +cargo build --target=x86_64-unknown-redox +``` + +## Changelog + +### 6 + +- Update `dirs-sys` dependency to `0.5.0`, which in turn updates `windows-sys` dependency to `0.59.0`. + +### 5 + +- Update `dirs-sys` dependency to `0.4.0`. +- Add `config_local_dir` for non-roaming configuration on Windows. On non-Windows platforms the behavior is identical to `config dir`. + +### 4 + +- **BREAKING CHANGE** The behavior of `executable_dir` has been adjusted to not depend on `$XDG_DATA_HOME`. + Code, which assumed that setting the `$XDG_DATA_HOME` environment variable also impacted `executable_dir` if + the `$XDG_BIN_HOME` environment variable was not set, requires adjustment. +- Add support for `XDG_STATE_HOME`. + +### 3 + +- **BREAKING CHANGE** The behavior of `config_dir` on macOS has been adjusted + (thanks to [everyone involved](https://github.com/dirs-dev/directories-rs/issues/62)): + - The existing `config_dir` function has been changed to return the `Application Support` + directory on macOS, as suggested by Apple documentation. + - The behavior of the `config_dir` function on non-macOS platforms has not been changed. + - If you have used the `config_dir` function to store files, it may be necessary to write code + that migrates the files to the new location on macOS.
+ (Alternative: change uses of the `config_dir` function to uses of the `preference_dir` function + to retain the old behavior.) +- The newly added `preference_dir` function returns the `Preferences` directory on macOS now, + which – according to Apple documentation – shall only be used to store .plist files using + Apple-proprietary APIs. + – `preference_dir` and `config_dir` behave identical on non-macOS platforms. + +### 2 + +**BREAKING CHANGE** The behavior of deactivated, missing or invalid [_XDG User Dirs_](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/) +entries on Linux has been improved (contributed by @tmiasko, thank you!): + +- Version 1 returned the user's home directory (`Some($HOME)`) for such faulty entries, except for a faulty `XDG_DESKTOP_DIR` entry which returned (`Some($HOME/Desktop)`). +- Version 2 returns `None` for such entries. + +## License + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/userland/upstream-src/dirs-6.0.0/src/lib.rs b/userland/upstream-src/dirs-6.0.0/src/lib.rs new file mode 100644 index 0000000000..cc0300b9e9 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/src/lib.rs @@ -0,0 +1,328 @@ +//! The _dirs_ crate is +//! +//! - a tiny library with a minimal API (18 public functions) +//! - that provides the platform-specific, user-accessible locations +//! - for finding and storing configuration, cache and other data +//! - on Linux, Redox, Windows (≥ Vista) and macOS. +//! +//! The library provides the location of these directories by leveraging the mechanisms defined by +//! +//! - the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and the [XDG user directory](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/) specifications on Linux, +//! - the [Known Folder](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776911(v=vs.85).aspx) system on Windows, and +//! - the [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6) on macOS. + +#![deny(missing_docs)] + +use std::path::PathBuf; + +#[cfg(target_os = "windows")] +mod win; +#[cfg(target_os = "windows")] +use win as sys; + +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod mac; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use mac as sys; + +#[cfg(target_arch = "wasm32")] +mod wasm; +#[cfg(target_arch = "wasm32")] +use wasm as sys; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", target_os = "ios", + target_arch = "wasm32" +)))] +mod lin; +#[cfg(not(any( + target_os = "windows", + target_os = "macos", target_os = "ios", + target_arch = "wasm32" +)))] +use lin as sys; + +/// Returns the path to the user's home directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | -------------------- | -------------- | +/// | Linux | `$HOME` | /home/alice | +/// | macOS | `$HOME` | /Users/Alice | +/// | Windows | `{FOLDERID_Profile}` | C:\Users\Alice | +/// +/// ### Linux and macOS: +/// +/// - Use `$HOME` if it is set and not empty. +/// - If `$HOME` is not set or empty, then the function `getpwuid_r` is used to determine +/// the home directory of the current user. +/// - If `getpwuid_r` lacks an entry for the current user id or the home directory field is empty, +/// then the function returns `None`. +/// +/// ### Windows: +/// +/// This function retrieves the user profile folder using `SHGetKnownFolderPath`. +/// +/// All the examples on this page mentioning `$HOME` use this behavior. +/// +/// _Note:_ This function's behavior differs from [`std::env::home_dir`], +/// which works incorrectly on Linux, macOS and Windows. +/// +/// [`std::env::home_dir`]: https://doc.rust-lang.org/std/env/fn.home_dir.html +pub fn home_dir() -> Option { + sys::home_dir() +} +/// Returns the path to the user's cache directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ----------------------------------- | ---------------------------- | +/// | Linux | `$XDG_CACHE_HOME` or `$HOME`/.cache | /home/alice/.cache | +/// | macOS | `$HOME`/Library/Caches | /Users/Alice/Library/Caches | +/// | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | +pub fn cache_dir() -> Option { + sys::cache_dir() +} +/// Returns the path to the user's config directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------------------------- | ---------------------------------------- | +/// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | +/// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | +/// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | +pub fn config_dir() -> Option { + sys::config_dir() +} +/// Returns the path to the user's local config directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------------------------- | ---------------------------------------- | +/// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | +/// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | +/// | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | +pub fn config_local_dir() -> Option { + sys::config_local_dir() +} +/// Returns the path to the user's data directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------------------------- | ---------------------------------------- | +/// | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | +/// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | +/// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | +pub fn data_dir() -> Option { + sys::data_dir() +} +/// Returns the path to the user's local data directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------------------------- | ---------------------------------------- | +/// | Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share | +/// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support | +/// | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | +pub fn data_local_dir() -> Option { + sys::data_local_dir() +} +/// Returns the path to the user's executable directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------------------------------------------------- | ---------------------- | +/// | Linux | `$XDG_BIN_HOME` or `$XDG_DATA_HOME`/../bin or `$HOME`/.local/bin | /home/alice/.local/bin | +/// | macOS | – | – | +/// | Windows | – | – | +pub fn executable_dir() -> Option { + sys::executable_dir() +} +/// Returns the path to the user's preference directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------------------------- | -------------------------------- | +/// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | +/// | macOS | `$HOME`/Library/Preferences | /Users/Alice/Library/Preferences | +/// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | +pub fn preference_dir() -> Option { + sys::preference_dir() +} +/// Returns the path to the user's runtime directory. +/// +/// The runtime directory contains transient, non-essential data (like sockets or named pipes) that +/// is expected to be cleared when the user's session ends. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------ | --------------- | +/// | Linux | `$XDG_RUNTIME_DIR` | /run/user/1001/ | +/// | macOS | – | – | +/// | Windows | – | – | +pub fn runtime_dir() -> Option { + sys::runtime_dir() +} +/// Returns the path to the user's state directory. +/// +/// The state directory contains data that should be retained between sessions (unlike the runtime +/// directory), but may not be important/portable enough to be synchronized across machines (unlike +/// the config/preferences/data directories). +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ----------------------------------------- | ------------------------ | +/// | Linux | `$XDG_STATE_HOME` or `$HOME`/.local/state | /home/alice/.local/state | +/// | macOS | – | – | +/// | Windows | – | – | +pub fn state_dir() -> Option { + sys::state_dir() +} + +/// Returns the path to the user's audio directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------ | -------------------- | +/// | Linux | `XDG_MUSIC_DIR` | /home/alice/Music | +/// | macOS | `$HOME`/Music | /Users/Alice/Music | +/// | Windows | `{FOLDERID_Music}` | C:\Users\Alice\Music | +pub fn audio_dir() -> Option { + sys::audio_dir() +} +/// Returns the path to the user's desktop directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | -------------------- | ---------------------- | +/// | Linux | `XDG_DESKTOP_DIR` | /home/alice/Desktop | +/// | macOS | `$HOME`/Desktop | /Users/Alice/Desktop | +/// | Windows | `{FOLDERID_Desktop}` | C:\Users\Alice\Desktop | +pub fn desktop_dir() -> Option { + sys::desktop_dir() +} +/// Returns the path to the user's document directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------- | ------------------------ | +/// | Linux | `XDG_DOCUMENTS_DIR` | /home/alice/Documents | +/// | macOS | `$HOME`/Documents | /Users/Alice/Documents | +/// | Windows | `{FOLDERID_Documents}` | C:\Users\Alice\Documents | +pub fn document_dir() -> Option { + sys::document_dir() +} +/// Returns the path to the user's download directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------- | ------------------------ | +/// | Linux | `XDG_DOWNLOAD_DIR` | /home/alice/Downloads | +/// | macOS | `$HOME`/Downloads | /Users/Alice/Downloads | +/// | Windows | `{FOLDERID_Downloads}` | C:\Users\Alice\Downloads | +pub fn download_dir() -> Option { + sys::download_dir() +} +/// Returns the path to the user's font directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------------------------------------- | ------------------------------ | +/// | Linux | `$XDG_DATA_HOME`/fonts or `$HOME`/.local/share/fonts | /home/alice/.local/share/fonts | +/// | macOS | `$HOME/Library/Fonts` | /Users/Alice/Library/Fonts | +/// | Windows | – | – | +pub fn font_dir() -> Option { + sys::font_dir() +} +/// Returns the path to the user's picture directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | --------------------- | ----------------------- | +/// | Linux | `XDG_PICTURES_DIR` | /home/alice/Pictures | +/// | macOS | `$HOME`/Pictures | /Users/Alice/Pictures | +/// | Windows | `{FOLDERID_Pictures}` | C:\Users\Alice\Pictures | +pub fn picture_dir() -> Option { + sys::picture_dir() +} +/// Returns the path to the user's public directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | --------------------- | ------------------- | +/// | Linux | `XDG_PUBLICSHARE_DIR` | /home/alice/Public | +/// | macOS | `$HOME`/Public | /Users/Alice/Public | +/// | Windows | `{FOLDERID_Public}` | C:\Users\Public | +pub fn public_dir() -> Option { + sys::public_dir() +} +/// Returns the path to the user's template directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ---------------------- | ---------------------------------------------------------- | +/// | Linux | `XDG_TEMPLATES_DIR` | /home/alice/Templates | +/// | macOS | – | – | +/// | Windows | `{FOLDERID_Templates}` | C:\Users\Alice\AppData\Roaming\Microsoft\Windows\Templates | +pub fn template_dir() -> Option { + sys::template_dir() +} + +/// Returns the path to the user's video directory. +/// +/// The returned value depends on the operating system and is either a `Some`, containing a value from the following table, or a `None`. +/// +/// |Platform | Value | Example | +/// | ------- | ------------------- | --------------------- | +/// | Linux | `XDG_VIDEOS_DIR` | /home/alice/Videos | +/// | macOS | `$HOME`/Movies | /Users/Alice/Movies | +/// | Windows | `{FOLDERID_Videos}` | C:\Users\Alice\Videos | +pub fn video_dir() -> Option { + sys::video_dir() +} + +#[cfg(test)] +mod tests { + #[test] + fn test_dirs() { + println!("home_dir: {:?}", ::home_dir()); + println!(); + println!("cache_dir: {:?}", ::cache_dir()); + println!("config_dir: {:?}", ::config_dir()); + println!("data_dir: {:?}", ::data_dir()); + println!("data_local_dir: {:?}", ::data_local_dir()); + println!("executable_dir: {:?}", ::executable_dir()); + println!("preference_dir: {:?}", ::preference_dir()); + println!("runtime_dir: {:?}", ::runtime_dir()); + println!("state_dir: {:?}", ::state_dir()); + println!(); + println!("audio_dir: {:?}", ::audio_dir()); + println!("desktop_dir: {:?}", ::desktop_dir()); + println!("document_dir: {:?}", ::document_dir()); + println!("download_dir: {:?}", ::download_dir()); + println!("font_dir: {:?}", ::font_dir()); + println!("picture_dir: {:?}", ::picture_dir()); + println!("public_dir: {:?}", ::public_dir()); + println!("template_dir: {:?}", ::template_dir()); + println!("video_dir: {:?}", ::video_dir()); + } +} diff --git a/userland/upstream-src/dirs-6.0.0/src/lin.rs b/userland/upstream-src/dirs-6.0.0/src/lin.rs new file mode 100644 index 0000000000..d1d3a00a7a --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/src/lin.rs @@ -0,0 +1,49 @@ +extern crate dirs_sys; + +use std::env; +use std::path::PathBuf; + +// On NONOS a capsule's home is the HOME environment variable when set and +// non-empty. There is no passwd database to fall back to, so an unset HOME +// simply means there is no home directory, and the XDG lookups below then have +// nothing to anchor to. Elsewhere this stays exactly the dirs-sys lookup. +#[cfg(target_vendor = "nonos")] +pub fn home_dir() -> Option { env::var_os("HOME").filter(|h| !h.is_empty()).map(PathBuf::from) } +#[cfg(not(target_vendor = "nonos"))] +pub fn home_dir() -> Option { dirs_sys::home_dir() } + +// The well-known user directories come from ~/.config/user-dirs.dirs, which a +// freshly spawned capsule does not carry, so they resolve to nothing on NONOS. +#[cfg(target_vendor = "nonos")] +fn user_dir(_name: &str) -> Option { None } +#[cfg(not(target_vendor = "nonos"))] +fn user_dir(name: &str) -> Option { dirs_sys::user_dir(name) } + +pub fn cache_dir() -> Option { env::var_os("XDG_CACHE_HOME") .and_then(dirs_sys::is_absolute_path).or_else(|| home_dir().map(|h| h.join(".cache"))) } +pub fn config_dir() -> Option { env::var_os("XDG_CONFIG_HOME").and_then(dirs_sys::is_absolute_path).or_else(|| home_dir().map(|h| h.join(".config"))) } +pub fn config_local_dir() -> Option { config_dir() } +pub fn data_dir() -> Option { env::var_os("XDG_DATA_HOME") .and_then(dirs_sys::is_absolute_path).or_else(|| home_dir().map(|h| h.join(".local/share"))) } +pub fn data_local_dir() -> Option { data_dir() } +pub fn preference_dir() -> Option { config_dir() } +pub fn runtime_dir() -> Option { env::var_os("XDG_RUNTIME_DIR").and_then(dirs_sys::is_absolute_path) } +pub fn state_dir() -> Option { env::var_os("XDG_STATE_HOME") .and_then(dirs_sys::is_absolute_path).or_else(|| home_dir().map(|h| h.join(".local/state"))) } +pub fn executable_dir() -> Option { env::var_os("XDG_BIN_HOME") .and_then(dirs_sys::is_absolute_path).or_else(|| home_dir().map(|h| h.join(".local/bin"))) } + +pub fn audio_dir() -> Option { user_dir("MUSIC") } +pub fn desktop_dir() -> Option { user_dir("DESKTOP") } +pub fn document_dir() -> Option { user_dir("DOCUMENTS") } +pub fn download_dir() -> Option { user_dir("DOWNLOAD") } +pub fn font_dir() -> Option { data_dir().map(|d| d.join("fonts")) } +pub fn picture_dir() -> Option { user_dir("PICTURES") } +pub fn public_dir() -> Option { user_dir("PUBLICSHARE") } +pub fn template_dir() -> Option { user_dir("TEMPLATES") } +pub fn video_dir() -> Option { user_dir("VIDEOS") } + +#[cfg(test)] +mod tests { + #[test] + fn test_file_user_dirs_exists() { + let user_dirs_file = ::config_dir().unwrap().join("user-dirs.dirs"); + println!("{:?} exists: {:?}", user_dirs_file, user_dirs_file.exists()); + } +} diff --git a/userland/upstream-src/dirs-6.0.0/src/mac.rs b/userland/upstream-src/dirs-6.0.0/src/mac.rs new file mode 100644 index 0000000000..d4636f18b9 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/src/mac.rs @@ -0,0 +1,27 @@ +extern crate dirs_sys; + +use std::path::PathBuf; + +pub fn home_dir() -> Option { dirs_sys::home_dir() } + +fn app_support_dir() -> Option { home_dir().map(|h| h.join("Library/Application Support")) } + +pub fn cache_dir() -> Option { home_dir().map(|h| h.join("Library/Caches")) } +pub fn config_dir() -> Option { app_support_dir() } +pub fn config_local_dir() -> Option { app_support_dir() } +pub fn data_dir() -> Option { app_support_dir() } +pub fn data_local_dir() -> Option { app_support_dir() } +pub fn preference_dir() -> Option { home_dir().map(|h| h.join("Library/Preferences")) } +pub fn executable_dir() -> Option { None } +pub fn runtime_dir() -> Option { None } +pub fn state_dir() -> Option { None } + +pub fn audio_dir() -> Option { home_dir().map(|h| h.join("Music")) } +pub fn desktop_dir() -> Option { home_dir().map(|h| h.join("Desktop")) } +pub fn document_dir() -> Option { home_dir().map(|h| h.join("Documents")) } +pub fn download_dir() -> Option { home_dir().map(|h| h.join("Downloads")) } +pub fn font_dir() -> Option { home_dir().map(|h| h.join("Library/Fonts")) } +pub fn picture_dir() -> Option { home_dir().map(|h| h.join("Pictures")) } +pub fn public_dir() -> Option { home_dir().map(|h| h.join("Public")) } +pub fn template_dir() -> Option { None } +pub fn video_dir() -> Option { home_dir().map(|h| h.join("Movies")) } diff --git a/userland/upstream-src/dirs-6.0.0/src/wasm.rs b/userland/upstream-src/dirs-6.0.0/src/wasm.rs new file mode 100644 index 0000000000..3ee1d27ea1 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/src/wasm.rs @@ -0,0 +1,25 @@ +// Stub definitions to make things *compile*. + +use std::path::PathBuf; + +pub fn home_dir() -> Option { None } + +pub fn cache_dir() -> Option { None } +pub fn config_dir() -> Option { None } +pub fn config_local_dir() -> Option { None } +pub fn data_dir() -> Option { None } +pub fn data_local_dir() -> Option { None } +pub fn preference_dir() -> Option { None } +pub fn runtime_dir() -> Option { None } +pub fn executable_dir() -> Option { None } +pub fn state_dir() -> Option { None } + +pub fn audio_dir() -> Option { None } +pub fn desktop_dir() -> Option { None } +pub fn document_dir() -> Option { None } +pub fn download_dir() -> Option { None } +pub fn font_dir() -> Option { None } +pub fn picture_dir() -> Option { None } +pub fn public_dir() -> Option { None } +pub fn template_dir() -> Option { None } +pub fn video_dir() -> Option { None } diff --git a/userland/upstream-src/dirs-6.0.0/src/win.rs b/userland/upstream-src/dirs-6.0.0/src/win.rs new file mode 100644 index 0000000000..46bbab4253 --- /dev/null +++ b/userland/upstream-src/dirs-6.0.0/src/win.rs @@ -0,0 +1,25 @@ +extern crate dirs_sys; + +use std::path::PathBuf; + +pub fn home_dir() -> Option { dirs_sys::known_folder_profile() } + +pub fn cache_dir() -> Option { data_local_dir() } +pub fn config_dir() -> Option { dirs_sys::known_folder_roaming_app_data() } +pub fn config_local_dir() -> Option { dirs_sys::known_folder_local_app_data() } +pub fn data_dir() -> Option { dirs_sys::known_folder_roaming_app_data() } +pub fn data_local_dir() -> Option { dirs_sys::known_folder_local_app_data() } +pub fn executable_dir() -> Option { None } +pub fn preference_dir() -> Option { dirs_sys::known_folder_local_app_data() } +pub fn runtime_dir() -> Option { None } +pub fn state_dir() -> Option { None } + +pub fn audio_dir() -> Option { dirs_sys::known_folder_music() } +pub fn desktop_dir() -> Option { dirs_sys::known_folder_desktop() } +pub fn document_dir() -> Option { dirs_sys::known_folder_documents() } +pub fn download_dir() -> Option { dirs_sys::known_folder_downloads() } +pub fn font_dir() -> Option { None } +pub fn picture_dir() -> Option { dirs_sys::known_folder_pictures() } +pub fn public_dir() -> Option { dirs_sys::known_folder_public()} +pub fn template_dir() -> Option { dirs_sys::known_folder_templates() } +pub fn video_dir() -> Option { dirs_sys::known_folder_videos() } diff --git a/userland/upstream-src/errno-0.3.14/.cargo-ok b/userland/upstream-src/errno-0.3.14/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/errno-0.3.14/.cargo_vcs_info.json b/userland/upstream-src/errno-0.3.14/.cargo_vcs_info.json new file mode 100644 index 0000000000..cf97390980 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "ffc03bfb9eb491013567115e6eea560948cd9e52" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/errno-0.3.14/.github/dependabot.yml b/userland/upstream-src/errno-0.3.14/.github/dependabot.yml new file mode 100644 index 0000000000..98e44ee5f7 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/userland/upstream-src/errno-0.3.14/.github/workflows/main.yml b/userland/upstream-src/errno-0.3.14/.github/workflows/main.yml new file mode 100644 index 0000000000..cba4b9ee00 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/.github/workflows/main.yml @@ -0,0 +1,89 @@ +on: + push: + branches: + - main + pull_request: + schedule: + - cron: '5 21 * * 5' + workflow_dispatch: + +name: CI + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [stable, nightly, '1.56'] + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - name: Pin to old dependency versions (Rust 1.56 only) + if: matrix.rust == '1.56' + run: | + cargo update -p libc --precise 0.2.163 + cargo update -p windows-sys --precise 0.52.0 + - name: Setup cache + uses: Swatinem/rust-cache@v2 + - name: Test (no features) + run: cargo test --no-default-features + - name: Test (all features) + run: cargo test --all-features + + + wasi: + name: Test WASI + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-wasip2 + - name: Install wasmtime + run: | + curl https://wasmtime.dev/install.sh -sSf | bash + echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + - name: Test (no features) + run: CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime cargo test --target wasm32-wasip2 --no-default-features + - name: Test (all features) + run: CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime cargo test --target wasm32-wasip2 --all-features + + emscripten: + name: Test Emscripten + runs-on: ubuntu-latest + container: emscripten/emsdk:latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-unknown-emscripten + - name: Test (no features) + run: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node cargo test --target wasm32-unknown-emscripten --no-default-features + - name: Test (all features) + run: CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node cargo test --target wasm32-unknown-emscripten --all-features + + + lints: + name: Rustfmt & Clippy + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - name: Check formatting + run: cargo fmt --check + - name: Check clippy + run: cargo clippy -- -D warnings diff --git a/userland/upstream-src/errno-0.3.14/.gitignore b/userland/upstream-src/errno-0.3.14/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/userland/upstream-src/errno-0.3.14/CHANGELOG.md b/userland/upstream-src/errno-0.3.14/CHANGELOG.md new file mode 100644 index 0000000000..15b71fb0b0 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/CHANGELOG.md @@ -0,0 +1,137 @@ +# [Unreleased] + +# [0.3.14] - 2025-09-08 + +- Update windows-sys requirement from >=0.52, <=0.59 to >=0.52, <0.62 + [#117](https://github.com/lambda-fairy/rust-errno/pull/117) + +# [0.3.13] - 2025-06-19 + +- Update windows-sys requirement from >=0.52, <=0.59 to >=0.52, <=0.60 + [#113](https://github.com/lambda-fairy/rust-errno/pull/113) + +# [0.3.12] - 2025-05-12 + +- Issue a better error message if the target is unsupported. + [#110](https://github.com/lambda-fairy/rust-errno/pull/110) + +# [0.3.11] - 2025-04-04 + +- Add VxWorks support + [#105](https://github.com/lambda-fairy/rust-errno/pull/105) + +- Add cygwin support + [#106](https://github.com/lambda-fairy/rust-errno/pull/106) + +# [0.3.10] - 2024-11-29 + +- Update to windows-sys 0.59 + [#98](https://github.com/lambda-fairy/rust-errno/pull/98) + +- Support emscripten + [#100](https://github.com/lambda-fairy/rust-errno/pull/100) + +- Remove Bitrig support + [#99](https://github.com/lambda-fairy/rust-errno/pull/99) + +# [0.3.9] - 2024-05-07 + +- Add visionOS support + [#95](https://github.com/lambda-fairy/rust-errno/pull/95) + +# [0.3.8] - 2023-11-27 + +- Update to windows-sys 0.52. + [#91](https://github.com/lambda-fairy/rust-errno/pull/91) + +- Update minimum Rust version to 1.56 + [#91](https://github.com/lambda-fairy/rust-errno/pull/91) + +# [0.3.7] - 2023-11-15 + +- Fix `to_string()` handling for unknown error codes + [#88](https://github.com/lambda-fairy/rust-errno/pull/88) + +# [0.3.6] - 2023-11-07 + +- Add support for tvOS and watchOS + [#84](https://github.com/lambda-fairy/rust-errno/pull/84) + +- Added support for vita target + [#86](https://github.com/lambda-fairy/rust-errno/pull/86) + +# [0.3.5] - 2023-10-08 + +- Use __errno_location on DragonFly BSD + [#82](https://github.com/lambda-fairy/rust-errno/pull/82) + +# [0.3.4] - 2023-10-01 + +- Add GNU/Hurd support + [#80](https://github.com/lambda-fairy/rust-errno/pull/80) + +# [0.3.3] - 2023-08-28 + +- Disable "libc/std" in no-std configurations. + [#77](https://github.com/lambda-fairy/rust-errno/pull/77) + +- Bump errno-dragonfly to 0.1.2 + [#75](https://github.com/lambda-fairy/rust-errno/pull/75) + +- Support for the ESP-IDF framework + [#74](https://github.com/lambda-fairy/rust-errno/pull/74) + +# [0.3.2] - 2023-07-30 + +- Fix build on Hermit + [#73](https://github.com/lambda-fairy/rust-errno/pull/73) + +- Add support for QNX Neutrino + [#72](https://github.com/lambda-fairy/rust-errno/pull/72) + +# [0.3.1] - 2023-04-08 + +- Correct link name on redox + [#69](https://github.com/lambda-fairy/rust-errno/pull/69) + +- Update windows-sys requirement from 0.45 to 0.48 + [#70](https://github.com/lambda-fairy/rust-errno/pull/70) + +# [0.3.0] - 2023-02-12 + +- Add haiku support + [#42](https://github.com/lambda-fairy/rust-errno/pull/42) + +- Add AIX support + [#54](https://github.com/lambda-fairy/rust-errno/pull/54) + +- Add formatting with `#![no_std]` + [#44](https://github.com/lambda-fairy/rust-errno/pull/44) + +- Switch from `winapi` to `windows-sys` [#55](https://github.com/lambda-fairy/rust-errno/pull/55) + +- Update minimum Rust version to 1.48 + [#48](https://github.com/lambda-fairy/rust-errno/pull/48) [#55](https://github.com/lambda-fairy/rust-errno/pull/55) + +- Upgrade to Rust 2018 edition [#59](https://github.com/lambda-fairy/rust-errno/pull/59) + +- wasm32-wasi: Use `__errno_location` instead of `feature(thread_local)`. [#66](https://github.com/lambda-fairy/rust-errno/pull/66) + +# [0.2.8] - 2021-10-27 + +- Optionally support no_std + [#31](https://github.com/lambda-fairy/rust-errno/pull/31) + +[Unreleased]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.10...HEAD +[0.3.10]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.9...v0.3.10 +[0.3.9]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.8...v0.3.9 +[0.3.8]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.7...v0.3.8 +[0.3.7]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.6...v0.3.7 +[0.3.6]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.5...v0.3.6 +[0.3.5]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.4...v0.3.5 +[0.3.4]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/lambda-fairy/rust-errno/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/lambda-fairy/rust-errno/compare/v0.2.8...v0.3.0 +[0.2.8]: https://github.com/lambda-fairy/rust-errno/compare/v0.2.7...v0.2.8 diff --git a/userland/upstream-src/errno-0.3.14/Cargo.toml b/userland/upstream-src/errno-0.3.14/Cargo.toml new file mode 100644 index 0000000000..ff3dfa984d --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/Cargo.toml @@ -0,0 +1,67 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.56" +name = "errno" +version = "0.3.14" +authors = [ + "Chris Wong ", + "Dan Gohman ", +] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Cross-platform interface to the `errno` variable." +documentation = "https://docs.rs/errno" +readme = "README.md" +categories = [ + "no-std", + "os", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/lambda-fairy/rust-errno" + +[features] +default = ["std"] +std = ["libc/std"] + +[lib] +name = "errno" +path = "src/lib.rs" + +[target.'cfg(target_os="hermit")'.dependencies.libc] +version = "0.2" +default-features = false + +[target.'cfg(target_os="wasi")'.dependencies.libc] +version = "0.2" +default-features = false + +[target."cfg(unix)".dependencies.libc] +version = "0.2" +default-features = false + +[target."cfg(windows)".dependencies.windows-sys] +version = ">=0.52, <0.62" +features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", +] + +[lints.rust.unexpected_cfgs] +level = "warn" +priority = 0 +check-cfg = ['cfg(target_os, values("cygwin"))'] diff --git a/userland/upstream-src/errno-0.3.14/Cargo.toml.orig b/userland/upstream-src/errno-0.3.14/Cargo.toml.orig new file mode 100644 index 0000000000..f3f5ce031f --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/Cargo.toml.orig @@ -0,0 +1,39 @@ +[package] + +name = "errno" +version = "0.3.14" +authors = ["Chris Wong ", "Dan Gohman "] + +license = "MIT OR Apache-2.0" +edition = "2018" +documentation = "https://docs.rs/errno" +repository = "https://github.com/lambda-fairy/rust-errno" +description = "Cross-platform interface to the `errno` variable." +categories = ["no-std", "os"] +rust-version = "1.56" + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2", default-features = false } + +[target.'cfg(windows)'.dependencies.windows-sys] +version = ">=0.52, <0.62" +features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", +] + +[target.'cfg(target_os="wasi")'.dependencies] +libc = { version = "0.2", default-features = false } + +[target.'cfg(target_os="hermit")'.dependencies] +libc = { version = "0.2", default-features = false } + +[features] +default = ["std"] +std = ["libc/std"] + +# TODO: Remove this exemption when Cygwin support lands in Rust stable +# https://github.com/rust-lang/rust/pull/134999 +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(target_os, values("cygwin"))'] diff --git a/userland/upstream-src/errno-0.3.14/LICENSE-APACHE b/userland/upstream-src/errno-0.3.14/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/userland/upstream-src/errno-0.3.14/LICENSE-MIT b/userland/upstream-src/errno-0.3.14/LICENSE-MIT new file mode 100644 index 0000000000..66b6578d39 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Chris Wong + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/errno-0.3.14/README.md b/userland/upstream-src/errno-0.3.14/README.md new file mode 100644 index 0000000000..808f6d0b31 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/README.md @@ -0,0 +1,62 @@ +# errno [![CI](https://github.com/lambda-fairy/rust-errno/actions/workflows/main.yml/badge.svg)](https://github.com/lambda-fairy/rust-errno/actions/workflows/main.yml) [![Cargo](https://img.shields.io/crates/v/errno.svg)](https://crates.io/crates/errno) + +Cross-platform interface to the [`errno`][errno] variable. Works on Rust 1.56 or newer. + +Documentation is available at . + +[errno]: https://en.wikipedia.org/wiki/Errno.h + + +## Dependency + +Add to your `Cargo.toml`: + +```toml +[dependencies] +errno = "0.3" +``` + + +## Comparison with `std::io::Error` + +The standard library provides [`Error::last_os_error`][last_os_error] which fetches `errno` in the same way. + +This crate provides these extra features: + +- No heap allocations +- Optional `#![no_std]` support +- A `set_errno` function + +[last_os_error]: https://doc.rust-lang.org/std/io/struct.Error.html#method.last_os_error + + +## Examples + +```rust +extern crate errno; +use errno::{Errno, errno, set_errno}; + +// Get the current value of errno +let e = errno(); + +// Set the current value of errno +set_errno(e); + +// Extract the error code as an i32 +let code = e.0; + +// Display a human-friendly error message +println!("Error {}: {}", code, e); +``` + + +## `#![no_std]` + +Enable `#![no_std]` support by disabling the default `std` feature: + +```toml +[dependencies] +errno = { version = "0.3", default-features = false } +``` + +The `Error` impl will be unavailable. diff --git a/userland/upstream-src/errno-0.3.14/clippy.toml b/userland/upstream-src/errno-0.3.14/clippy.toml new file mode 100644 index 0000000000..62ca742340 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/clippy.toml @@ -0,0 +1 @@ +msrv = "1.56" diff --git a/userland/upstream-src/errno-0.3.14/src/hermit.rs b/userland/upstream-src/errno-0.3.14/src/hermit.rs new file mode 100644 index 0000000000..331b6b1522 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/hermit.rs @@ -0,0 +1,32 @@ +//! Implementation of `errno` functionality for RustyHermit. +//! +//! Currently, the error handling in RustyHermit isn't clearly +//! defined. At the current stage of RustyHermit, only a placeholder +//! is provided to be compatible to the classical errno interface. + +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::Errno; + +pub fn with_description(_err: Errno, callback: F) -> T +where + F: FnOnce(Result<&str, Errno>) -> T, +{ + callback(Ok("unknown error")) +} + +pub const STRERROR_NAME: &str = "strerror_r"; + +pub fn errno() -> Errno { + Errno(0) +} + +pub fn set_errno(_: Errno) {} diff --git a/userland/upstream-src/errno-0.3.14/src/lib.rs b/userland/upstream-src/errno-0.3.14/src/lib.rs new file mode 100644 index 0000000000..4b2ea8e07a --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/lib.rs @@ -0,0 +1,160 @@ +//! Cross-platform interface to the `errno` variable. +//! +//! # Examples +//! ``` +//! use errno::{Errno, errno, set_errno}; +//! +//! // Get the current value of errno +//! let e = errno(); +//! +//! // Set the current value of errno +//! set_errno(e); +//! +//! // Extract the error code as an i32 +//! let code = e.0; +//! +//! // Display a human-friendly error message +//! println!("Error {}: {}", code, e); +//! ``` + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg_attr(unix, path = "unix.rs")] +#[cfg_attr(windows, path = "windows.rs")] +#[cfg_attr(target_os = "wasi", path = "wasi.rs")] +#[cfg_attr(target_os = "hermit", path = "hermit.rs")] +mod sys; + +use core::fmt; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::io; + +/// Wraps a platform-specific error code. +/// +/// The `Display` instance maps the code to a human-readable string. It +/// calls [`strerror_r`][1] under POSIX, and [`FormatMessageW`][2] on +/// Windows. +/// +/// [1]: http://pubs.opengroup.org/onlinepubs/009695399/functions/strerror.html +/// [2]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351%28v=vs.85%29.aspx +#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)] +pub struct Errno(pub i32); + +impl fmt::Debug for Errno { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + sys::with_description(*self, |desc| { + fmt.debug_struct("Errno") + .field("code", &self.0) + .field("description", &desc.ok()) + .finish() + }) + } +} + +impl fmt::Display for Errno { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + sys::with_description(*self, |desc| match desc { + Ok(desc) => fmt.write_str(desc), + Err(fm_err) => write!( + fmt, + "OS error {} ({} returned error {})", + self.0, + sys::STRERROR_NAME, + fm_err.0 + ), + }) + } +} + +impl From for i32 { + fn from(e: Errno) -> Self { + e.0 + } +} + +#[cfg(feature = "std")] +impl Error for Errno { + // TODO: Remove when MSRV >= 1.27 + #[allow(deprecated)] + fn description(&self) -> &str { + "system error" + } +} + +#[cfg(feature = "std")] +impl From for io::Error { + fn from(errno: Errno) -> Self { + io::Error::from_raw_os_error(errno.0) + } +} + +/// Returns the platform-specific value of `errno`. +pub fn errno() -> Errno { + sys::errno() +} + +/// Sets the platform-specific value of `errno`. +pub fn set_errno(err: Errno) { + sys::set_errno(err) +} + +#[test] +fn it_works() { + let x = errno(); + set_errno(x); +} + +#[cfg(feature = "std")] +#[test] +fn it_works_with_to_string() { + let x = errno(); + let _ = x.to_string(); +} + +#[cfg(feature = "std")] +#[test] +fn check_description() { + let expect = if cfg!(windows) { + "Incorrect function." + } else if cfg!(target_os = "illumos") { + "Not owner" + } else if cfg!(target_os = "wasi") || cfg!(target_os = "emscripten") { + "Argument list too long" + } else if cfg!(target_os = "haiku") { + "Operation not allowed" + } else if cfg!(target_os = "vxworks") { + "operation not permitted" + } else { + "Operation not permitted" + }; + + let errno_code = if cfg!(target_os = "haiku") { + -2147483633 + } else if cfg!(target_os = "hurd") { + 1073741825 + } else { + 1 + }; + set_errno(Errno(errno_code)); + + assert_eq!(errno().to_string(), expect); + assert_eq!( + format!("{:?}", errno()), + format!( + "Errno {{ code: {}, description: Some({:?}) }}", + errno_code, expect + ) + ); +} + +#[cfg(feature = "std")] +#[test] +fn check_error_into_errno() { + const ERROR_CODE: i32 = 1; + + let error = io::Error::from_raw_os_error(ERROR_CODE); + let new_error: io::Error = Errno(ERROR_CODE).into(); + assert_eq!(error.kind(), new_error.kind()); +} diff --git a/userland/upstream-src/errno-0.3.14/src/sys.rs b/userland/upstream-src/errno-0.3.14/src/sys.rs new file mode 100644 index 0000000000..e40a0161d7 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/sys.rs @@ -0,0 +1,40 @@ +//! A default sys.rs for unrecognized targets. +//! +//! If lib.rs doesn't recognize the target, it defaults to using this file. On +//! NONOS it provides a real thread-local errno; on every other unrecognized +//! target it still issues the explanatory compile error. + +// If there is no OS, there's no `errno` or equivalent defined. NONOS is such a +// target but supplies its own compatibility errno below, so it is excluded here. +#[cfg(all(any(target_os = "unknown", target_os = "none"), not(target_vendor = "nonos")))] +compile_error!("The target OS is \"unknown\" or \"none\", so it's unsupported by the errno crate."); + +// If there is an OS, support may be added. +#[cfg(all(not(any(target_os = "unknown", target_os = "none")), not(target_vendor = "nonos")))] +compile_error!("The target OS is not yet supported in the errno crate."); + +use crate::Errno; + +// NONOS capsules report syscall failure through Result values, so there is no +// kernel errno location. This keeps one process-wide slot for the crate's API +// using only core (the crate is no_std): set_errno stores, errno reads it back, +// and descriptions fall through to the raw code since there is no strerror table. +#[cfg(target_vendor = "nonos")] +static NONOS_ERRNO: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0); + +pub fn with_description(err: Errno, callback: F) -> T +where + F: FnOnce(Result<&str, Errno>) -> T, +{ + callback(Err(err)) +} + +pub const STRERROR_NAME: &str = ""; + +pub fn errno() -> Errno { + Errno(NONOS_ERRNO.load(core::sync::atomic::Ordering::Relaxed)) +} + +pub fn set_errno(err: Errno) { + NONOS_ERRNO.store(err.0, core::sync::atomic::Ordering::Relaxed); +} diff --git a/userland/upstream-src/errno-0.3.14/src/unix.rs b/userland/upstream-src/errno-0.3.14/src/unix.rs new file mode 100644 index 0000000000..5536eef258 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/unix.rs @@ -0,0 +1,104 @@ +//! Implementation of `errno` functionality for Unix systems. +//! +//! Adapted from `src/libstd/sys/unix/os.rs` in the Rust distribution. + +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::str; +use libc::{self, c_int, size_t, strerror_r, strlen}; + +use crate::Errno; + +fn from_utf8_lossy(input: &[u8]) -> &str { + match str::from_utf8(input) { + Ok(valid) => valid, + Err(error) => unsafe { str::from_utf8_unchecked(&input[..error.valid_up_to()]) }, + } +} + +pub fn with_description(err: Errno, callback: F) -> T +where + F: FnOnce(Result<&str, Errno>) -> T, +{ + let mut buf = [0u8; 1024]; + let c_str = unsafe { + let rc = strerror_r(err.0, buf.as_mut_ptr() as *mut _, buf.len() as size_t); + if rc != 0 { + // Handle negative return codes for compatibility with glibc < 2.13 + let fm_err = match rc < 0 { + true => errno(), + false => Errno(rc), + }; + if fm_err != Errno(libc::ERANGE) { + return callback(Err(fm_err)); + } + } + let c_str_len = strlen(buf.as_ptr() as *const _); + &buf[..c_str_len] + }; + callback(Ok(from_utf8_lossy(c_str))) +} + +pub const STRERROR_NAME: &str = "strerror_r"; + +pub fn errno() -> Errno { + unsafe { Errno(*errno_location()) } +} + +pub fn set_errno(Errno(errno): Errno) { + unsafe { + *errno_location() = errno; + } +} + +extern "C" { + #[cfg_attr( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "freebsd" + ), + link_name = "__error" + )] + #[cfg_attr( + any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "android", + target_os = "espidf", + target_os = "vxworks", + target_os = "cygwin", + target_env = "newlib" + ), + link_name = "__errno" + )] + #[cfg_attr( + any(target_os = "solaris", target_os = "illumos"), + link_name = "___errno" + )] + #[cfg_attr(target_os = "haiku", link_name = "_errnop")] + #[cfg_attr( + any( + target_os = "linux", + target_os = "hurd", + target_os = "redox", + target_os = "dragonfly", + target_os = "emscripten", + ), + link_name = "__errno_location" + )] + #[cfg_attr(target_os = "aix", link_name = "_Errno")] + #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")] + fn errno_location() -> *mut c_int; +} diff --git a/userland/upstream-src/errno-0.3.14/src/wasi.rs b/userland/upstream-src/errno-0.3.14/src/wasi.rs new file mode 100644 index 0000000000..404d7d5192 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/wasi.rs @@ -0,0 +1,60 @@ +//! Implementation of `errno` functionality for WASI. +//! +//! Adapted from `unix.rs`. + +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::str; +use libc::{self, c_int, size_t, strerror_r, strlen}; + +use crate::Errno; + +fn from_utf8_lossy(input: &[u8]) -> &str { + match str::from_utf8(input) { + Ok(valid) => valid, + Err(error) => unsafe { str::from_utf8_unchecked(&input[..error.valid_up_to()]) }, + } +} + +pub fn with_description(err: Errno, callback: F) -> T +where + F: FnOnce(Result<&str, Errno>) -> T, +{ + let mut buf = [0u8; 1024]; + let c_str = unsafe { + let rc = strerror_r(err.0, buf.as_mut_ptr() as *mut _, buf.len() as size_t); + if rc != 0 { + let fm_err = Errno(rc); + if fm_err != Errno(libc::ERANGE) { + return callback(Err(fm_err)); + } + } + let c_str_len = strlen(buf.as_ptr() as *const _); + &buf[..c_str_len] + }; + callback(Ok(from_utf8_lossy(c_str))) +} + +pub const STRERROR_NAME: &str = "strerror_r"; + +pub fn errno() -> Errno { + unsafe { Errno(*__errno_location()) } +} + +pub fn set_errno(Errno(new_errno): Errno) { + unsafe { + *__errno_location() = new_errno; + } +} + +extern "C" { + fn __errno_location() -> *mut c_int; +} diff --git a/userland/upstream-src/errno-0.3.14/src/windows.rs b/userland/upstream-src/errno-0.3.14/src/windows.rs new file mode 100644 index 0000000000..9c7c0e4001 --- /dev/null +++ b/userland/upstream-src/errno-0.3.14/src/windows.rs @@ -0,0 +1,81 @@ +//! Implementation of `errno` functionality for Windows. +//! +//! Adapted from `src/libstd/sys/windows/os.rs` in the Rust distribution. + +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::char::{self, REPLACEMENT_CHARACTER}; +use core::ptr; +use core::str; +use windows_sys::Win32::Foundation::{GetLastError, SetLastError, WIN32_ERROR}; +use windows_sys::Win32::System::Diagnostics::Debug::{ + FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, +}; + +use crate::Errno; + +fn from_utf16_lossy<'a>(input: &[u16], output: &'a mut [u8]) -> &'a str { + let mut output_len = 0; + for c in char::decode_utf16(input.iter().copied().take_while(|&x| x != 0)) + .map(|x| x.unwrap_or(REPLACEMENT_CHARACTER)) + { + let c_len = c.len_utf8(); + if c_len > output.len() - output_len { + break; + } + c.encode_utf8(&mut output[output_len..]); + output_len += c_len; + } + unsafe { str::from_utf8_unchecked(&output[..output_len]) } +} + +pub fn with_description(err: Errno, callback: F) -> T +where + F: FnOnce(Result<&str, Errno>) -> T, +{ + // This value is calculated from the macro + // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) + let lang_id = 0x0800_u32; + + let mut buf = [0u16; 2048]; + + unsafe { + let res = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + ptr::null_mut(), + err.0 as u32, + lang_id, + buf.as_mut_ptr(), + buf.len() as u32, + ptr::null_mut(), + ); + if res == 0 { + // Sometimes FormatMessageW can fail e.g. system doesn't like lang_id + let fm_err = errno(); + return callback(Err(fm_err)); + } + + let mut msg = [0u8; 2048]; + let msg = from_utf16_lossy(&buf[..res as usize], &mut msg[..]); + // Trim trailing CRLF inserted by FormatMessageW + callback(Ok(msg.trim_end())) + } +} + +pub const STRERROR_NAME: &str = "FormatMessageW"; + +pub fn errno() -> Errno { + unsafe { Errno(GetLastError() as i32) } +} + +pub fn set_errno(Errno(errno): Errno) { + unsafe { SetLastError(errno as WIN32_ERROR) } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/.cargo-ok b/userland/upstream-src/fd-lock-4.0.4/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/fd-lock-4.0.4/.cargo_vcs_info.json b/userland/upstream-src/fd-lock-4.0.4/.cargo_vcs_info.json new file mode 100644 index 0000000000..66dfefe829 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "af18798c1790003815a8180fb1929c0a7da1f512" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/fd-lock-4.0.4/.github/CODE_OF_CONDUCT.md b/userland/upstream-src/fd-lock-4.0.4/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..d02f05007f --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or +advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic +address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a +professional setting + + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at yoshuawuyts@gmail.com, or through +IRC. All complaints will be reviewed and investigated and will result in a +response that is deemed necessary and appropriate to the circumstances. The +project team is obligated to maintain confidentiality with regard to the +reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/userland/upstream-src/fd-lock-4.0.4/.github/CONTRIBUTING.md b/userland/upstream-src/fd-lock-4.0.4/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..a11172a7f1 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.github/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing +Contributions include code, documentation, answering user questions, running the +project's infrastructure, and advocating for all types of users. + +The project welcomes all contributions from anyone willing to work in good faith +with other contributors and the community. No contribution is too small and all +contributions are valued. + +This guide explains the process for contributing to the project's GitHub +Repository. + +- [Code of Conduct](#code-of-conduct) +- [Bad Actors](#bad-actors) + +## Code of Conduct +The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* +contributors are expected to follow. This code describes the *minimum* behavior +expectations for all contributors. + +As a contributor, how you choose to act and interact towards your +fellow contributors, as well as to the community, will reflect back not only +on yourself but on the project as a whole. The Code of Conduct is designed and +intended, above all else, to help establish a culture within the project that +allows anyone and everyone who wants to contribute to feel safe doing so. + +Should any individual act in any way that is considered in violation of the +[Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is +possible, however, for any individual to *act* in such a manner that is not in +violation of the strict letter of the Code of Conduct guidelines while still +going completely against the spirit of what that Code is intended to accomplish. + +Open, diverse, and inclusive communities live and die on the basis of trust. +Contributors can disagree with one another so long as they trust that those +disagreements are in good faith and everyone is working towards a common +goal. + +## Bad Actors +All contributors to tacitly agree to abide by both the letter and +spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or +unwillingness, to do so will result in contributions being respectfully +declined. + +A *bad actor* is someone who repeatedly violates the *spirit* of the Code of +Conduct through consistent failure to self-regulate the way in which they +interact with other contributors in the project. In doing so, bad actors +alienate other contributors, discourage collaboration, and generally reflect +poorly on the project as a whole. + +Being a bad actor may be intentional or unintentional. Typically, unintentional +bad behavior can be easily corrected by being quick to apologize and correct +course *even if you are not entirely convinced you need to*. Giving other +contributors the benefit of the doubt and having a sincere willingness to admit +that you *might* be wrong is critical for any successful open collaboration. + +Don't be a bad actor. diff --git a/userland/upstream-src/fd-lock-4.0.4/.github/workflows/ci.yaml b/userland/upstream-src/fd-lock-4.0.4/.github/workflows/ci.yaml new file mode 100644 index 0000000000..7a7a152cec --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.github/workflows/ci.yaml @@ -0,0 +1,92 @@ +name: CI + +on: + pull_request: + push: + branches: + - staging + - trying + +env: + RUSTFLAGS: -Dwarnings + +jobs: + build_and_test: + name: Build and test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + rust: [stable] + + steps: + - uses: actions/checkout@v3 + + - name: Install ${{ matrix.rust }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - name: check + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --bins --examples + + - name: check unstable + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --benches --bins --examples --tests + + - name: tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --all + + - name: update lockfile for windows-sys v0.52.0 + uses: actions-rs/cargo@v1 + if: runner.os == 'Windows' + with: + command: update + args: -p windows-sys --precise 0.52.0 + + - name: check windows-sys v0.52.0 + uses: actions-rs/cargo@v1 + if: runner.os == 'Windows' + with: + command: test + args: --all + + - name: update lockfile for windows-sys v0.59.0 + uses: actions-rs/cargo@v1 + if: runner.os == 'Windows' + with: + command: update + args: -p windows-sys --precise 0.59.0 + + - name: check windows-sys v0.59.0 + uses: actions-rs/cargo@v1 + if: runner.os == 'Windows' + with: + command: test + args: --all + + check_fmt_and_docs: + name: Checking fmt and docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: rustfmt, clippy + override: true + + - name: fmt + run: cargo fmt --all -- --check + + - name: Docs + run: cargo doc diff --git a/userland/upstream-src/fd-lock-4.0.4/.gitignore b/userland/upstream-src/fd-lock-4.0.4/.gitignore new file mode 100644 index 0000000000..ac0d3c74eb --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/.gitignore @@ -0,0 +1,7 @@ +coverage/ +target/ +tmp/ +dist/ +npm-debug.log* +Cargo.lock +.DS_Store diff --git a/userland/upstream-src/fd-lock-4.0.4/Cargo.toml b/userland/upstream-src/fd-lock-4.0.4/Cargo.toml new file mode 100644 index 0000000000..c765b60cf8 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/Cargo.toml @@ -0,0 +1,67 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "fd-lock" +version = "4.0.4" +authors = ["Yoshua Wuyts "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Advisory cross-platform lock on a file using a file descriptor to it." +documentation = "https://docs.rs/fd-lock" +readme = "README.md" +keywords = [ + "file", + "fd", + "lock", + "windows", + "unix", +] +categories = [ + "filesystem", + "os", + "os::macos-apis", + "os::unix-apis", + "os::windows-apis", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/yoshuawuyts/fd-lock" + +[lib] +name = "fd_lock" +path = "src/lib.rs" + +[[test]] +name = "test" +path = "tests/test.rs" + +[dependencies.cfg-if] +version = "1.0.0" + +[dev-dependencies.tempfile] +version = "3.0.8" + +[target."cfg(unix)".dependencies.rustix] +version = "1.0.0" +features = ["fs"] + +[target."cfg(windows)".dependencies.windows-sys] +version = ">=0.52.0, <0.60.0" +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_IO", +] diff --git a/userland/upstream-src/fd-lock-4.0.4/Cargo.toml.orig b/userland/upstream-src/fd-lock-4.0.4/Cargo.toml.orig new file mode 100644 index 0000000000..ada784d259 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/Cargo.toml.orig @@ -0,0 +1,29 @@ +[package] +name = "fd-lock" +version = "4.0.4" +license = "MIT OR Apache-2.0" +repository = "https://github.com/yoshuawuyts/fd-lock" +documentation = "https://docs.rs/fd-lock" +description = "Advisory cross-platform lock on a file using a file descriptor to it." +keywords = ["file", "fd", "lock", "windows", "unix"] +categories = ["filesystem", "os", "os::macos-apis", "os::unix-apis", "os::windows-apis"] +authors = ["Yoshua Wuyts "] +readme = "README.md" +edition = "2021" + +[dependencies] +cfg-if = "1.0.0" + +[target.'cfg(windows)'.dependencies.windows-sys] +version = ">=0.52.0, <0.60.0" +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_IO", +] + +[target.'cfg(unix)'.dependencies] +rustix = { version = "1.0.0", features = ["fs"] } + +[dev-dependencies] +tempfile = "3.0.8" diff --git a/userland/upstream-src/fd-lock-4.0.4/LICENSE-APACHE b/userland/upstream-src/fd-lock-4.0.4/LICENSE-APACHE new file mode 100644 index 0000000000..1fe151fe55 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/LICENSE-APACHE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019 Yoshua Wuyts + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/userland/upstream-src/fd-lock-4.0.4/LICENSE-MIT b/userland/upstream-src/fd-lock-4.0.4/LICENSE-MIT new file mode 100644 index 0000000000..645b57f697 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Yoshua Wuyts + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/userland/upstream-src/fd-lock-4.0.4/README.md b/userland/upstream-src/fd-lock-4.0.4/README.md new file mode 100644 index 0000000000..6720153afb --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/README.md @@ -0,0 +1,72 @@ +# fd-lock +[![crates.io version][1]][2] +[![downloads][5]][6] [![docs.rs docs][7]][8] + +Advisory cross-platform file locks using file descriptors. Adapted from +[mafintosh/fd-lock]. + +Note that advisory lock compliance is opt-in, and can freely be ignored by other +parties. This means this crate __should never be used for security purposes__, +but solely to coordinate file access. + +[mafintosh/fd-lock]: https://github.com/mafintosh/fd-lock + +- [Documentation][8] +- [Crates.io][2] +- [Releases][releases] + +## Examples +__Basic usage__ +```rust +use std::io::prelude::*; +use std::fs::File; +use fd_lock::RwLock; + +// Lock a file and write to it. +let mut f = RwLock::new(File::open("foo.txt")?); +write!(f.write()?, "chashu cat")?; + +// A lock can also be held across multiple operations. +let mut f = f.write()?; +write!(f, "nori cat")?; +write!(f, "bird!")?; +``` + +## Installation +```sh +$ cargo add fd-lock +``` + +## Safety +This crate uses `unsafe` on Windows to interface with `windows-sys`. All +invariants have been carefully checked, and are manually enforced. + +## Contributing +Want to join us? Check out our ["Contributing" guide][contributing] and take a +look at some of these issues: + +- [Issues labeled "good first issue"][good-first-issue] +- [Issues labeled "help wanted"][help-wanted] + +## References +- [LockFile function - WDC](https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfile) +- [flock(2) - Linux Man Page](https://man7.org/linux/man-pages/man2/flock.2.html) +- [`rustix::fs::flock`](https://docs.rs/rustix/*/rustix/fs/fn.flock.html) +- [`windows_sys::Win32::Storage::FileSystem::LockFile`](https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/FileSystem/fn.LockFile.html) + +## License +[MIT](./LICENSE-MIT) OR [Apache-2.0](./LICENSE-APACHE) + +[1]: https://img.shields.io/crates/v/fd-lock.svg?style=flat-square +[2]: https://crates.io/crates/fd-lock +[3]: https://img.shields.io/travis/yoshuawuyts/fd-lock/master.svg?style=flat-square +[4]: https://travis-ci.org/yoshuawuyts/fd-lock +[5]: https://img.shields.io/crates/d/fd-lock.svg?style=flat-square +[6]: https://crates.io/crates/fd-lock +[7]: https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square +[8]: https://docs.rs/fd-lock + +[releases]: https://github.com/yoshuawuyts/fd-lock/releases +[contributing]: https://github.com/yoshuawuyts/fd-lock/blob/master.github/CONTRIBUTING.md +[good-first-issue]: https://github.com/yoshuawuyts/fd-lock/labels/good%20first%20issue +[help-wanted]: https://github.com/yoshuawuyts/fd-lock/labels/help%20wanted diff --git a/userland/upstream-src/fd-lock-4.0.4/src/lib.rs b/userland/upstream-src/fd-lock-4.0.4/src/lib.rs new file mode 100644 index 0000000000..9d3d6192bc --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/lib.rs @@ -0,0 +1,43 @@ +//! Advisory reader-writer locks for files. +//! +//! # Notes on Advisory Locks +//! +//! "advisory locks" are locks which programs must opt-in to adhere to. This +//! means that they can be used to coordinate file access, but not prevent +//! access. Use this to coordinate file access between multiple instances of the +//! same program. But do not use this to prevent actors from accessing or +//! modifying files. +//! +//! # Example +//! +//! ```no_run +//! # use std::io; +//! use std::io::prelude::*; +//! use std::fs::File; +//! use fd_lock::RwLock; +//! +//! # fn main() -> io::Result<()> { +//! // Lock a file and write to it. +//! let mut f = RwLock::new(File::open("foo.txt")?); +//! write!(f.write()?, "chashu cat")?; +//! +//! // A lock can also be held across multiple operations. +//! let mut f = f.write()?; +//! write!(f, "nori cat")?; +//! write!(f, "bird!")?; +//! # Ok(()) } +//! ``` + +#![forbid(future_incompatible)] +#![deny(missing_debug_implementations, nonstandard_style)] +#![cfg_attr(doc, warn(missing_docs, rustdoc::missing_doc_code_examples))] + +mod read_guard; +mod rw_lock; +mod write_guard; + +pub(crate) mod sys; + +pub use read_guard::RwLockReadGuard; +pub use rw_lock::RwLock; +pub use write_guard::RwLockWriteGuard; diff --git a/userland/upstream-src/fd-lock-4.0.4/src/read_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/read_guard.rs new file mode 100644 index 0000000000..bf93dca787 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/read_guard.rs @@ -0,0 +1,39 @@ +use std::ops; + +use crate::sys; + +/// RAII structure used to release the shared read access of a lock when +/// dropped. +/// +/// This structure is created by the [`read`] and [`try_read`] methods on +/// [`RwLock`]. +/// +/// [`read`]: crate::RwLock::read +/// [`try_read`]: crate::RwLock::try_read +/// [`RwLock`]: crate::RwLock +#[must_use = "if unused the RwLock will immediately unlock"] +#[derive(Debug)] +pub struct RwLockReadGuard<'lock, T: sys::AsOpenFile> { + guard: sys::RwLockReadGuard<'lock, T>, +} + +impl<'lock, T: sys::AsOpenFile> RwLockReadGuard<'lock, T> { + pub(crate) fn new(guard: sys::RwLockReadGuard<'lock, T>) -> Self { + Self { guard } + } +} + +impl ops::Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +/// Release the lock. +impl Drop for RwLockReadGuard<'_, T> { + #[inline] + fn drop(&mut self) {} +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/rw_lock.rs b/userland/upstream-src/fd-lock-4.0.4/src/rw_lock.rs new file mode 100644 index 0000000000..872dddd326 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/rw_lock.rs @@ -0,0 +1,126 @@ +use crate::read_guard::RwLockReadGuard; +use crate::sys; +use crate::write_guard::RwLockWriteGuard; +use std::io; + +/// Advisory reader-writer lock for files. +/// +/// This type of lock allows a number of readers or at most one writer at any point +/// in time. The write portion of this lock typically allows modification of the +/// underlying data (exclusive access) and the read portion of this lock typically +/// allows for read-only access (shared access). +#[derive(Debug)] +pub struct RwLock { + lock: sys::RwLock, +} + +impl RwLock { + /// Create a new instance. + /// + /// # Examples + /// + /// ```no_run + /// use fd_lock::RwLock; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut f = RwLock::new(File::open("foo.txt")?); + /// Ok(()) + /// } + /// ``` + #[inline] + pub fn new(inner: T) -> Self { + Self { + lock: sys::RwLock::new(inner), + } + } + + /// Locks this lock with shared read access, blocking the current thread + /// until it can be acquired. + /// + /// The calling thread will be blocked until there are no more writers which + /// hold the lock. There may be other readers currently inside the lock when + /// this method returns. This method does not provide any guarantees with + /// respect to the ordering of whether contentious readers or writers will + /// acquire the lock first. + /// + /// Returns an RAII guard which will release this thread's shared access + /// once it is dropped. + /// + /// # Errors + /// + /// On Unix this may return an `ErrorKind::Interrupted` if the operation was + /// interrupted by a signal handler. + #[inline] + pub fn read(&self) -> io::Result> { + let guard = self.lock.read()?; + Ok(RwLockReadGuard::new(guard)) + } + + /// Attempts to acquire this lock with shared read access. + /// + /// If the access could not be granted at this time, then `Err` is returned. + /// Otherwise, an RAII guard is returned which will release the shared access + /// when it is dropped. + /// + /// This function does not block. + /// + /// This function does not provide any guarantees with respect to the ordering + /// of whether contentious readers or writers will acquire the lock first. + /// + /// # Errors + /// + /// If the lock is already held and `ErrorKind::WouldBlock` error is returned. + /// On Unix this may return an `ErrorKind::Interrupted` if the operation was + /// interrupted by a signal handler. + #[inline] + pub fn try_read(&self) -> io::Result> { + let guard = self.lock.try_read()?; + Ok(RwLockReadGuard::new(guard)) + } + + /// Locks this lock with exclusive write access, blocking the current thread + /// until it can be acquired. + /// + /// This function will not return while other writers or other readers + /// currently have access to the lock. + /// + /// Returns an RAII guard which will drop the write access of this rwlock + /// when dropped. + /// + /// # Errors + /// + /// On Unix this may return an `ErrorKind::Interrupted` if the operation was + /// interrupted by a signal handler. + #[inline] + pub fn write(&mut self) -> io::Result> { + let guard = self.lock.write()?; + Ok(RwLockWriteGuard::new(guard)) + } + + /// Attempts to lock this lock with exclusive write access. + /// + /// If the lock could not be acquired at this time, then `Err` is returned. + /// Otherwise, an RAII guard is returned which will release the lock when + /// it is dropped. + /// + /// # Errors + /// + /// If the lock is already held and `ErrorKind::WouldBlock` error is returned. + /// On Unix this may return an `ErrorKind::Interrupted` if the operation was + /// interrupted by a signal handler. + #[inline] + pub fn try_write(&mut self) -> io::Result> { + let guard = self.lock.try_write()?; + Ok(RwLockWriteGuard::new(guard)) + } + + /// Consumes this `RwLock`, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T + where + T: Sized, + { + self.lock.into_inner() + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/mod.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/mod.rs new file mode 100644 index 0000000000..5a1591f9d5 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/mod.rs @@ -0,0 +1,21 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(unix)] { + mod unix; + pub use unix::*; + pub(crate) use rustix::fd::AsFd as AsOpenFile; + } else if #[cfg(windows)] { + mod windows; + pub use windows::*; + #[doc(no_inline)] + pub(crate) use std::os::windows::io::AsHandle as AsOpenFile; + } else if #[cfg(target_vendor = "nonos")] { + mod nonos; + pub use nonos::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + pub(crate) use nonos::AsOpenFile; + } else { + mod unsupported; + pub use unsupported; + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/nonos.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/nonos.rs new file mode 100644 index 0000000000..df29986078 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/nonos.rs @@ -0,0 +1,88 @@ +// NONOS advisory-lock backend. +// +// fd-lock's advisory locks exist to coordinate access between separate +// instances of a program. A NONOS capsule is single-instance and RAM-resident: +// it owns its file descriptors outright, so there is never a second holder to +// coordinate with. Every lock is therefore trivially acquired and the guards +// simply borrow the wrapped handle. No syscalls, and no panics. +use std::io; +use std::ops; + +// Any handle can back a lock here; the capsule owns whatever it wraps. +pub(crate) trait AsOpenFile {} +impl AsOpenFile for T {} + +#[derive(Debug)] +pub struct RwLock { + inner: T, +} + +impl RwLock { + #[inline] + pub fn new(inner: T) -> Self { + Self { inner } + } + + #[inline] + pub fn read(&self) -> io::Result> { + Ok(RwLockReadGuard { lock: self }) + } + + #[inline] + pub fn try_read(&self) -> io::Result> { + Ok(RwLockReadGuard { lock: self }) + } + + #[inline] + pub fn write(&mut self) -> io::Result> { + Ok(RwLockWriteGuard { lock: self }) + } + + #[inline] + pub fn try_write(&mut self) -> io::Result> { + Ok(RwLockWriteGuard { lock: self }) + } + + #[inline] + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner + } +} + +#[derive(Debug)] +pub struct RwLockReadGuard<'lock, T> { + lock: &'lock RwLock, +} + +impl ops::Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.lock.inner + } +} + +#[derive(Debug)] +pub struct RwLockWriteGuard<'lock, T> { + lock: &'lock mut RwLock, +} + +impl ops::Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.lock.inner + } +} + +impl ops::DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + &mut self.lock.inner + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/mod.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/mod.rs new file mode 100644 index 0000000000..12544dd3cd --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/mod.rs @@ -0,0 +1,20 @@ +mod read_guard; +mod rw_lock; +mod write_guard; + +pub use read_guard::RwLockReadGuard; +pub use rw_lock::RwLock; +pub use write_guard::RwLockWriteGuard; + +use rustix::{fd::AsFd, fs}; + +pub(crate) fn compatible_unix_lock( + fd: Fd, + operation: fs::FlockOperation, +) -> rustix::io::Result<()> { + #[cfg(not(target_os = "solaris"))] + return fs::flock(fd, operation); + + #[cfg(target_os = "solaris")] + return fs::fcntl_lock(fd, operation); +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/read_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/read_guard.rs new file mode 100644 index 0000000000..2e58366f18 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/read_guard.rs @@ -0,0 +1,32 @@ +use rustix::fd::AsFd; +use rustix::fs::FlockOperation; +use std::ops; + +use super::{compatible_unix_lock, RwLock}; + +#[derive(Debug)] +pub struct RwLockReadGuard<'lock, T: AsFd> { + lock: &'lock RwLock, +} + +impl<'lock, T: AsFd> RwLockReadGuard<'lock, T> { + pub(crate) fn new(lock: &'lock RwLock) -> Self { + Self { lock } + } +} + +impl ops::Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.lock.inner + } +} + +impl Drop for RwLockReadGuard<'_, T> { + #[inline] + fn drop(&mut self) { + let _ = compatible_unix_lock(self.lock.inner.as_fd(), FlockOperation::Unlock).ok(); + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/rw_lock.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/rw_lock.rs new file mode 100644 index 0000000000..e10b55c556 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/rw_lock.rs @@ -0,0 +1,58 @@ +use rustix::fd::AsFd; +use rustix::fs::FlockOperation; +use std::io::{self, Error, ErrorKind}; + +use super::{compatible_unix_lock, RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +pub struct RwLock { + pub(crate) inner: T, +} + +impl RwLock { + #[inline] + pub fn new(inner: T) -> Self { + RwLock { inner } + } + + #[inline] + pub fn write(&mut self) -> io::Result> { + compatible_unix_lock(self.inner.as_fd(), FlockOperation::LockExclusive)?; + Ok(RwLockWriteGuard::new(self)) + } + + #[inline] + pub fn try_write(&mut self) -> Result, Error> { + compatible_unix_lock(self.inner.as_fd(), FlockOperation::NonBlockingLockExclusive) + .map_err(|err| match err.kind() { + ErrorKind::AlreadyExists => ErrorKind::WouldBlock.into(), + _ => Error::from(err), + })?; + Ok(RwLockWriteGuard::new(self)) + } + + #[inline] + pub fn read(&self) -> io::Result> { + compatible_unix_lock(self.inner.as_fd(), FlockOperation::LockShared)?; + Ok(RwLockReadGuard::new(self)) + } + + #[inline] + pub fn try_read(&self) -> Result, Error> { + compatible_unix_lock(self.inner.as_fd(), FlockOperation::NonBlockingLockShared).map_err( + |err| match err.kind() { + ErrorKind::AlreadyExists => ErrorKind::WouldBlock.into(), + _ => Error::from(err), + }, + )?; + Ok(RwLockReadGuard::new(self)) + } + + #[inline] + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/write_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/write_guard.rs new file mode 100644 index 0000000000..7e61034eff --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unix/write_guard.rs @@ -0,0 +1,39 @@ +use rustix::fd::AsFd; +use rustix::fs::FlockOperation; +use std::ops; + +use super::{compatible_unix_lock, RwLock}; + +#[derive(Debug)] +pub struct RwLockWriteGuard<'lock, T: AsFd> { + lock: &'lock mut RwLock, +} + +impl<'lock, T: AsFd> RwLockWriteGuard<'lock, T> { + pub(crate) fn new(lock: &'lock mut RwLock) -> Self { + Self { lock } + } +} + +impl ops::Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.lock.inner + } +} + +impl ops::DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.lock.inner + } +} + +impl Drop for RwLockWriteGuard<'_, T> { + #[inline] + fn drop(&mut self) { + let _ = compatible_unix_lock(self.lock.inner.as_fd(), FlockOperation::Unlock).ok(); + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/mod.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/mod.rs new file mode 100644 index 0000000000..9d7bd2cbd2 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/mod.rs @@ -0,0 +1,9 @@ +mod read_guard; +mod rw_lock; +mod write_guard; + +pub(crate) mod utils; + +pub use read_guard::RwLockReadGuard; +pub use rw_lock::RwLock; +pub use write_guard::RwLockWriteGuard; diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/read_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/read_guard.rs new file mode 100644 index 0000000000..7d6b4c7839 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/read_guard.rs @@ -0,0 +1,31 @@ +use std::ops; +use std::os::unix::io::AsRawFd; + +use super::RwLock; + +#[derive(Debug)] +pub struct RwLockReadGuard<'lock, T: AsRawFd> { + lock: &'lock RwLock, +} + +impl<'lock, T: AsRawFd> RwLockReadGuard<'lock, T> { + pub(crate) fn new(lock: &'lock RwLock) -> Self { + panic!("target unsupported") + } +} + +impl ops::Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + panic!("target unsupported") + } +} + +impl Drop for RwLockReadGuard<'_, T> { + #[inline] + fn drop(&mut self) { + panic!("target unsupported") + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/rw_lock.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/rw_lock.rs new file mode 100644 index 0000000000..fb11da84b8 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/rw_lock.rs @@ -0,0 +1,44 @@ +use std::io::{self, Error, ErrorKind}; +use std::os::unix::io::AsRawFd; + +use super::{RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +pub struct RwLock { + pub(crate) inner: T, +} + +impl RwLock { + #[inline] + pub fn new(inner: T) -> Self { + panic!("target unsupported") + } + + #[inline] + pub fn write(&mut self) -> io::Result> { + panic!("target unsupported") + } + + #[inline] + pub fn try_write(&mut self) -> Result, Error> { + panic!("target unsupported") + } + + #[inline] + pub fn read(&self) -> io::Result> { + panic!("target unsupported") + } + + #[inline] + pub fn try_read(&self) -> Result, Error> { + panic!("target unsupported") + } + + #[inline] + pub fn into_inner(self) -> T + where + T: Sized, + { + panic!("target unsupported") + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/utils.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/utils.rs new file mode 100644 index 0000000000..191dd0c5c5 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/utils.rs @@ -0,0 +1,9 @@ +use std::io; +use std::os::raw::c_int; + +pub(crate) fn syscall(int: c_int) -> io::Result<()> { + match int { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()), + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/write_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/write_guard.rs new file mode 100644 index 0000000000..19727c3070 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/unsupported/write_guard.rs @@ -0,0 +1,38 @@ +use std::ops; +use std::os::unix::io::AsRawFd; + +use super::RwLock; + +#[derive(Debug)] +pub struct RwLockWriteGuard<'lock, T: AsRawFd> { + lock: &'lock mut RwLock, +} + +impl<'lock, T: AsRawFd> RwLockWriteGuard<'lock, T> { + pub(crate) fn new(lock: &'lock mut RwLock) -> Self { + panic!("target unsupported") + } +} + +impl ops::Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + panic!("target unsupported") + } +} + +impl ops::DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + panic!("target unsupported") + } +} + +impl Drop for RwLockWriteGuard<'_, T> { + #[inline] + fn drop(&mut self) { + panic!("target unsupported") + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/mod.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/mod.rs new file mode 100644 index 0000000000..dafbd31adb --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/mod.rs @@ -0,0 +1,8 @@ +mod read_guard; +mod rw_lock; +mod utils; +mod write_guard; + +pub use read_guard::RwLockReadGuard; +pub use rw_lock::RwLock; +pub use write_guard::RwLockWriteGuard; diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/read_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/read_guard.rs new file mode 100644 index 0000000000..4a584ce263 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/read_guard.rs @@ -0,0 +1,31 @@ +use std::os::windows::io::AsHandle; +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::Storage::FileSystem::UnlockFile; + +use std::ops; +use std::os::windows::prelude::*; + +use super::utils::syscall; +use super::RwLock; + +#[derive(Debug)] +pub struct RwLockReadGuard<'lock, T: AsHandle> { + pub(crate) lock: &'lock RwLock, +} + +impl ops::Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.lock.inner + } +} + +impl Drop for RwLockReadGuard<'_, T> { + #[inline] + fn drop(&mut self) { + let handle = self.lock.inner.as_handle().as_raw_handle() as HANDLE; + let _ = syscall(unsafe { UnlockFile(handle, 0, 0, 1, 0) }); + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/rw_lock.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/rw_lock.rs new file mode 100644 index 0000000000..4af90e4cdd --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/rw_lock.rs @@ -0,0 +1,81 @@ +use std::io::{self, Error, ErrorKind}; +use std::os::windows::io::{AsHandle, AsRawHandle}; + +use windows_sys::Win32::Foundation::ERROR_LOCK_VIOLATION; +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::Storage::FileSystem::{ + LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, +}; + +use super::utils::{syscall, Overlapped}; +use super::{RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +pub struct RwLock { + pub(crate) inner: T, +} + +impl RwLock { + #[inline] + pub fn new(inner: T) -> Self { + RwLock { inner } + } + + #[inline] + pub fn read(&self) -> io::Result> { + // See: https://stackoverflow.com/a/9186532, https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex + let handle = self.inner.as_handle().as_raw_handle() as HANDLE; + let overlapped = Overlapped::zero(); + let flags = 0; + syscall(unsafe { LockFileEx(handle, flags, 0, 1, 0, overlapped.raw()) })?; + Ok(RwLockReadGuard { lock: self }) + } + + #[inline] + pub fn try_read(&self) -> io::Result> { + let handle = self.inner.as_handle().as_raw_handle() as HANDLE; + let overlapped = Overlapped::zero(); + let flags = LOCKFILE_FAIL_IMMEDIATELY; + + syscall(unsafe { LockFileEx(handle, flags, 0, 1, 0, overlapped.raw()) }).map_err( + |error| match error.raw_os_error().map(|error_code| error_code as u32) { + Some(ERROR_LOCK_VIOLATION) => Error::from(ErrorKind::WouldBlock), + _ => error, + }, + )?; + Ok(RwLockReadGuard { lock: self }) + } + + #[inline] + pub fn write(&mut self) -> io::Result> { + // See: https://stackoverflow.com/a/9186532, https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex + let handle = self.inner.as_handle().as_raw_handle() as HANDLE; + let overlapped = Overlapped::zero(); + let flags = LOCKFILE_EXCLUSIVE_LOCK; + syscall(unsafe { LockFileEx(handle, flags, 0, 1, 0, overlapped.raw()) })?; + Ok(RwLockWriteGuard { lock: self }) + } + + #[inline] + pub fn try_write(&mut self) -> io::Result> { + let handle = self.inner.as_handle().as_raw_handle() as HANDLE; + let overlapped = Overlapped::zero(); + let flags = LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK; + + syscall(unsafe { LockFileEx(handle, flags, 0, 1, 0, overlapped.raw()) }).map_err( + |error| match error.raw_os_error().map(|error_code| error_code as u32) { + Some(ERROR_LOCK_VIOLATION) => Error::from(ErrorKind::WouldBlock), + _ => error, + }, + )?; + Ok(RwLockWriteGuard { lock: self }) + } + + #[inline] + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/utils.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/utils.rs new file mode 100644 index 0000000000..43656a0838 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/utils.rs @@ -0,0 +1,33 @@ +use std::io; +use std::mem; + +use windows_sys::Win32::Foundation::BOOL; +use windows_sys::Win32::System::IO::OVERLAPPED; + +/// A wrapper around `OVERLAPPED` to provide "rustic" accessors and +/// initializers. +pub(crate) struct Overlapped(OVERLAPPED); + +impl Overlapped { + /// Creates a new zeroed out instance of an overlapped I/O tracking state. + /// + /// This is suitable for passing to methods which will then later get + /// notified via an I/O Completion Port. + pub(crate) fn zero() -> Overlapped { + Overlapped(unsafe { mem::zeroed() }) + } + + /// Gain access to the raw underlying data + pub(crate) fn raw(&self) -> *mut OVERLAPPED { + &self.0 as *const _ as *mut _ + } +} + +/// Convert a system call which returns a `BOOL` to an `io::Result`. +pub(crate) fn syscall(status: BOOL) -> std::io::Result<()> { + if status == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/write_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/write_guard.rs new file mode 100644 index 0000000000..661b4b53e7 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/sys/windows/write_guard.rs @@ -0,0 +1,38 @@ +use std::os::windows::io::AsHandle; +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::Storage::FileSystem::UnlockFile; + +use std::ops; +use std::os::windows::prelude::*; + +use super::utils::syscall; +use super::RwLock; + +#[derive(Debug)] +pub struct RwLockWriteGuard<'lock, T: AsHandle> { + pub(crate) lock: &'lock mut RwLock, +} + +impl ops::Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.lock.inner + } +} + +impl ops::DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.lock.inner + } +} + +impl Drop for RwLockWriteGuard<'_, T> { + #[inline] + fn drop(&mut self) { + let handle = self.lock.inner.as_handle().as_raw_handle() as HANDLE; + let _ = syscall(unsafe { UnlockFile(handle, 0, 0, 1, 0) }); + } +} diff --git a/userland/upstream-src/fd-lock-4.0.4/src/write_guard.rs b/userland/upstream-src/fd-lock-4.0.4/src/write_guard.rs new file mode 100644 index 0000000000..4eda03dca2 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/src/write_guard.rs @@ -0,0 +1,46 @@ +use std::ops; + +use crate::sys; + +/// RAII structure used to release the exclusive write access of a lock when +/// dropped. +/// +/// This structure is created by the [`write`] and [`try_write`] methods +/// on [`RwLock`]. +/// +/// [`write`]: crate::RwLock::write +/// [`try_write`]: crate::RwLock::try_write +/// [`RwLock`]: crate::RwLock +#[must_use = "if unused the RwLock will immediately unlock"] +#[derive(Debug)] +pub struct RwLockWriteGuard<'lock, T: sys::AsOpenFile> { + guard: sys::RwLockWriteGuard<'lock, T>, +} + +impl<'lock, T: sys::AsOpenFile> RwLockWriteGuard<'lock, T> { + pub(crate) fn new(guard: sys::RwLockWriteGuard<'lock, T>) -> Self { + Self { guard } + } +} + +impl ops::Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +impl ops::DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.guard.deref_mut() + } +} + +/// Release the lock. +impl Drop for RwLockWriteGuard<'_, T> { + #[inline] + fn drop(&mut self) {} +} diff --git a/userland/upstream-src/fd-lock-4.0.4/tests/test.rs b/userland/upstream-src/fd-lock-4.0.4/tests/test.rs new file mode 100644 index 0000000000..dc43ae0733 --- /dev/null +++ b/userland/upstream-src/fd-lock-4.0.4/tests/test.rs @@ -0,0 +1,94 @@ +use fd_lock::RwLock; +use std::fs::File; +use std::io::ErrorKind; + +use tempfile::tempdir; + +#[test] +fn double_read_lock() { + let dir = tempdir().unwrap(); + let path = dir.path().join("lockfile"); + + let l0 = RwLock::new(File::create(&path).unwrap()); + let l1 = RwLock::new(File::open(path).unwrap()); + + let _g0 = l0.try_read().unwrap(); + let _g1 = l1.try_read().unwrap(); +} + +#[test] +fn double_write_lock() { + let dir = tempdir().unwrap(); + let path = dir.path().join("lockfile"); + + let mut l0 = RwLock::new(File::create(&path).unwrap()); + let mut l1 = RwLock::new(File::open(path).unwrap()); + + let g0 = l0.try_write().unwrap(); + + let err = l1.try_write().unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::WouldBlock)); + + drop(g0); +} + +#[test] +fn read_and_write_lock() { + let dir = tempdir().unwrap(); + let path = dir.path().join("lockfile"); + + let l0 = RwLock::new(File::create(&path).unwrap()); + let mut l1 = RwLock::new(File::open(path).unwrap()); + + let g0 = l0.try_read().unwrap(); + + let err = l1.try_write().unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::WouldBlock)); + + drop(g0); +} + +#[test] +fn write_and_read_lock() { + let dir = tempdir().unwrap(); + let path = dir.path().join("lockfile"); + + let mut l0 = RwLock::new(File::create(&path).unwrap()); + let l1 = RwLock::new(File::open(path).unwrap()); + + let g0 = l0.try_write().unwrap(); + + let err = l1.try_read().unwrap_err(); + assert!(matches!(err.kind(), ErrorKind::WouldBlock)); + + drop(g0); +} + +#[cfg(windows)] +mod windows { + use super::*; + use std::os::windows::fs::OpenOptionsExt; + + #[test] + fn try_lock_error() { + let dir = tempdir().unwrap(); + let path = dir.path().join("lockfile"); + + // On Windows, opening with an access_mode as 0 will prevent all locking operations from succeeding, simulating an I/O error. + let mut l0 = RwLock::new( + File::options() + .create(true) + .read(true) + .write(true) + .access_mode(0) + .open(path) + .unwrap(), + ); + + let err1 = l0.try_read().unwrap_err(); + assert!(matches!(err1.kind(), ErrorKind::PermissionDenied)); + + let err2 = l0.try_write().unwrap_err(); + assert!(matches!(err2.kind(), ErrorKind::PermissionDenied)); + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/.cargo-ok b/userland/upstream-src/getrandom-0.2.17/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/getrandom-0.2.17/.cargo_vcs_info.json b/userland/upstream-src/getrandom-0.2.17/.cargo_vcs_info.json new file mode 100644 index 0000000000..d0d51b9b7d --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "b625985d852600a3eeb68556811e59e7c9a6a098" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/getrandom-0.2.17/CHANGELOG.md b/userland/upstream-src/getrandom-0.2.17/CHANGELOG.md new file mode 100644 index 0000000000..403f8240ba --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/CHANGELOG.md @@ -0,0 +1,508 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.17] - 2026-01-12 +### Fixed +- Use `doc_cfg` instead of `doc_auto_cfg` (partial backport of [#732]) [#768] +- `BCryptGenRandom` signature [#778] + +[#732]: https://github.com/rust-random/getrandom/pull/732 +[#768]: https://github.com/rust-random/getrandom/pull/768 +[#778]: https://github.com/rust-random/getrandom/pull/778 + +## [0.2.16] - 2025-04-22 +### Added +- Cygwin support (backport of [#626]) [#654] + +[#626]: https://github.com/rust-random/getrandom/pull/626 +[#654]: https://github.com/rust-random/getrandom/pull/654 + +## [0.2.15] - 2024-05-06 +### Added +- Apple visionOS support [#410] + +### Changed +- Use `libc::getrandom` on DragonflyBSD, FreeBSD, illumos, and Solaris [#411] [#416] [#417] [#420] +- Unify `libc::getentropy`-based implementations [#418] + +[#410]: https://github.com/rust-random/getrandom/pull/410 +[#411]: https://github.com/rust-random/getrandom/pull/411 +[#416]: https://github.com/rust-random/getrandom/pull/416 +[#417]: https://github.com/rust-random/getrandom/pull/417 +[#418]: https://github.com/rust-random/getrandom/pull/418 +[#420]: https://github.com/rust-random/getrandom/pull/420 + +## [0.2.14] - 2024-04-08 +### Fixed +- Enable `/dev/urandom` fallback for MUSL-based Linux targets [#408] + +[#408]: https://github.com/rust-random/getrandom/pull/408 + +## [0.2.13] - 2024-04-06 +### Added +- `linux_disable_fallback` crate feature to disable `/dev/urandom`-based fallback on Linux and + Android targets. Enabling this feature bumps minimum supported Linux kernel version to 3.17 and + Android API level to 23 (Marshmallow). [#396] + +### Changed +- Disable `/dev/urandom` fallback for Linux targets outside of the following `target_arch`es: + `aarch64`, `arm`, `powerpc`, `powerpc64`, `s390x`, `x86`, `x86_64` [#396] +- Do not catch `EPERM` error code on Android while checking availability of + the `getrandom` syscall [#396] + +[#396]: https://github.com/rust-random/getrandom/pull/396 + +## [0.2.12] - 2024-01-09 +### Fixed +- Custom backend for targets without atomics [#385] + +### Changed +- Improve robustness of the Hermit backend and `sys_fill_exact` [#386] +- Raise minimum supported Apple OS versions to macOS 10.12 and iOS 10 [#388] + +### Added +- Document platform support policy [#387] + +[#385]: https://github.com/rust-random/getrandom/pull/385 +[#386]: https://github.com/rust-random/getrandom/pull/386 +[#387]: https://github.com/rust-random/getrandom/pull/387 +[#388]: https://github.com/rust-random/getrandom/pull/388 + +## [0.2.11] - 2023-11-08 +### Added +- GNU/Hurd support [#370] + +### Changed +- Renamed `__getrandom_internal` to `__GETRANDOM_INTERNAL` [#369] +- Updated link to Hermit docs [#374] + +[#369]: https://github.com/rust-random/getrandom/pull/369 +[#370]: https://github.com/rust-random/getrandom/pull/370 +[#374]: https://github.com/rust-random/getrandom/pull/374 + +## [0.2.10] - 2023-06-06 +### Added +- Support for PS Vita (`armv7-sony-vita-newlibeabihf`) [#359] + +### Changed +- Use getentropy from libc on Emscripten targets [#362] + +[#359]: https://github.com/rust-random/getrandom/pull/359 +[#362]: https://github.com/rust-random/getrandom/pull/362 + +## [0.2.9] - 2023-04-06 +### Added +- AIX support [#282] +- `getrandom_uninit` function [#291] +- `wasm64-unknown-unknown` support [#303] +- tvOS and watchOS support [#317] +- QNX/nto support [#325] +- Support for `getrandom` syscall on NetBSD ≥ 10.0 [#331] +- `RtlGenRandom` fallback for non-UWP Windows [#337] + +### Breaking Changes +- Update MSRV to 1.36 [#291] + +### Fixed +- Solaris/OpenBSD/Dragonfly build [#301] + +### Changed +- Update MSRV to 1.36 [#291] +- Use getentropy on Emscripten [#307] +- Solaris: consistantly use `/dev/random` source [#310] +- Move 3ds selection above rdrand/js/custom fallback [#312] +- Remove buffer zeroing from Node.js implementation [#315] +- Use `open` instead of `open64` [#326] +- Remove #cfg from bsd_arandom.rs [#332] +- Hermit: use `sys_read_entropy` syscall [#333] +- Eliminate potential panic in sys_fill_exact [#334] +- rdrand: Remove checking for 0 and !0 and instead check CPU family and do a self-test [#335] +- Move `__getrandom_custom` definition into a const block [#344] +- Switch the custom backend to Rust ABI [#347] + +[#282]: https://github.com/rust-random/getrandom/pull/282 +[#291]: https://github.com/rust-random/getrandom/pull/291 +[#301]: https://github.com/rust-random/getrandom/pull/301 +[#303]: https://github.com/rust-random/getrandom/pull/303 +[#307]: https://github.com/rust-random/getrandom/pull/307 +[#310]: https://github.com/rust-random/getrandom/pull/310 +[#312]: https://github.com/rust-random/getrandom/pull/312 +[#315]: https://github.com/rust-random/getrandom/pull/315 +[#317]: https://github.com/rust-random/getrandom/pull/317 +[#325]: https://github.com/rust-random/getrandom/pull/325 +[#326]: https://github.com/rust-random/getrandom/pull/326 +[#331]: https://github.com/rust-random/getrandom/pull/331 +[#332]: https://github.com/rust-random/getrandom/pull/332 +[#333]: https://github.com/rust-random/getrandom/pull/333 +[#334]: https://github.com/rust-random/getrandom/pull/334 +[#335]: https://github.com/rust-random/getrandom/pull/335 +[#337]: https://github.com/rust-random/getrandom/pull/337 +[#344]: https://github.com/rust-random/getrandom/pull/344 +[#347]: https://github.com/rust-random/getrandom/pull/347 + +## [0.2.8] - 2022-10-20 +### Changed +- The [Web Cryptography API] will now be preferred on `wasm32-unknown-unknown` + when using the `"js"` feature, even on Node.js [#284] [#295] + +### Added +- Added benchmarks to track buffer initialization cost [#272] + +### Fixed +- Use `$crate` in `register_custom_getrandom!` [#270] + +### Documentation +- Add information about enabling `"js"` feature [#280] +- Fix link to `wasm-bindgen` [#278] +- Document the varied implementations for underlying randomness sources [#276] + +[Web Cryptography API]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API +[#284]: https://github.com/rust-random/getrandom/pull/284 +[#295]: https://github.com/rust-random/getrandom/pull/295 +[#272]: https://github.com/rust-random/getrandom/pull/272 +[#270]: https://github.com/rust-random/getrandom/pull/270 +[#280]: https://github.com/rust-random/getrandom/pull/280 +[#278]: https://github.com/rust-random/getrandom/pull/278 +[#276]: https://github.com/rust-random/getrandom/pull/276 + +## [0.2.7] - 2022-06-14 +### Changed +- Update `wasi` dependency to `0.11` [#253] + +### Fixed +- Use `AtomicPtr` instead of `AtomicUsize` for Strict Provenance compatibility. [#263] + +### Documentation +- Add comments explaining use of fallback mechanisms [#257] [#260] + +[#263]: https://github.com/rust-random/getrandom/pull/263 +[#260]: https://github.com/rust-random/getrandom/pull/260 +[#253]: https://github.com/rust-random/getrandom/pull/253 +[#257]: https://github.com/rust-random/getrandom/pull/257 + +## [0.2.6] - 2022-03-28 +### Added +- Nintendo 3DS (`armv6k-nintendo-3ds`) support [#248] + +### Changed +- Retry `open` when interrupted [#252] + +[#248]: https://github.com/rust-random/getrandom/pull/248 +[#252]: https://github.com/rust-random/getrandom/pull/252 + +## [0.2.5] - 2022-02-22 +### Added +- ESP-IDF targets (`*‑espidf`) support [#245] + +### Fixed +- Webpack warning caused by dynamic require [#234] +- Error checking on iOS for `SecRandomCopyBytes` [#244] + +[#234]: https://github.com/rust-random/getrandom/pull/234 +[#244]: https://github.com/rust-random/getrandom/pull/244 +[#245]: https://github.com/rust-random/getrandom/pull/245 + +## [0.2.4] - 2021-12-13 +### Changed +- Use explicit imports in the `js` backend [#220] +- Use `/dev/urandom` on Redox instead of `rand:` [#222] +- Use `NonZeroU32::new_unchecked` to convert wasi error [#233] + +### Added +- SOLID targets (`*-kmc-solid_*`) support [#235] +- Limited Hermit (`x86_64-unknown-hermit`) support [#236] + +[#220]: https://github.com/rust-random/getrandom/pull/220 +[#222]: https://github.com/rust-random/getrandom/pull/222 +[#233]: https://github.com/rust-random/getrandom/pull/233 +[#235]: https://github.com/rust-random/getrandom/pull/235 +[#236]: https://github.com/rust-random/getrandom/pull/236 + +## [0.2.3] - 2021-04-10 +### Changed +- Replace build.rs with link attributes. [#205] +- Add support for getrandom syscall on DragonFly BSD. [#210] +- Improve Node.js detection. [#215] + +[#205]: https://github.com/rust-random/getrandom/pull/205 +[#210]: https://github.com/rust-random/getrandom/pull/210 +[#215]: https://github.com/rust-random/getrandom/pull/215 + +## [0.2.2] - 2021-01-19 +### Changed +- Forward `rustc-dep-of-std` to dependencies. [#198] +- Highlight feature-dependent functionality in documentation using the `doc_cfg` feature. [#200] + +[#198]: https://github.com/rust-random/getrandom/pull/198 +[#200]: https://github.com/rust-random/getrandom/pull/200 + +## [0.2.1] - 2021-01-03 +### Changed +- Update `cfg-if` to v1.0. [#166] +- Update `wasi` to v0.10. [#167] + +### Fixed +- Multithreaded WASM support. [#165] + +### Removed +- Windows XP support. [#177] +- Direct `stdweb` support. [#178] +- CloudABI support. [#184] + +[#165]: https://github.com/rust-random/getrandom/pull/165 +[#166]: https://github.com/rust-random/getrandom/pull/166 +[#167]: https://github.com/rust-random/getrandom/pull/167 +[#177]: https://github.com/rust-random/getrandom/pull/177 +[#178]: https://github.com/rust-random/getrandom/pull/178 +[#184]: https://github.com/rust-random/getrandom/pull/184 + +## [0.2.0] - 2020-09-10 +### Features for using getrandom on unsupported targets + +The following (off by default) Cargo features have been added: +- `"rdrand"` - use the RDRAND instruction on `no_std` `x86`/`x86_64` targets [#133] +- `"js"` - use JavaScript calls on `wasm32-unknown-unknown` [#149] + - Replaces the `stdweb` and `wasm-bindgen` features (which are removed) +- `"custom"` - allows a user to specify a custom implementation [#109] + +### Breaking Changes +- Unsupported targets no longer compile [#107] +- Change/Add `Error` constants [#120] +- Only impl `std` traits when the `"std"` Cargo feature is specified [#106] +- Remove official support for Hermit, L4Re, and UEFI [#133] +- Remove optional `"log"` dependency [#131] +- Update minimum supported Linux kernel to 2.6.32 [#153] +- Update MSRV to 1.34 [#159] + +[#106]: https://github.com/rust-random/getrandom/pull/106 +[#107]: https://github.com/rust-random/getrandom/pull/107 +[#109]: https://github.com/rust-random/getrandom/pull/109 +[#120]: https://github.com/rust-random/getrandom/pull/120 +[#131]: https://github.com/rust-random/getrandom/pull/131 +[#133]: https://github.com/rust-random/getrandom/pull/133 +[#149]: https://github.com/rust-random/getrandom/pull/149 +[#153]: https://github.com/rust-random/getrandom/pull/153 +[#159]: https://github.com/rust-random/getrandom/pull/159 + +## [0.1.16] - 2020-12-31 +### Changed +- Update `cfg-if` to v1.0. [#173] +- Implement `std::error::Error` for the `Error` type on additional targets. [#169] + +### Fixed +- Multithreaded WASM support. [#171] + +[#173]: https://github.com/rust-random/getrandom/pull/173 +[#171]: https://github.com/rust-random/getrandom/pull/171 +[#169]: https://github.com/rust-random/getrandom/pull/169 + +## [0.1.15] - 2020-09-10 +### Changed +- Added support for Internet Explorer 11 [#139] +- Fix Webpack require warning with `wasm-bindgen` [#137] + +[#137]: https://github.com/rust-random/getrandom/pull/137 +[#139]: https://github.com/rust-random/getrandom/pull/139 + +## [0.1.14] - 2020-01-07 +### Changed +- Remove use of spin-locks in the `use_file` module. [#125] +- Update `wasi` to v0.9. [#126] +- Do not read errno value on DragonFlyBSD to fix compilation failure. [#129] + +[#125]: https://github.com/rust-random/getrandom/pull/125 +[#126]: https://github.com/rust-random/getrandom/pull/126 +[#129]: https://github.com/rust-random/getrandom/pull/129 + +## [0.1.13] - 2019-08-25 +### Added +- VxWorks targets support. [#86] + +### Changed +- If zero-length slice is passed to the `getrandom` function, always return +`Ok(())` immediately without doing any calls to the underlying operating +system. [#104] +- Use the `kern.arandom` sysctl on NetBSD. [#115] + +### Fixed +- Bump `cfg-if` minimum version from 0.1.0 to 0.1.2. [#112] +- Typos and bad doc links. [#117] + +[#86]: https://github.com/rust-random/getrandom/pull/86 +[#104]: https://github.com/rust-random/getrandom/pull/104 +[#112]: https://github.com/rust-random/getrandom/pull/112 +[#115]: https://github.com/rust-random/getrandom/pull/115 +[#117]: https://github.com/rust-random/getrandom/pull/117 + +## [0.1.12] - 2019-08-18 +### Changed +- Update wasi dependency from v0.5 to v0.7. [#100] + +[#100]: https://github.com/rust-random/getrandom/pull/100 + +## [0.1.11] - 2019-08-25 +### Fixed +- Implement `std`-dependent traits for selected targets even if `std` +feature is disabled. (backward compatibility with v0.1.8) [#96] + +[#96]: https://github.com/rust-random/getrandom/pull/96 + +## [0.1.10] - 2019-08-18 [YANKED] +### Changed +- Use the dummy implementation on `wasm32-unknown-unknown` even with the +disabled `dummy` feature. [#90] + +### Fixed +- Fix CSP error for `wasm-bindgen`. [#92] + +[#90]: https://github.com/rust-random/getrandom/pull/90 +[#92]: https://github.com/rust-random/getrandom/pull/92 + +## [0.1.9] - 2019-08-14 [YANKED] +### Changed +- Remove `std` dependency for opening and reading files. [#58] +- Use `wasi` instead of `libc` on WASI target. [#64] +- By default emit a compile-time error when built for an unsupported target. +This behaviour can be disabled by using the `dummy` feature. [#71] + +### Added +- Add support for UWP targets. [#69] +- Add unstable `rustc-dep-of-std` feature. [#78] + +[#58]: https://github.com/rust-random/getrandom/pull/58 +[#64]: https://github.com/rust-random/getrandom/pull/64 +[#69]: https://github.com/rust-random/getrandom/pull/69 +[#71]: https://github.com/rust-random/getrandom/pull/71 +[#78]: https://github.com/rust-random/getrandom/pull/78 + +## [0.1.8] - 2019-07-29 +### Changed +- Explicitly specify types to arguments of 'libc::syscall'. [#74] + +[#74]: https://github.com/rust-random/getrandom/pull/74 + +## [0.1.7] - 2019-07-29 +### Added +- Support for hermit and l4re. [#61] +- `Error::raw_os_error` method, `Error::INTERNAL_START` and +`Error::CUSTOM_START` constants. Use `libc` for retrieving OS error descriptions. [#54] + +### Changed +- Remove `lazy_static` dependency and use custom structures for lock-free +initialization. [#51] [#52] +- Try `getrandom()` first on FreeBSD. [#57] + +### Removed +- Bitrig support. [#56] + +### Deprecated +- `Error::UNKNOWN`, `Error::UNAVAILABLE`. [#54] + +[#51]: https://github.com/rust-random/getrandom/pull/51 +[#52]: https://github.com/rust-random/getrandom/pull/52 +[#54]: https://github.com/rust-random/getrandom/pull/54 +[#56]: https://github.com/rust-random/getrandom/pull/56 +[#57]: https://github.com/rust-random/getrandom/pull/57 +[#61]: https://github.com/rust-random/getrandom/pull/61 + +## [0.1.6] - 2019-06-30 +### Changed +- Minor change of RDRAND AMD bug handling. [#48] + +[#48]: https://github.com/rust-random/getrandom/pull/48 + +## [0.1.5] - 2019-06-29 +### Fixed +- Use shared `File` instead of shared file descriptor. [#44] +- Workaround for RDRAND hardware bug present on some AMD CPUs. [#43] + +### Changed +- Try `getentropy` and then fallback to `/dev/random` on macOS. [#38] + +[#38]: https://github.com/rust-random/getrandom/issues/38 +[#43]: https://github.com/rust-random/getrandom/pull/43 +[#44]: https://github.com/rust-random/getrandom/issues/44 + +## [0.1.4] - 2019-06-28 +### Added +- Add support for `x86_64-unknown-uefi` target by using RDRAND with CPUID +feature detection. [#30] + +### Fixed +- Fix long buffer issues on Windows and Linux. [#31] [#32] +- Check `EPERM` in addition to `ENOSYS` on Linux. [#37] + +### Changed +- Improve efficiency by sharing file descriptor across threads. [#13] +- Remove `cloudabi`, `winapi`, and `fuchsia-cprng` dependencies. [#40] +- Improve RDRAND implementation. [#24] +- Don't block during syscall detection on Linux. [#26] +- Increase consistency with libc implementation on FreeBSD. [#36] +- Apply `rustfmt`. [#39] + +[#30]: https://github.com/rust-random/getrandom/pull/30 +[#13]: https://github.com/rust-random/getrandom/issues/13 +[#40]: https://github.com/rust-random/getrandom/pull/40 +[#26]: https://github.com/rust-random/getrandom/pull/26 +[#24]: https://github.com/rust-random/getrandom/pull/24 +[#39]: https://github.com/rust-random/getrandom/pull/39 +[#36]: https://github.com/rust-random/getrandom/pull/36 +[#31]: https://github.com/rust-random/getrandom/issues/31 +[#32]: https://github.com/rust-random/getrandom/issues/32 +[#37]: https://github.com/rust-random/getrandom/issues/37 + +## [0.1.3] - 2019-05-15 +- Update for `wasm32-unknown-wasi` being renamed to `wasm32-wasi`, and for + WASI being categorized as an OS. + +## [0.1.2] - 2019-04-06 +- Add support for `wasm32-unknown-wasi` target. + +## [0.1.1] - 2019-04-05 +- Enable std functionality for CloudABI by default. + +## [0.1.0] - 2019-03-23 +Publish initial implementation. + +## [0.0.0] - 2019-01-19 +Publish an empty template library. + +[0.2.17]: https://github.com/rust-random/getrandom/compare/v0.2.16...v0.2.17 +[0.2.16]: https://github.com/rust-random/getrandom/compare/v0.2.15...v0.2.16 +[0.2.15]: https://github.com/rust-random/getrandom/compare/v0.2.14...v0.2.15 +[0.2.14]: https://github.com/rust-random/getrandom/compare/v0.2.13...v0.2.14 +[0.2.13]: https://github.com/rust-random/getrandom/compare/v0.2.12...v0.2.13 +[0.2.12]: https://github.com/rust-random/getrandom/compare/v0.2.11...v0.2.12 +[0.2.11]: https://github.com/rust-random/getrandom/compare/v0.2.10...v0.2.11 +[0.2.10]: https://github.com/rust-random/getrandom/compare/v0.2.9...v0.2.10 +[0.2.9]: https://github.com/rust-random/getrandom/compare/v0.2.8...v0.2.9 +[0.2.8]: https://github.com/rust-random/getrandom/compare/v0.2.7...v0.2.8 +[0.2.7]: https://github.com/rust-random/getrandom/compare/v0.2.6...v0.2.7 +[0.2.6]: https://github.com/rust-random/getrandom/compare/v0.2.5...v0.2.6 +[0.2.5]: https://github.com/rust-random/getrandom/compare/v0.2.4...v0.2.5 +[0.2.4]: https://github.com/rust-random/getrandom/compare/v0.2.3...v0.2.4 +[0.2.3]: https://github.com/rust-random/getrandom/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/rust-random/getrandom/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/rust-random/getrandom/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/rust-random/getrandom/compare/v0.1.15...v0.2.0 +[0.1.16]: https://github.com/rust-random/getrandom/compare/v0.1.15...v0.1.16 +[0.1.15]: https://github.com/rust-random/getrandom/compare/v0.1.14...v0.1.15 +[0.1.14]: https://github.com/rust-random/getrandom/compare/v0.1.13...v0.1.14 +[0.1.13]: https://github.com/rust-random/getrandom/compare/v0.1.12...v0.1.13 +[0.1.12]: https://github.com/rust-random/getrandom/compare/v0.1.11...v0.1.12 +[0.1.11]: https://github.com/rust-random/getrandom/compare/v0.1.10...v0.1.11 +[0.1.10]: https://github.com/rust-random/getrandom/compare/v0.1.9...v0.1.10 +[0.1.9]: https://github.com/rust-random/getrandom/compare/v0.1.8...v0.1.9 +[0.1.8]: https://github.com/rust-random/getrandom/compare/v0.1.7...v0.1.8 +[0.1.7]: https://github.com/rust-random/getrandom/compare/v0.1.6...v0.1.7 +[0.1.6]: https://github.com/rust-random/getrandom/compare/v0.1.5...v0.1.6 +[0.1.5]: https://github.com/rust-random/getrandom/compare/v0.1.4...v0.1.5 +[0.1.4]: https://github.com/rust-random/getrandom/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/rust-random/getrandom/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/rust-random/getrandom/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/rust-random/getrandom/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/rust-random/getrandom/compare/v0.0.0...v0.1.0 +[0.0.0]: https://github.com/rust-random/getrandom/releases/tag/v0.0.0 diff --git a/userland/upstream-src/getrandom-0.2.17/Cargo.lock b/userland/upstream-src/getrandom-0.2.17/Cargo.lock new file mode 100644 index 0000000000..3c978ac437 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/Cargo.lock @@ -0,0 +1,413 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compiler_builtins" +version = "0.1.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6376049cfa92c0aa8b9ac95fae22184b981c658208d4ed8a1dc553cd83612895" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "getrandom" +version = "0.2.17" +dependencies = [ + "cfg-if", + "compiler_builtins", + "js-sys", + "libc", + "rustc-std-workspace-core", + "wasi", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +dependencies = [ + "rustc-std-workspace-core", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-std-workspace-alloc" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d441c3b2ebf55cebf796bfdc265d67fa09db17b7bb6bd4be75c509e1e8fec3" + +[[package]] +name = "rustc-std-workspace-core" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9c45b374136f52f2d6311062c7146bff20fec063c3f5d46a410bd937746955" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +dependencies = [ + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/userland/upstream-src/getrandom-0.2.17/Cargo.toml b/userland/upstream-src/getrandom-0.2.17/Cargo.toml new file mode 100644 index 0000000000..626f9cfbe8 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/Cargo.toml @@ -0,0 +1,121 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "getrandom" +version = "0.2.17" +authors = ["The Rand Project Developers"] +build = false +exclude = [".*"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A small cross-platform library for retrieving random data from system source" +documentation = "https://docs.rs/getrandom" +readme = "README.md" +categories = [ + "os", + "no-std", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-random/getrandom" + +[package.metadata.docs.rs] +features = [ + "std", + "custom", +] +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[package.metadata.cross.target.x86_64-unknown-netbsd] +pre-build = [ + "mkdir -p /tmp/netbsd", + "curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", + "tar -C /tmp/netbsd -xJf base.tar.xz", + "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", + "rm base.tar.xz", + "rm -rf /tmp/netbsd", +] + +[features] +custom = [] +js = [ + "wasm-bindgen", + "js-sys", +] +linux_disable_fallback = [] +rdrand = [] +rustc-dep-of-std = [ + "compiler_builtins", + "core", + "libc/rustc-dep-of-std", + "wasi/rustc-dep-of-std", +] +std = [] +test-in-browser = [] + +[lib] +name = "getrandom" +path = "src/lib.rs" + +[[test]] +name = "custom" +path = "tests/custom.rs" + +[[test]] +name = "normal" +path = "tests/normal.rs" + +[[test]] +name = "rdrand" +path = "tests/rdrand.rs" + +[[bench]] +name = "buffer" +path = "benches/buffer.rs" + +[dependencies.cfg-if] +version = "1" + +[dependencies.compiler_builtins] +version = "0.1" +optional = true + +[dependencies.core] +version = "1.0" +optional = true +package = "rustc-std-workspace-core" + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.js-sys] +version = "0.3" +optional = true + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.wasm-bindgen] +version = "0.2.62" +optional = true +default-features = false + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies.wasm-bindgen-test] +version = "0.3.18" + +[target.'cfg(target_os = "wasi")'.dependencies.wasi] +version = "0.11" +default-features = false + +[target."cfg(unix)".dependencies.libc] +version = "0.2.154" +default-features = false diff --git a/userland/upstream-src/getrandom-0.2.17/Cargo.toml.orig b/userland/upstream-src/getrandom-0.2.17/Cargo.toml.orig new file mode 100644 index 0000000000..fa2a05d8d6 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/Cargo.toml.orig @@ -0,0 +1,67 @@ +[package] +name = "getrandom" +version = "0.2.17" # Also update html_root_url in lib.rs when bumping this +edition = "2018" +authors = ["The Rand Project Developers"] +license = "MIT OR Apache-2.0" +description = "A small cross-platform library for retrieving random data from system source" +documentation = "https://docs.rs/getrandom" +repository = "https://github.com/rust-random/getrandom" +categories = ["os", "no-std"] +exclude = [".*"] + +[dependencies] +cfg-if = "1" + +# When built as part of libstd +compiler_builtins = { version = "0.1", optional = true } +core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2.154", default-features = false } + +[target.'cfg(target_os = "wasi")'.dependencies] +wasi = { version = "0.11", default-features = false } + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] +wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } +js-sys = { version = "0.3", optional = true } +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = "0.3.18" + +[features] +# Implement std-only traits for getrandom::Error +std = [] +# Disable `/dev/urandom` fallback for Linux and Android targets. +# Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow). +linux_disable_fallback = [] +# Feature to enable fallback RDRAND-based implementation on x86/x86_64 +rdrand = [] +# Feature to enable JavaScript bindings on wasm*-unknown-unknown +js = ["wasm-bindgen", "js-sys"] +# Feature to enable custom RNG implementations +custom = [] +# Unstable feature to support being a libstd dependency +rustc-dep-of-std = [ + "compiler_builtins", + "core", + "libc/rustc-dep-of-std", + "wasi/rustc-dep-of-std", +] +# Unstable/test-only feature to run wasm-bindgen tests in a browser +test-in-browser = [] + +[package.metadata.docs.rs] +features = ["std", "custom"] +rustdoc-args = ["--cfg", "docsrs"] + +# workaround for https://github.com/cross-rs/cross/issues/1345 +[package.metadata.cross.target.x86_64-unknown-netbsd] +pre-build = [ + "mkdir -p /tmp/netbsd", + "curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", + "tar -C /tmp/netbsd -xJf base.tar.xz", + "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", + "rm base.tar.xz", + "rm -rf /tmp/netbsd", +] diff --git a/userland/upstream-src/getrandom-0.2.17/LICENSE-APACHE b/userland/upstream-src/getrandom-0.2.17/LICENSE-APACHE new file mode 100644 index 0000000000..17d74680f8 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/userland/upstream-src/getrandom-0.2.17/LICENSE-MIT b/userland/upstream-src/getrandom-0.2.17/LICENSE-MIT new file mode 100644 index 0000000000..8ca28a1a09 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2018-2024 The rust-random Project Developers +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/getrandom-0.2.17/README.md b/userland/upstream-src/getrandom-0.2.17/README.md new file mode 100644 index 0000000000..b4b5a2b566 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/README.md @@ -0,0 +1,81 @@ +# getrandom + +[![Build Status]][GitHub Actions] [![Crate]][crates.io] [![Documentation]][docs.rs] [![Dependency Status]][deps.rs] [![Downloads]][crates.io] [![License]][LICENSE-MIT] + +[GitHub Actions]: https://github.com/rust-random/getrandom/actions?query=workflow:Tests+branch:master +[Build Status]: https://github.com/rust-random/getrandom/actions/workflows/tests.yml/badge.svg?branch=master +[crates.io]: https://crates.io/crates/getrandom +[Crate]: https://img.shields.io/crates/v/getrandom +[docs.rs]: https://docs.rs/getrandom +[Documentation]: https://docs.rs/getrandom/badge.svg +[deps.rs]: https://deps.rs/repo/github/rust-random/getrandom +[Dependency Status]: https://deps.rs/repo/github/rust-random/getrandom/status.svg +[Downloads]: https://img.shields.io/crates/d/getrandom +[LICENSE-MIT]: https://raw.githubusercontent.com/rust-random/getrandom/master/LICENSE-MIT +[License]: https://img.shields.io/crates/l/getrandom + + +A Rust library for retrieving random data from (operating) system sources. It is +assumed that the system always provides high-quality cryptographically secure random +data, ideally backed by hardware entropy sources. This crate derives its name +from Linux's `getrandom` function, but is cross-platform, roughly supporting +the same set of platforms as Rust's `std` lib. + +This is a low-level API. Most users should prefer using high-level random-number +library like [`rand`]. + +[`rand`]: https://crates.io/crates/rand + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +getrandom = "0.2" +``` + +Then invoke the `getrandom` function: + +```rust +fn get_random_buf() -> Result<[u8; 32], getrandom::Error> { + let mut buf = [0u8; 32]; + getrandom::getrandom(&mut buf)?; + Ok(buf) +} +``` + +For more information about supported targets, entropy sources, `no_std` targets, +crate features, WASM support and Custom RNGs see the +[`getrandom` documentation](https://docs.rs/getrandom/latest) and +[`getrandom::Error` documentation](https://docs.rs/getrandom/latest/getrandom/struct.Error.html). + +## Minimum Supported Rust Version + +This crate requires Rust 1.36.0 or later. + +## Platform Support + +This crate generally supports the same operating system and platform versions that the Rust standard library does. +Additional targets may be supported using pluggable custom implementations. + +This means that as Rust drops support for old versions of operating systems (such as old Linux kernel versions, Android API levels, etc) +in stable releases, `getrandom` may create new patch releases (`0.N.x`) that remove support for outdated platform versions. + +## License + +The `getrandom` library is distributed under either of + + * [Apache License, Version 2.0][LICENSE-APACHE] + * [MIT license][LICENSE-MIT] + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-APACHE]: https://github.com/rust-random/getrandom/blob/master/LICENSE-APACHE +[LICENSE-MIT]: https://github.com/rust-random/getrandom/blob/master/LICENSE-MIT diff --git a/userland/upstream-src/getrandom-0.2.17/SECURITY.md b/userland/upstream-src/getrandom-0.2.17/SECURITY.md new file mode 100644 index 0000000000..19bfb9a27a --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/rust-random/getrandom/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/userland/upstream-src/getrandom-0.2.17/benches/buffer.rs b/userland/upstream-src/getrandom-0.2.17/benches/buffer.rs new file mode 100644 index 0000000000..b32be4336c --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/benches/buffer.rs @@ -0,0 +1,71 @@ +#![feature(test, maybe_uninit_uninit_array_transpose)] +extern crate test; + +use std::mem::MaybeUninit; + +// Call getrandom on a zero-initialized stack buffer +#[inline(always)] +fn bench_getrandom() { + let mut buf = [0u8; N]; + getrandom::getrandom(&mut buf).unwrap(); + test::black_box(&buf as &[u8]); +} + +// Call getrandom_uninit on an uninitialized stack buffer +#[inline(always)] +fn bench_getrandom_uninit() { + let mut uninit = [MaybeUninit::uninit(); N]; + let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap(); + test::black_box(buf); +} + +// We benchmark using #[inline(never)] "inner" functions for two reasons: +// - Avoiding inlining reduces a source of variance when running benchmarks. +// - It is _much_ easier to get the assembly or IR for the inner loop. +// +// For example, using cargo-show-asm (https://github.com/pacak/cargo-show-asm), +// we can get the assembly for a particular benchmark's inner loop by running: +// cargo asm --bench buffer --release buffer::p384::bench_getrandom::inner +macro_rules! bench { + ( $name:ident, $size:expr ) => { + pub mod $name { + #[bench] + pub fn bench_getrandom(b: &mut test::Bencher) { + #[inline(never)] + fn inner() { + super::bench_getrandom::<{ $size }>() + } + + b.bytes = $size as u64; + b.iter(inner); + } + #[bench] + pub fn bench_getrandom_uninit(b: &mut test::Bencher) { + #[inline(never)] + fn inner() { + super::bench_getrandom_uninit::<{ $size }>() + } + + b.bytes = $size as u64; + b.iter(inner); + } + } + }; +} + +// 16 bytes (128 bits) is the size of an 128-bit AES key/nonce. +bench!(aes128, 128 / 8); + +// 32 bytes (256 bits) is the seed sized used for rand::thread_rng +// and the `random` value in a ClientHello/ServerHello for TLS. +// This is also the size of a 256-bit AES/HMAC/P-256/Curve25519 key +// and/or nonce. +bench!(p256, 256 / 8); + +// A P-384/HMAC-384 key and/or nonce. +bench!(p384, 384 / 8); + +// Initializing larger buffers is not the primary use case of this library, as +// this should normally be done by a userspace CSPRNG. However, we have a test +// here to see the effects of a lower (amortized) syscall overhead. +bench!(page, 4096); diff --git a/userland/upstream-src/getrandom-0.2.17/src/apple-other.rs b/userland/upstream-src/getrandom-0.2.17/src/apple-other.rs new file mode 100644 index 0000000000..167d8cf0fa --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/apple-other.rs @@ -0,0 +1,24 @@ +//! Implementation for iOS, tvOS, and watchOS where `getentropy` is unavailable. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +// libsystem contains the libc of Darwin, and every binary ends up linked against it either way. This +// makes it a more lightweight choice compared to `Security.framework`. +extern "C" { + // This RNG uses a thread-local CSPRNG to provide data, which is seeded by the operating system's root CSPRNG. + // Its the best option after `getentropy` on modern Darwin-based platforms that also avoids the + // high startup costs and linking of Security.framework. + // + // While its just an implementation detail, `Security.framework` just calls into this anyway. + fn CCRandomGenerateBytes(bytes: *mut c_void, size: usize) -> i32; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = unsafe { CCRandomGenerateBytes(dest.as_mut_ptr() as *mut c_void, dest.len()) }; + // kCCSuccess (from CommonCryptoError.h) is always zero. + if ret != 0 { + Err(Error::IOS_SEC_RANDOM) + } else { + Ok(()) + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/custom.rs b/userland/upstream-src/getrandom-0.2.17/src/custom.rs new file mode 100644 index 0000000000..79be7fc26e --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/custom.rs @@ -0,0 +1,105 @@ +//! An implementation which calls out to an externally defined function. +use crate::{util::uninit_slice_fill_zero, Error}; +use core::{mem::MaybeUninit, num::NonZeroU32}; + +/// Register a function to be invoked by `getrandom` on unsupported targets. +/// +/// ## Writing a custom `getrandom` implementation +/// +/// The function to register must have the same signature as +/// [`getrandom::getrandom`](crate::getrandom). The function can be defined +/// wherever you want, either in root crate or a dependent crate. +/// +/// For example, if we wanted a `failure-getrandom` crate containing an +/// implementation that always fails, we would first depend on `getrandom` +/// (for the [`Error`] type) in `failure-getrandom/Cargo.toml`: +/// ```toml +/// [dependencies] +/// getrandom = "0.2" +/// ``` +/// Note that the crate containing this function does **not** need to enable the +/// `"custom"` Cargo feature. +/// +/// Next, in `failure-getrandom/src/lib.rs`, we define our function: +/// ```rust +/// use core::num::NonZeroU32; +/// use getrandom::Error; +/// +/// // Some application-specific error code +/// const MY_CUSTOM_ERROR_CODE: u32 = Error::CUSTOM_START + 42; +/// pub fn always_fail(buf: &mut [u8]) -> Result<(), Error> { +/// let code = NonZeroU32::new(MY_CUSTOM_ERROR_CODE).unwrap(); +/// Err(Error::from(code)) +/// } +/// ``` +/// +/// ## Registering a custom `getrandom` implementation +/// +/// Functions can only be registered in the root binary crate. Attempting to +/// register a function in a non-root crate will result in a linker error. +/// This is similar to +/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or +/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), +/// where helper crates define handlers/allocators but only the binary crate +/// actually _uses_ the functionality. +/// +/// To register the function, we first depend on `failure-getrandom` _and_ +/// `getrandom` in `Cargo.toml`: +/// ```toml +/// [dependencies] +/// failure-getrandom = "0.1" +/// getrandom = { version = "0.2", features = ["custom"] } +/// ``` +/// +/// Then, we register the function in `src/main.rs`: +/// ```rust +/// # mod failure_getrandom { pub fn always_fail(_: &mut [u8]) -> Result<(), getrandom::Error> { unimplemented!() } } +/// use failure_getrandom::always_fail; +/// use getrandom::register_custom_getrandom; +/// +/// register_custom_getrandom!(always_fail); +/// ``` +/// +/// Now any user of `getrandom` (direct or indirect) on this target will use the +/// registered function. As noted in the +/// [top-level documentation](index.html#custom-implementations) this +/// registration only has an effect on unsupported targets. +#[macro_export] +macro_rules! register_custom_getrandom { + ($path:path) => { + // TODO(MSRV 1.37): change to unnamed block + const __GETRANDOM_INTERNAL: () = { + // We use Rust ABI to be safe against potential panics in the passed function. + #[no_mangle] + unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { + // Make sure the passed function has the type of getrandom::getrandom + type F = fn(&mut [u8]) -> ::core::result::Result<(), $crate::Error>; + let _: F = $crate::getrandom; + let f: F = $path; + let slice = ::core::slice::from_raw_parts_mut(dest, len); + match f(slice) { + Ok(()) => 0, + Err(e) => e.code().get(), + } + } + }; + }; +} + +#[allow(dead_code)] +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; + } + // Previously we always passed a valid, initialized slice to + // `__getrandom_custom`. Ensure `dest` has been initialized for backward + // compatibility with implementations that rely on that (e.g. Rust + // implementations that construct a `&mut [u8]` slice from `dest` and + // `len`). + let dest = uninit_slice_fill_zero(dest); + let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) }; + match NonZeroU32::new(ret) { + None => Ok(()), + Some(code) => Err(Error::from(code)), + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/error.rs b/userland/upstream-src/getrandom-0.2.17/src/error.rs new file mode 100644 index 0000000000..13c81c7aff --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/error.rs @@ -0,0 +1,189 @@ +use core::{fmt, num::NonZeroU32}; + +/// A small and `no_std` compatible error type +/// +/// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and +/// if so, which error code the OS gave the application. If such an error is +/// encountered, please consult with your system documentation. +/// +/// Internally this type is a NonZeroU32, with certain values reserved for +/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`]. +/// +/// *If this crate's `"std"` Cargo feature is enabled*, then: +/// - [`getrandom::Error`][Error] implements +/// [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) +/// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements +/// [`From`](https://doc.rust-lang.org/std/convert/trait.From.html). +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Error(NonZeroU32); + +const fn internal_error(n: u16) -> Error { + // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32. + let code = Error::INTERNAL_START + (n as u32); + Error(unsafe { NonZeroU32::new_unchecked(code) }) +} + +impl Error { + /// This target/platform is not supported by `getrandom`. + pub const UNSUPPORTED: Error = internal_error(0); + /// The platform-specific `errno` returned a non-positive value. + pub const ERRNO_NOT_POSITIVE: Error = internal_error(1); + /// Encountered an unexpected situation which should not happen in practice. + pub const UNEXPECTED: Error = internal_error(2); + /// Call to [`CCRandomGenerateBytes`](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html) failed + /// on iOS, tvOS, or waatchOS. + // TODO: Update this constant name in the next breaking release. + pub const IOS_SEC_RANDOM: Error = internal_error(3); + /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. + pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4); + /// RDRAND instruction failed due to a hardware issue. + pub const FAILED_RDRAND: Error = internal_error(5); + /// RDRAND instruction unsupported on this target. + pub const NO_RDRAND: Error = internal_error(6); + /// The environment does not support the Web Crypto API. + pub const WEB_CRYPTO: Error = internal_error(7); + /// Calling Web Crypto API `crypto.getRandomValues` failed. + pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8); + /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). + pub const VXWORKS_RAND_SECURE: Error = internal_error(11); + /// Node.js does not have the `crypto` CommonJS module. + pub const NODE_CRYPTO: Error = internal_error(12); + /// Calling Node.js function `crypto.randomFillSync` failed. + pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13); + /// Called from an ES module on Node.js. This is unsupported, see: + /// . + pub const NODE_ES_MODULE: Error = internal_error(14); + + /// Codes below this point represent OS Errors (i.e. positive i32 values). + /// Codes at or above this point, but below [`Error::CUSTOM_START`] are + /// reserved for use by the `rand` and `getrandom` crates. + pub const INTERNAL_START: u32 = 1 << 31; + + /// Codes at or above this point can be used by users to define their own + /// custom errors. + pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); + + /// Extract the raw OS error code (if this error came from the OS) + /// + /// This method is identical to [`std::io::Error::raw_os_error()`][1], except + /// that it works in `no_std` contexts. If this method returns `None`, the + /// error value can still be formatted via the `Display` implementation. + /// + /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error + #[inline] + pub fn raw_os_error(self) -> Option { + if self.0.get() < Self::INTERNAL_START { + match () { + #[cfg(target_os = "solid_asp3")] + // On SOLID, negate the error code again to obtain the original + // error code. + () => Some(-(self.0.get() as i32)), + #[cfg(not(target_os = "solid_asp3"))] + () => Some(self.0.get() as i32), + } + } else { + None + } + } + + /// Extract the bare error code. + /// + /// This code can either come from the underlying OS, or be a custom error. + /// Use [`Error::raw_os_error()`] to disambiguate. + #[inline] + pub const fn code(self) -> NonZeroU32 { + self.0 + } +} + +cfg_if! { + if #[cfg(unix)] { + fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { + let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; + if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { + return None; + } + + // Take up to trailing null byte + let n = buf.len(); + let idx = buf.iter().position(|&b| b == 0).unwrap_or(n); + core::str::from_utf8(&buf[..idx]).ok() + } + } else { + fn os_err(_errno: i32, _buf: &mut [u8]) -> Option<&str> { + None + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut dbg = f.debug_struct("Error"); + if let Some(errno) = self.raw_os_error() { + dbg.field("os_error", &errno); + let mut buf = [0u8; 128]; + if let Some(err) = os_err(errno, &mut buf) { + dbg.field("description", &err); + } + } else if let Some(desc) = internal_desc(*self) { + dbg.field("internal_code", &self.0.get()); + dbg.field("description", &desc); + } else { + dbg.field("unknown_code", &self.0.get()); + } + dbg.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(errno) = self.raw_os_error() { + let mut buf = [0u8; 128]; + match os_err(errno, &mut buf) { + Some(err) => err.fmt(f), + None => write!(f, "OS Error: {}", errno), + } + } else if let Some(desc) = internal_desc(*self) { + f.write_str(desc) + } else { + write!(f, "Unknown Error: {}", self.0.get()) + } + } +} + +impl From for Error { + fn from(code: NonZeroU32) -> Self { + Self(code) + } +} + +fn internal_desc(error: Error) -> Option<&'static str> { + match error { + Error::UNSUPPORTED => Some("getrandom: this target is not supported"), + Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"), + Error::UNEXPECTED => Some("unexpected situation"), + Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"), + Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"), + Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), + Error::NO_RDRAND => Some("RDRAND: instruction not supported"), + Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"), + Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"), + Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"), + Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"), + Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"), + Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::Error; + use core::mem::size_of; + + #[test] + fn test_size() { + assert_eq!(size_of::(), 4); + assert_eq!(size_of::>(), 4); + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/error_impls.rs b/userland/upstream-src/getrandom-0.2.17/src/error_impls.rs new file mode 100644 index 0000000000..2c326012c8 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/error_impls.rs @@ -0,0 +1,15 @@ +extern crate std; + +use crate::Error; +use std::io; + +impl From for io::Error { + fn from(err: Error) -> Self { + match err.raw_os_error() { + Some(errno) => io::Error::from_raw_os_error(errno), + None => io::Error::new(io::ErrorKind::Other, err), + } + } +} + +impl std::error::Error for Error {} diff --git a/userland/upstream-src/getrandom-0.2.17/src/espidf.rs b/userland/upstream-src/getrandom-0.2.17/src/espidf.rs new file mode 100644 index 0000000000..7da5ca88ea --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/espidf.rs @@ -0,0 +1,18 @@ +//! Implementation for ESP-IDF +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +extern "C" { + fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) + // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html + // + // However tracking if some of these entropy sources is enabled is way too difficult to implement here + unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) }; + + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/fuchsia.rs b/userland/upstream-src/getrandom-0.2.17/src/fuchsia.rs new file mode 100644 index 0000000000..11970685c0 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/fuchsia.rs @@ -0,0 +1,13 @@ +//! Implementation for Fuchsia Zircon +use crate::Error; +use core::mem::MaybeUninit; + +#[link(name = "zircon")] +extern "C" { + fn zx_cprng_draw(buffer: *mut u8, length: usize); +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { zx_cprng_draw(dest.as_mut_ptr() as *mut u8, dest.len()) } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/getentropy.rs b/userland/upstream-src/getrandom-0.2.17/src/getentropy.rs new file mode 100644 index 0000000000..41bab8fe91 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/getentropy.rs @@ -0,0 +1,21 @@ +//! Implementation using getentropy(2) +//! +//! Available since: +//! - macOS 10.12 +//! - OpenBSD 5.6 +//! - Emscripten 2.0.5 +//! - vita newlib since Dec 2021 +//! +//! For these targets, we use getentropy(2) because getrandom(2) doesn't exist. +use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) }; + if ret != 0 { + return Err(last_os_error()); + } + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/getrandom.rs b/userland/upstream-src/getrandom-0.2.17/src/getrandom.rs new file mode 100644 index 0000000000..bc58365309 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/getrandom.rs @@ -0,0 +1,25 @@ +//! Implementation using getrandom(2). +//! +//! Available since: +//! - Linux Kernel 3.17, Glibc 2.25, Musl 1.1.20 +//! - Android API level 23 (Marshmallow) +//! - NetBSD 10.0 +//! - FreeBSD 12.0 +//! - illumos since Dec 2018 +//! - DragonFly 5.7 +//! - Hurd Glibc 2.31 +//! - shim-3ds since Feb 2022 +//! +//! For these platforms, we always use the default pool and never set the +//! GRND_RANDOM flag to use the /dev/random pool. On Linux/Android/Hurd, using +//! GRND_RANDOM is not recommended. On NetBSD/FreeBSD/Dragonfly/3ds, it does +//! nothing. On illumos, the default pool is used to implement getentropy(2), +//! so we assume it is acceptable here. +use crate::{util_libc::sys_fill_exact, Error}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + sys_fill_exact(dest, |buf| unsafe { + libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) + }) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/hermit.rs b/userland/upstream-src/getrandom-0.2.17/src/hermit.rs new file mode 100644 index 0000000000..c4f619417e --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/hermit.rs @@ -0,0 +1,29 @@ +//! Implementation for Hermit +use crate::Error; +use core::{mem::MaybeUninit, num::NonZeroU32}; + +/// Minimum return value which we should get from syscalls in practice, +/// because Hermit uses positive `i32`s for error codes: +/// https://github.com/hermitcore/libhermit-rs/blob/main/src/errno.rs +const MIN_RET_CODE: isize = -(i32::MAX as isize); + +extern "C" { + fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize; +} + +pub fn getrandom_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !dest.is_empty() { + let res = unsafe { sys_read_entropy(dest.as_mut_ptr() as *mut u8, dest.len(), 0) }; + // Positive `isize`s can be safely casted to `usize` + if res > 0 && (res as usize) <= dest.len() { + dest = &mut dest[res as usize..]; + } else { + let err = match res { + MIN_RET_CODE..=-1 => NonZeroU32::new(-res as u32).unwrap().into(), + _ => Error::UNEXPECTED, + }; + return Err(err); + } + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/js.rs b/userland/upstream-src/getrandom-0.2.17/src/js.rs new file mode 100644 index 0000000000..e5428f50d1 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/js.rs @@ -0,0 +1,155 @@ +//! Implementation for WASM based on Web and Node.js +use crate::Error; + +extern crate std; +use std::{mem::MaybeUninit, thread_local}; + +use js_sys::{global, Function, Uint8Array}; +use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; + +// Size of our temporary Uint8Array buffer used with WebCrypto methods +// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const WEB_CRYPTO_BUFFER_SIZE: usize = 256; +// Node.js's crypto.randomFillSync requires the size to be less than 2**31. +const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; + +enum RngSource { + Node(NodeCrypto), + Web(WebCrypto, Uint8Array), +} + +// JsValues are always per-thread, so we initialize RngSource for each thread. +// See: https://github.com/rustwasm/wasm-bindgen/pull/955 +thread_local!( + static RNG_SOURCE: Result = getrandom_init(); +); + +pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + RNG_SOURCE.with(|result| { + let source = result.as_ref().map_err(|&e| e)?; + + match source { + RngSource::Node(n) => { + for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { + // SAFETY: chunk is never used directly, the memory is only + // modified via the Uint8Array view, which is passed + // directly to JavaScript. Also, crypto.randomFillSync does + // not resize the buffer. We know the length is less than + // u32::MAX because of the chunking above. + // Note that this uses the fact that JavaScript doesn't + // have a notion of "uninitialized memory", this is purely + // a Rust/C/C++ concept. + let res = n.random_fill_sync(unsafe { + Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len()) + }); + if res.is_err() { + return Err(Error::NODE_RANDOM_FILL_SYNC); + } + } + } + RngSource::Web(crypto, buf) => { + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) { + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = buf.subarray(0, chunk.len() as u32); + + if crypto.get_random_values(&sub_buf).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); + } + + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) }; + } + } + }; + Ok(()) + }) +} + +fn getrandom_init() -> Result { + let global: Global = global().unchecked_into(); + + // Get the Web Crypto interface if we are in a browser, Web Worker, Deno, + // or another environment that supports the Web Cryptography API. This + // also allows for user-provided polyfills in unsupported environments. + let crypto = match global.crypto() { + // Standard Web Crypto interface + c if c.is_object() => c, + // Node.js CommonJS Crypto module + _ if is_node(&global) => { + // If module.require isn't a valid function, we are in an ES module. + match Module::require_fn().and_then(JsCast::dyn_into::) { + Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) { + Ok(n) => return Ok(RngSource::Node(n.unchecked_into())), + Err(_) => return Err(Error::NODE_CRYPTO), + }, + Err(_) => return Err(Error::NODE_ES_MODULE), + } + } + // IE 11 Workaround + _ => match global.ms_crypto() { + c if c.is_object() => c, + _ => return Err(Error::WEB_CRYPTO), + }, + }; + + let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32); + Ok(RngSource::Web(crypto, buf)) +} + +// Taken from https://www.npmjs.com/package/browser-or-node +fn is_node(global: &Global) -> bool { + let process = global.process(); + if process.is_object() { + let versions = process.versions(); + if versions.is_object() { + return versions.node().is_string(); + } + } + false +} + +#[wasm_bindgen] +extern "C" { + // Return type of js_sys::global() + type Global; + + // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) + type WebCrypto; + // Getters for the WebCrypto API + #[wasm_bindgen(method, getter)] + fn crypto(this: &Global) -> WebCrypto; + #[wasm_bindgen(method, getter, js_name = msCrypto)] + fn ms_crypto(this: &Global) -> WebCrypto; + // Crypto.getRandomValues() + #[wasm_bindgen(method, js_name = getRandomValues, catch)] + fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; + + // Node JS crypto module (https://nodejs.org/api/crypto.html) + type NodeCrypto; + // crypto.randomFillSync() + #[wasm_bindgen(method, js_name = randomFillSync, catch)] + fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>; + + // Ideally, we would just use `fn require(s: &str)` here. However, doing + // this causes a Webpack warning. So we instead return the function itself + // and manually invoke it using call1. This also lets us to check that the + // function actually exists, allowing for better error messages. See: + // https://github.com/rust-random/getrandom/issues/224 + // https://github.com/rust-random/getrandom/issues/256 + type Module; + #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)] + fn require_fn() -> Result; + + // Node JS process Object (https://nodejs.org/api/process.html) + #[wasm_bindgen(method, getter)] + fn process(this: &Global) -> Process; + type Process; + #[wasm_bindgen(method, getter)] + fn versions(this: &Process) -> Versions; + type Versions; + #[wasm_bindgen(method, getter)] + fn node(this: &Versions) -> JsValue; +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/lazy.rs b/userland/upstream-src/getrandom-0.2.17/src/lazy.rs new file mode 100644 index 0000000000..100ce1eaf5 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/lazy.rs @@ -0,0 +1,56 @@ +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +// This structure represents a lazily initialized static usize value. Useful +// when it is preferable to just rerun initialization instead of locking. +// unsync_init will invoke an init() function until it succeeds, then return the +// cached value for future calls. +// +// unsync_init supports init() "failing". If the init() method returns UNINIT, +// that value will be returned as normal, but will not be cached. +// +// Users should only depend on the _value_ returned by init() functions. +// Specifically, for the following init() function: +// fn init() -> usize { +// a(); +// let v = b(); +// c(); +// v +// } +// the effects of c() or writes to shared memory will not necessarily be +// observed and additional synchronization methods may be needed. +pub(crate) struct LazyUsize(AtomicUsize); + +impl LazyUsize { + pub const fn new() -> Self { + Self(AtomicUsize::new(Self::UNINIT)) + } + + // The initialization is not completed. + pub const UNINIT: usize = usize::max_value(); + + // Runs the init() function at most once, returning the value of some run of + // init(). Multiple callers can run their init() functions in parallel. + // init() should always return the same value, if it succeeds. + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + // Relaxed ordering is fine, as we only have a single atomic variable. + let mut val = self.0.load(Relaxed); + if val == Self::UNINIT { + val = init(); + self.0.store(val, Relaxed); + } + val + } +} + +// Identical to LazyUsize except with bool instead of usize. +pub(crate) struct LazyBool(LazyUsize); + +impl LazyBool { + pub const fn new() -> Self { + Self(LazyUsize::new()) + } + + pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { + self.0.unsync_init(|| init() as usize) != 0 + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/lib.rs b/userland/upstream-src/getrandom-0.2.17/src/lib.rs new file mode 100644 index 0000000000..8844ab35e2 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/lib.rs @@ -0,0 +1,409 @@ +//! Interface to the operating system's random number generator. +//! +//! # Supported targets +//! +//! | Target | Target Triple | Implementation +//! | ----------------- | ------------------ | -------------- +//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` +//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`] +//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] +//! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`] +//! | FreeBSD | `*‑freebsd` | [`getrandom`][5] +//! | OpenBSD | `*‑openbsd` | [`getentropy`][7] +//! | NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] +//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] +//! | Solaris | `*‑solaris` | [`getrandom`][11] (with `GRND_RANDOM`) +//! | illumos | `*‑illumos` | [`getrandom`][12] +//! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] +//! | Redox | `*‑redox` | `/dev/urandom` +//! | Haiku | `*‑haiku` | `/dev/urandom` (identical to `/dev/random`) +//! | Hermit | `*-hermit` | [`sys_read_entropy`] +//! | Hurd | `*-hurd-*` | [`getrandom`][17] +//! | SGX | `x86_64‑*‑sgx` | [`RDRAND`] +//! | VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` +//! | ESP-IDF | `*‑espidf` | [`esp_fill_random`] +//! | Emscripten | `*‑emscripten` | [`getentropy`][13] +//! | WASI | `wasm32‑wasi` | [`random_get`] +//! | Web Browser and Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js, see [WebAssembly support] +//! | SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes` +//! | Nintendo 3DS | `*-nintendo-3ds` | [`getrandom`][18] +//! | PS Vita | `*-vita-*` | [`getentropy`][13] +//! | QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) +//! | AIX | `*-ibm-aix` | [`/dev/urandom`][15] +//! | Cygwin | `*-cygwin` | [`getrandom`][19] (based on [`RtlGenRandom`]) +//! +//! Pull Requests that add support for new targets to `getrandom` are always welcome. +//! +//! ## Unsupported targets +//! +//! By default, `getrandom` will not compile on unsupported targets, but certain +//! features allow a user to select a "fallback" implementation if no supported +//! implementation exists. +//! +//! All of the below mechanisms only affect unsupported +//! targets. Supported targets will _always_ use their supported implementations. +//! This prevents a crate from overriding a secure source of randomness +//! (either accidentally or intentionally). +//! +//! ## `/dev/urandom` fallback on Linux and Android +//! +//! On Linux targets the fallback is present only if either `target_env` is `musl`, +//! or `target_arch` is one of the following: `aarch64`, `arm`, `powerpc`, `powerpc64`, +//! `s390x`, `x86`, `x86_64`. Other supported targets [require][platform-support] +//! kernel versions which support `getrandom` system call, so fallback is not needed. +//! +//! On Android targets the fallback is present only for the following `target_arch`es: +//! `aarch64`, `arm`, `x86`, `x86_64`. Other `target_arch`es (e.g. RISC-V) require +//! sufficiently high API levels. +//! +//! The fallback can be disabled by enabling the `linux_disable_fallback` crate feature. +//! Note that doing so will bump minimum supported Linux kernel version to 3.17 and +//! Android API level to 23 (Marshmallow). +//! +//! ### RDRAND on x86 +//! +//! *If the `rdrand` Cargo feature is enabled*, `getrandom` will fallback to using +//! the [`RDRAND`] instruction to get randomness on `no_std` `x86`/`x86_64` +//! targets. This feature has no effect on other CPU architectures. +//! +//! ### WebAssembly support +//! +//! This crate fully supports the +//! [`wasm32-wasi`](https://github.com/CraneStation/wasi) and +//! [`wasm32-unknown-emscripten`](https://www.hellorust.com/setup/emscripten/) +//! targets. However, the `wasm32-unknown-unknown` target (i.e. the target used +//! by `wasm-pack`) is not automatically +//! supported since, from the target name alone, we cannot deduce which +//! JavaScript interface is in use (or if JavaScript is available at all). +//! +//! Instead, *if the `js` Cargo feature is enabled*, this crate will assume +//! that you are building for an environment containing JavaScript, and will +//! call the appropriate methods. Both web browser (main window and Web Workers) +//! and Node.js environments are supported, invoking the methods +//! [described above](#supported-targets) using the [`wasm-bindgen`] toolchain. +//! +//! To enable the `js` Cargo feature, add the following to the `dependencies` +//! section in your `Cargo.toml` file: +//! ```toml +//! [dependencies] +//! getrandom = { version = "0.2", features = ["js"] } +//! ``` +//! +//! This can be done even if `getrandom` is not a direct dependency. Cargo +//! allows crates to enable features for indirect dependencies. +//! +//! This feature should only be enabled for binary, test, or benchmark crates. +//! Library crates should generally not enable this feature, leaving such a +//! decision to *users* of their library. Also, libraries should not introduce +//! their own `js` features *just* to enable `getrandom`'s `js` feature. +//! +//! This feature has no effect on targets other than `wasm32-unknown-unknown`. +//! +//! #### Node.js ES module support +//! +//! Node.js supports both [CommonJS modules] and [ES modules]. Due to +//! limitations in wasm-bindgen's [`module`] support, we cannot directly +//! support ES Modules running on Node.js. However, on Node v15 and later, the +//! module author can add a simple shim to support the Web Cryptography API: +//! ```js +//! import { webcrypto } from 'node:crypto' +//! globalThis.crypto = webcrypto +//! ``` +//! This crate will then use the provided `webcrypto` implementation. +//! +//! ### Platform Support +//! This crate generally supports the same operating system and platform versions +//! that the Rust standard library does. Additional targets may be supported using +//! pluggable custom implementations. +//! +//! This means that as Rust drops support for old versions of operating systems +//! (such as old Linux kernel versions, Android API levels, etc) in stable releases, +//! `getrandom` may create new patch releases (`0.N.x`) that remove support for +//! outdated platform versions. +//! +//! ### Custom implementations +//! +//! The [`register_custom_getrandom!`] macro allows a user to mark their own +//! function as the backing implementation for [`getrandom`]. See the macro's +//! documentation for more information about writing and registering your own +//! custom implementations. +//! +//! Note that registering a custom implementation only has an effect on targets +//! that would otherwise not compile. Any supported targets (including those +//! using `rdrand` and `js` Cargo features) continue using their normal +//! implementations even if a function is registered. +//! +//! ## Early boot +//! +//! Sometimes, early in the boot process, the OS has not collected enough +//! entropy to securely seed its RNG. This is especially common on virtual +//! machines, where standard "random" events are hard to come by. +//! +//! Some operating system interfaces always block until the RNG is securely +//! seeded. This can take anywhere from a few seconds to more than a minute. +//! A few (Linux, NetBSD and Solaris) offer a choice between blocking and +//! getting an error; in these cases, we always choose to block. +//! +//! On Linux (when the `getrandom` system call is not available), reading from +//! `/dev/urandom` never blocks, even when the OS hasn't collected enough +//! entropy yet. To avoid returning low-entropy bytes, we first poll +//! `/dev/random` and only switch to `/dev/urandom` once this has succeeded. +//! +//! On OpenBSD, this kind of entropy accounting isn't available, and on +//! NetBSD, blocking on it is discouraged. On these platforms, nonblocking +//! interfaces are used, even when reliable entropy may not be available. +//! On the platforms where it is used, the reliability of entropy accounting +//! itself isn't free from controversy. This library provides randomness +//! sourced according to the platform's best practices, but each platform has +//! its own limits on the grade of randomness it can promise in environments +//! with few sources of entropy. +//! +//! ## Error handling +//! +//! We always choose failure over returning known insecure "random" bytes. In +//! general, on supported platforms, failure is highly unlikely, though not +//! impossible. If an error does occur, then it is likely that it will occur +//! on every call to `getrandom`, hence after the first successful call one +//! can be reasonably confident that no errors will occur. +//! +//! [1]: https://manned.org/getrandom.2 +//! [2]: https://manned.org/urandom.4 +//! [3]: https://www.unix.com/man-page/mojave/2/getentropy/ +//! [4]: https://www.unix.com/man-page/mojave/4/urandom/ +//! [5]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable +//! [7]: https://man.openbsd.org/getentropy.2 +//! [8]: https://man.netbsd.org/sysctl.7 +//! [9]: https://leaf.dragonflybsd.org/cgi/web-man?command=getrandom +//! [11]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html +//! [12]: https://illumos.org/man/2/getrandom +//! [13]: https://github.com/emscripten-core/emscripten/pull/12240 +//! [14]: https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.utilities/topic/r/random.html +//! [15]: https://www.ibm.com/docs/en/aix/7.3?topic=files-random-urandom-devices +//! [16]: https://man.netbsd.org/getrandom.2 +//! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom +//! [18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d +//! [19]: https://github.com/cygwin/cygwin/blob/main/winsup/cygwin/libc/getentropy.cc +//! +//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom +//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom +//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues +//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide +//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html +//! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw +//! [`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size +//! [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t +//! [`random_get`]: https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno +//! [WebAssembly support]: #webassembly-support +//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen +//! [`module`]: https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/module.html +//! [CommonJS modules]: https://nodejs.org/api/modules.html +//! [ES modules]: https://nodejs.org/api/esm.html +//! [`sys_read_entropy`]: https://github.com/hermit-os/kernel/blob/315f58ff5efc81d9bf0618af85a59963ff55f8b1/src/syscalls/entropy.rs#L47-L55 +//! [platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html + +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://docs.rs/getrandom/0.2.17" +)] +#![no_std] +#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[macro_use] +extern crate cfg_if; + +use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; +use core::mem::MaybeUninit; + +mod error; +mod util; +// To prevent a breaking change when targets are added, we always export the +// register_custom_getrandom macro, so old Custom RNG crates continue to build. +#[cfg(feature = "custom")] +mod custom; +#[cfg(feature = "std")] +mod error_impls; + +pub use crate::error::Error; + +// System-specific implementations. +// +// These should all provide getrandom_inner with the signature +// `fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error>`. +// The function MUST fully initialize `dest` when `Ok(())` is returned. +// The function MUST NOT ever write uninitialized bytes into `dest`, +// regardless of what value it returns. +cfg_if! { + if #[cfg(any(target_os = "haiku", target_os = "redox", target_os = "nto", target_os = "aix"))] { + mod util_libc; + #[path = "use_file.rs"] mod imp; + } else if #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "vita", + target_os = "emscripten", + ))] { + mod util_libc; + #[path = "getentropy.rs"] mod imp; + } else if #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "hurd", + target_os = "illumos", + // Check for target_arch = "arm" to only include the 3DS. Does not + // include the Nintendo Switch (which is target_arch = "aarch64"). + all(target_os = "horizon", target_arch = "arm"), + target_os = "cygwin", + ))] { + mod util_libc; + #[path = "getrandom.rs"] mod imp; + } else if #[cfg(all( + not(feature = "linux_disable_fallback"), + any( + // Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets + // level 21 (Lollipop) [1], while `getrandom(2)` was added only in + // level 23 (Marshmallow). Note that it applies only to the "old" `target_arch`es, + // RISC-V Android targets sufficiently new API level, same will apply for potential + // new Android `target_arch`es. + // [0]: https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html + // [1]: https://github.com/rust-lang/rust/pull/120593 + all( + target_os = "android", + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "x86", + target_arch = "x86_64", + ), + ), + // Only on these `target_arch`es Rust supports Linux kernel versions (3.2+) + // that precede the version (3.17) in which `getrandom(2)` was added: + // https://doc.rust-lang.org/stable/rustc/platform-support.html + all( + target_os = "linux", + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + // Minimum supported Linux kernel version for MUSL targets + // is not specified explicitly (as of Rust 1.77) and they + // are used in practice to target pre-3.17 kernels. + target_env = "musl", + ), + ) + ), + ))] { + mod util_libc; + mod use_file; + mod lazy; + #[path = "linux_android_with_fallback.rs"] mod imp; + } else if #[cfg(any(target_os = "android", target_os = "linux"))] { + mod util_libc; + #[path = "linux_android.rs"] mod imp; + } else if #[cfg(target_os = "solaris")] { + mod util_libc; + #[path = "solaris.rs"] mod imp; + } else if #[cfg(target_os = "netbsd")] { + mod util_libc; + #[path = "netbsd.rs"] mod imp; + } else if #[cfg(target_os = "fuchsia")] { + #[path = "fuchsia.rs"] mod imp; + } else if #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))] { + #[path = "apple-other.rs"] mod imp; + } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { + #[path = "wasi.rs"] mod imp; + } else if #[cfg(target_os = "hermit")] { + #[path = "hermit.rs"] mod imp; + } else if #[cfg(target_os = "vxworks")] { + mod util_libc; + #[path = "vxworks.rs"] mod imp; + } else if #[cfg(target_os = "solid_asp3")] { + #[path = "solid.rs"] mod imp; + } else if #[cfg(target_os = "espidf")] { + #[path = "espidf.rs"] mod imp; + } else if #[cfg(windows)] { + #[path = "windows.rs"] mod imp; + } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { + mod lazy; + #[path = "rdrand.rs"] mod imp; + } else if #[cfg(all(feature = "rdrand", + any(target_arch = "x86_64", target_arch = "x86")))] { + mod lazy; + #[path = "rdrand.rs"] mod imp; + } else if #[cfg(all(feature = "js", + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown"))] { + #[path = "js.rs"] mod imp; + } else if #[cfg(target_vendor = "nonos")] { + #[path = "nonos.rs"] mod imp; + } else if #[cfg(feature = "custom")] { + use custom as imp; + } else if #[cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown"))] { + compile_error!("the wasm*-unknown-unknown targets are not supported by \ + default, you may need to enable the \"js\" feature. \ + For more information see: \ + https://docs.rs/getrandom/#webassembly-support"); + } else { + compile_error!("target is not supported, for more information see: \ + https://docs.rs/getrandom/#unsupported-targets"); + } +} + +/// Fill `dest` with random bytes from the system's preferred random number +/// source. +/// +/// This function returns an error on any failure, including partial reads. We +/// make no guarantees regarding the contents of `dest` on error. If `dest` is +/// empty, `getrandom` immediately returns success, making no calls to the +/// underlying operating system. +/// +/// Blocking is possible, at least during early boot; see module documentation. +/// +/// In general, `getrandom` will be fast enough for interactive usage, though +/// significantly slower than a user-space CSPRNG; for the latter consider +/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). +#[inline] +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and + // `getrandom_uninit` guarantees it will never de-initialize any part of + // `dest`. + getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?; + Ok(()) +} + +/// Version of the `getrandom` function which fills `dest` with random bytes +/// returns a mutable reference to those bytes. +/// +/// On successful completion this function is guaranteed to return a slice +/// which points to the same memory as `dest` and has the same length. +/// In other words, it's safe to assume that `dest` is initialized after +/// this function has returned `Ok`. +/// +/// No part of `dest` will ever be de-initialized at any point, regardless +/// of what is returned. +/// +/// # Examples +/// +/// ```ignore +/// # // We ignore this test since `uninit_array` is unstable. +/// #![feature(maybe_uninit_uninit_array)] +/// # fn main() -> Result<(), getrandom::Error> { +/// let mut buf = core::mem::MaybeUninit::uninit_array::<1024>(); +/// let buf: &mut [u8] = getrandom::getrandom_uninit(&mut buf)?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { + if !dest.is_empty() { + imp::getrandom_inner(dest)?; + } + // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` + // since it returned `Ok`. + Ok(unsafe { slice_assume_init_mut(dest) }) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/linux_android.rs b/userland/upstream-src/getrandom-0.2.17/src/linux_android.rs new file mode 100644 index 0000000000..93a649452f --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/linux_android.rs @@ -0,0 +1,7 @@ +//! Implementation for Linux / Android without `/dev/urandom` fallback +use crate::{util_libc, Error}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/linux_android_with_fallback.rs b/userland/upstream-src/getrandom-0.2.17/src/linux_android_with_fallback.rs new file mode 100644 index 0000000000..0f5ea8a992 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/linux_android_with_fallback.rs @@ -0,0 +1,33 @@ +//! Implementation for Linux / Android with `/dev/urandom` fallback +use crate::{ + lazy::LazyBool, + util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}, + {use_file, Error}, +}; +use core::mem::MaybeUninit; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // getrandom(2) was introduced in Linux 3.17 + static HAS_GETRANDOM: LazyBool = LazyBool::new(); + if HAS_GETRANDOM.unsync_init(is_getrandom_available) { + sys_fill_exact(dest, getrandom_syscall) + } else { + use_file::getrandom_inner(dest) + } +} + +fn is_getrandom_available() -> bool { + if getrandom_syscall(&mut []) < 0 { + match last_os_error().raw_os_error() { + Some(libc::ENOSYS) => false, // No kernel support + // The fallback on EPERM is intentionally not done on Android since this workaround + // seems to be needed only for specific Linux-based products that aren't based + // on Android. See https://github.com/rust-random/getrandom/issues/229. + #[cfg(target_os = "linux")] + Some(libc::EPERM) => false, // Blocked by seccomp + _ => true, + } + } else { + true + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/netbsd.rs b/userland/upstream-src/getrandom-0.2.17/src/netbsd.rs new file mode 100644 index 0000000000..b8a770f5d6 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/netbsd.rs @@ -0,0 +1,46 @@ +//! Implementation for NetBSD +use crate::{ + util_libc::{sys_fill_exact, Weak}, + Error, +}; +use core::{mem::MaybeUninit, ptr}; + +fn kern_arnd(buf: &mut [MaybeUninit]) -> libc::ssize_t { + static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; + let mut len = buf.len(); + let ret = unsafe { + libc::sysctl( + MIB.as_ptr(), + MIB.len() as libc::c_uint, + buf.as_mut_ptr() as *mut _, + &mut len, + ptr::null(), + 0, + ) + }; + if ret == -1 { + -1 + } else { + len as libc::ssize_t + } +} + +type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // getrandom(2) was introduced in NetBSD 10.0 + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; + return sys_fill_exact(dest, |buf| unsafe { + func(buf.as_mut_ptr() as *mut u8, buf.len(), 0) + }); + } + + // NetBSD will only return up to 256 bytes at a time, and + // older NetBSD kernels will fail on longer buffers. + for chunk in dest.chunks_mut(256) { + sys_fill_exact(chunk, kern_arnd)? + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/nonos.rs b/userland/upstream-src/getrandom-0.2.17/src/nonos.rs new file mode 100644 index 0000000000..33449858fa --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/nonos.rs @@ -0,0 +1,32 @@ +// NONOS backend for getrandom. The kernel exposes a CSPRNG through the CRND +// syscall (rax = the tag "CRND", rdi = buffer pointer, rsi = length), the same +// source the std platform layer draws from, so a userspace capsule fills a +// buffer with one call and no OS-specific dependency. + +use crate::Error; +use core::mem::MaybeUninit; + +// The CRND syscall number, "CRND" packed little-endian into the low 32 bits. +const N_CRYPTO_RANDOM: i64 = { + let b = *b"CRND"; + (b[0] as i64) | ((b[1] as i64) << 8) | ((b[2] as i64) << 16) | ((b[3] as i64) << 24) +}; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if dest.is_empty() { + return Ok(()); + } + // SAFETY: the kernel writes exactly `len` bytes into `ptr`, clobbers only + // the syscall scratch registers, and the buffer is valid for that length. + unsafe { + core::arch::asm!( + "syscall", + inout("rax") N_CRYPTO_RANDOM => _, + in("rdi") dest.as_mut_ptr() as u64, + in("rsi") dest.len() as u64, + out("rcx") _, + out("r11") _, + ); + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/rdrand.rs b/userland/upstream-src/getrandom-0.2.17/src/rdrand.rs new file mode 100644 index 0000000000..f527c8c643 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/rdrand.rs @@ -0,0 +1,121 @@ +//! RDRAND backend for x86(-64) targets +use crate::{lazy::LazyBool, util::slice_as_uninit, Error}; +use core::mem::{size_of, MaybeUninit}; + +cfg_if! { + if #[cfg(target_arch = "x86_64")] { + use core::arch::x86_64 as arch; + use arch::_rdrand64_step as rdrand_step; + } else if #[cfg(target_arch = "x86")] { + use core::arch::x86 as arch; + use arch::_rdrand32_step as rdrand_step; + } +} + +// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software +// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures +// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. +const RETRY_LIMIT: usize = 10; + +#[target_feature(enable = "rdrand")] +unsafe fn rdrand() -> Option { + for _ in 0..RETRY_LIMIT { + let mut val = 0; + if rdrand_step(&mut val) == 1 { + return Some(val as usize); + } + } + None +} + +// "rdrand" target feature requires "+rdrand" flag, see https://github.com/rust-lang/rust/issues/49653. +#[cfg(all(target_env = "sgx", not(target_feature = "rdrand")))] +compile_error!( + "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrand." +); + +// Run a small self-test to make sure we aren't repeating values +// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c +// Fails with probability < 2^(-90) on 32-bit systems +#[target_feature(enable = "rdrand")] +unsafe fn self_test() -> bool { + // On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision. + let mut prev = !0; // TODO(MSRV 1.43): Move to usize::MAX + let mut fails = 0; + for _ in 0..8 { + match rdrand() { + Some(val) if val == prev => fails += 1, + Some(val) => prev = val, + None => return false, + }; + } + fails <= 2 +} + +fn is_rdrand_good() -> bool { + #[cfg(not(target_feature = "rdrand"))] + { + // SAFETY: All Rust x86 targets are new enough to have CPUID, and we + // check that leaf 1 is supported before using it. + let cpuid0 = unsafe { arch::__cpuid(0) }; + if cpuid0.eax < 1 { + return false; + } + let cpuid1 = unsafe { arch::__cpuid(1) }; + + let vendor_id = [ + cpuid0.ebx.to_le_bytes(), + cpuid0.edx.to_le_bytes(), + cpuid0.ecx.to_le_bytes(), + ]; + if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] { + let mut family = (cpuid1.eax >> 8) & 0xF; + if family == 0xF { + family += (cpuid1.eax >> 20) & 0xFF; + } + // AMD CPUs families before 17h (Zen) sometimes fail to set CF when + // RDRAND fails after suspend. Don't use RDRAND on those families. + // See https://bugzilla.redhat.com/show_bug.cgi?id=1150286 + if family < 0x17 { + return false; + } + } + + const RDRAND_FLAG: u32 = 1 << 30; + if cpuid1.ecx & RDRAND_FLAG == 0 { + return false; + } + } + + // SAFETY: We have already checked that rdrand is available. + unsafe { self_test() } +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + static RDRAND_GOOD: LazyBool = LazyBool::new(); + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) +} + +// TODO: make this function safe when we have feature(target_feature_11) +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { + // We use chunks_exact_mut instead of chunks_mut as it allows almost all + // calls to memcpy to be elided by the compiler. + let mut chunks = dest.chunks_exact_mut(size_of::()); + for chunk in chunks.by_ref() { + let src = rdrand()?.to_ne_bytes(); + chunk.copy_from_slice(slice_as_uninit(&src)); + } + + let tail = chunks.into_remainder(); + let n = tail.len(); + if n > 0 { + let src = rdrand()?.to_ne_bytes(); + tail.copy_from_slice(slice_as_uninit(&src[..n])); + } + Some(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/solaris.rs b/userland/upstream-src/getrandom-0.2.17/src/solaris.rs new file mode 100644 index 0000000000..8a3401e0f6 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/solaris.rs @@ -0,0 +1,34 @@ +//! Solaris implementation using getrandom(2). +//! +//! While getrandom(2) has been available since Solaris 11.3, it has a few +//! quirks not present on other OSes. First, on Solaris 11.3, calls will always +//! fail if bufsz > 1024. Second, it will always either fail or completely fill +//! the buffer (returning bufsz). Third, error is indicated by returning 0, +//! rather than by returning -1. Finally, "if GRND_RANDOM is not specified +//! then getrandom(2) is always a non blocking call". This _might_ imply that +//! in early-boot scenarios with low entropy, getrandom(2) will not properly +//! block. To be safe, we set GRND_RANDOM, mirroring the man page examples. +//! +//! For more information, see the man page linked in lib.rs and this blog post: +//! https://blogs.oracle.com/solaris/post/solaris-new-system-calls-getentropy2-and-getrandom2 +//! which also explains why this crate should not use getentropy(2). +use crate::{util_libc::last_os_error, Error}; +use core::mem::MaybeUninit; + +const MAX_BYTES: usize = 1024; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BYTES) { + let ptr = chunk.as_mut_ptr() as *mut libc::c_void; + let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; + // In case the man page has a typo, we also check for negative ret. + if ret <= 0 { + return Err(last_os_error()); + } + // If getrandom(2) succeeds, it should have completely filled chunk. + if (ret as usize) != chunk.len() { + return Err(Error::UNEXPECTED); + } + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/solid.rs b/userland/upstream-src/getrandom-0.2.17/src/solid.rs new file mode 100644 index 0000000000..cae8caf667 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/solid.rs @@ -0,0 +1,18 @@ +//! Implementation for SOLID +use crate::Error; +use core::{mem::MaybeUninit, num::NonZeroU32}; + +extern "C" { + pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr() as *mut u8, dest.len()) }; + if ret >= 0 { + Ok(()) + } else { + // ITRON error numbers are always negative, so we negate it so that it + // falls in the dedicated OS error range (1..INTERNAL_START). + Err(NonZeroU32::new((-ret) as u32).unwrap().into()) + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/use_file.rs b/userland/upstream-src/getrandom-0.2.17/src/use_file.rs new file mode 100644 index 0000000000..bd643ae5aa --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/use_file.rs @@ -0,0 +1,120 @@ +//! Implementations that just need to read from a file +use crate::{ + util_libc::{open_readonly, sys_fill_exact}, + Error, +}; +use core::{ + cell::UnsafeCell, + mem::MaybeUninit, + sync::atomic::{AtomicUsize, Ordering::Relaxed}, +}; + +/// For all platforms, we use `/dev/urandom` rather than `/dev/random`. +/// For more information see the linked man pages in lib.rs. +/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". +/// - On Redox, only /dev/urandom is provided. +/// - On AIX, /dev/urandom will "provide cryptographically secure output". +/// - On Haiku and QNX Neutrino they are identical. +const FILE_PATH: &str = "/dev/urandom\0"; +const FD_UNINIT: usize = usize::max_value(); + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let fd = get_rng_fd()?; + sys_fill_exact(dest, |buf| unsafe { + libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) + }) +} + +// Returns the file descriptor for the device file used to retrieve random +// bytes. The file will be opened exactly once. All subsequent calls will +// return the same file descriptor. This file descriptor is never closed. +fn get_rng_fd() -> Result { + static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT); + fn get_fd() -> Option { + match FD.load(Relaxed) { + FD_UNINIT => None, + val => Some(val as libc::c_int), + } + } + + // Use double-checked locking to avoid acquiring the lock if possible. + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // SAFETY: We use the mutex only in this method, and we always unlock it + // before returning, making sure we don't violate the pthread_mutex_t API. + static MUTEX: Mutex = Mutex::new(); + unsafe { MUTEX.lock() }; + let _guard = DropGuard(|| unsafe { MUTEX.unlock() }); + + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // On Linux, /dev/urandom might return insecure values. + #[cfg(any(target_os = "android", target_os = "linux"))] + wait_until_rng_ready()?; + + let fd = unsafe { open_readonly(FILE_PATH)? }; + // The fd always fits in a usize without conflicting with FD_UNINIT. + debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); + FD.store(fd as usize, Relaxed); + + Ok(fd) +} + +// Succeeds once /dev/urandom is safe to read from +#[cfg(any(target_os = "android", target_os = "linux"))] +fn wait_until_rng_ready() -> Result<(), Error> { + // Poll /dev/random to make sure it is ok to read from /dev/urandom. + let fd = unsafe { open_readonly("/dev/random\0")? }; + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let _guard = DropGuard(|| unsafe { + libc::close(fd); + }); + + loop { + // A negative timeout means an infinite timeout. + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res >= 0 { + debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout. + return Ok(()); + } + let err = crate::util_libc::last_os_error(); + match err.raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => continue, + _ => return Err(err), + } + } +} + +struct Mutex(UnsafeCell); + +impl Mutex { + const fn new() -> Self { + Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) + } + unsafe fn lock(&self) { + let r = libc::pthread_mutex_lock(self.0.get()); + debug_assert_eq!(r, 0); + } + unsafe fn unlock(&self) { + let r = libc::pthread_mutex_unlock(self.0.get()); + debug_assert_eq!(r, 0); + } +} + +unsafe impl Sync for Mutex {} + +struct DropGuard(F); + +impl Drop for DropGuard { + fn drop(&mut self) { + self.0() + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/util.rs b/userland/upstream-src/getrandom-0.2.17/src/util.rs new file mode 100644 index 0000000000..1c4e70ba4e --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/util.rs @@ -0,0 +1,35 @@ +#![allow(dead_code)] +use core::{mem::MaybeUninit, ptr}; + +/// Polyfill for `maybe_uninit_slice` feature's +/// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have +/// been initialized. +#[inline(always)] +pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + &mut *(slice as *mut [MaybeUninit] as *mut [T]) +} + +#[inline] +pub fn uninit_slice_fill_zero(slice: &mut [MaybeUninit]) -> &mut [u8] { + unsafe { ptr::write_bytes(slice.as_mut_ptr(), 0, slice.len()) }; + unsafe { slice_assume_init_mut(slice) } +} + +#[inline(always)] +pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + // There is no risk of writing a `MaybeUninit` into the result since + // the result isn't mutable. + unsafe { &*(slice as *const [T] as *const [MaybeUninit]) } +} + +/// View an mutable initialized array as potentially-uninitialized. +/// +/// This is unsafe because it allows assigning uninitialized values into +/// `slice`, which would be undefined behavior. +#[inline(always)] +pub unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. + &mut *(slice as *mut [T] as *mut [MaybeUninit]) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/util_libc.rs b/userland/upstream-src/getrandom-0.2.17/src/util_libc.rs new file mode 100644 index 0000000000..f488b41561 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/util_libc.rs @@ -0,0 +1,162 @@ +#![allow(dead_code)] +use crate::Error; +use core::{ + mem::MaybeUninit, + num::NonZeroU32, + ptr::NonNull, + sync::atomic::{fence, AtomicPtr, Ordering}, +}; +use libc::c_void; + +cfg_if! { + if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android", target_os = "cygwin"))] { + use libc::__errno as errno_location; + } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "hurd", target_os = "redox", target_os = "dragonfly"))] { + use libc::__errno_location as errno_location; + } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { + use libc::___errno as errno_location; + } else if #[cfg(any(target_os = "macos", target_os = "freebsd"))] { + use libc::__error as errno_location; + } else if #[cfg(target_os = "haiku")] { + use libc::_errnop as errno_location; + } else if #[cfg(target_os = "nto")] { + use libc::__get_errno_ptr as errno_location; + } else if #[cfg(any(all(target_os = "horizon", target_arch = "arm"), target_os = "vita"))] { + extern "C" { + // Not provided by libc: https://github.com/rust-lang/libc/issues/1995 + fn __errno() -> *mut libc::c_int; + } + use __errno as errno_location; + } else if #[cfg(target_os = "aix")] { + use libc::_Errno as errno_location; + } +} + +cfg_if! { + if #[cfg(target_os = "vxworks")] { + use libc::errnoGet as get_errno; + } else { + unsafe fn get_errno() -> libc::c_int { *errno_location() } + } +} + +pub fn last_os_error() -> Error { + let errno = unsafe { get_errno() }; + if errno > 0 { + Error::from(NonZeroU32::new(errno as u32).unwrap()) + } else { + Error::ERRNO_NOT_POSITIVE + } +} + +// Fill a buffer by repeatedly invoking a system call. The `sys_fill` function: +// - should return -1 and set errno on failure +// - should return the number of bytes written on success +pub fn sys_fill_exact( + mut buf: &mut [MaybeUninit], + sys_fill: impl Fn(&mut [MaybeUninit]) -> libc::ssize_t, +) -> Result<(), Error> { + while !buf.is_empty() { + let res = sys_fill(buf); + match res { + res if res > 0 => buf = buf.get_mut(res as usize..).ok_or(Error::UNEXPECTED)?, + -1 => { + let err = last_os_error(); + // We should try again if the call was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } + } + // Negative return codes not equal to -1 should be impossible. + // EOF (ret = 0) should be impossible, as the data we are reading + // should be an infinite stream of random bytes. + _ => return Err(Error::UNEXPECTED), + } + } + Ok(()) +} + +// A "weak" binding to a C function that may or may not be present at runtime. +// Used for supporting newer OS features while still building on older systems. +// Based off of the DlsymWeak struct in libstd: +// https://github.com/rust-lang/rust/blob/1.61.0/library/std/src/sys/unix/weak.rs#L84 +// except that the caller must manually cast self.ptr() to a function pointer. +pub struct Weak { + name: &'static str, + addr: AtomicPtr, +} + +impl Weak { + // A non-null pointer value which indicates we are uninitialized. This + // constant should ideally not be a valid address of a function pointer. + // However, if by chance libc::dlsym does return UNINIT, there will not + // be undefined behavior. libc::dlsym will just be called each time ptr() + // is called. This would be inefficient, but correct. + // TODO: Replace with core::ptr::invalid_mut(1) when that is stable. + const UNINIT: *mut c_void = 1 as *mut c_void; + + // Construct a binding to a C function with a given name. This function is + // unsafe because `name` _must_ be null terminated. + pub const unsafe fn new(name: &'static str) -> Self { + Self { + name, + addr: AtomicPtr::new(Self::UNINIT), + } + } + + // Return the address of a function if present at runtime. Otherwise, + // return None. Multiple callers can call ptr() concurrently. It will + // always return _some_ value returned by libc::dlsym. However, the + // dlsym function may be called multiple times. + pub fn ptr(&self) -> Option> { + // Despite having only a single atomic variable (self.addr), we still + // cannot always use Ordering::Relaxed, as we need to make sure a + // successful call to dlsym() is "ordered before" any data read through + // the returned pointer (which occurs when the function is called). + // Our implementation mirrors that of the one in libstd, meaning that + // the use of non-Relaxed operations is probably unnecessary. + match self.addr.load(Ordering::Relaxed) { + Self::UNINIT => { + let symbol = self.name.as_ptr() as *const _; + let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) }; + // Synchronizes with the Acquire fence below + self.addr.store(addr, Ordering::Release); + NonNull::new(addr) + } + addr => { + let func = NonNull::new(addr)?; + fence(Ordering::Acquire); + Some(func) + } + } + } +} + +// SAFETY: path must be null terminated, FD must be manually closed. +pub unsafe fn open_readonly(path: &str) -> Result { + debug_assert_eq!(path.as_bytes().last(), Some(&0)); + loop { + let fd = libc::open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); + if fd >= 0 { + return Ok(fd); + } + let err = last_os_error(); + // We should try again if open() was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } + } +} + +/// Thin wrapper around the `getrandom()` Linux system call +#[cfg(any(target_os = "android", target_os = "linux"))] +pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> libc::ssize_t { + unsafe { + libc::syscall( + libc::SYS_getrandom, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len(), + 0, + ) as libc::ssize_t + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/vxworks.rs b/userland/upstream-src/getrandom-0.2.17/src/vxworks.rs new file mode 100644 index 0000000000..7ca9d6bfdd --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/vxworks.rs @@ -0,0 +1,29 @@ +//! Implementation for VxWorks +use crate::{util_libc::last_os_error, Error}; +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicBool, Ordering::Relaxed}, +}; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + static RNG_INIT: AtomicBool = AtomicBool::new(false); + while !RNG_INIT.load(Relaxed) { + let ret = unsafe { libc::randSecure() }; + if ret < 0 { + return Err(Error::VXWORKS_RAND_SECURE); + } else if ret > 0 { + RNG_INIT.store(true, Relaxed); + break; + } + unsafe { libc::usleep(10) }; + } + + // Prevent overflow of i32 + for chunk in dest.chunks_mut(i32::max_value() as usize) { + let ret = unsafe { libc::randABytes(chunk.as_mut_ptr() as *mut u8, chunk.len() as i32) }; + if ret != 0 { + return Err(last_os_error()); + } + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/wasi.rs b/userland/upstream-src/getrandom-0.2.17/src/wasi.rs new file mode 100644 index 0000000000..d6c8a912c9 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/wasi.rs @@ -0,0 +1,17 @@ +//! Implementation for WASI +use crate::Error; +use core::{ + mem::MaybeUninit, + num::{NonZeroU16, NonZeroU32}, +}; +use wasi::random_get; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { random_get(dest.as_mut_ptr() as *mut u8, dest.len()) }.map_err(|e| { + // The WASI errno will always be non-zero, but we check just in case. + match NonZeroU16::new(e.raw()) { + Some(r) => Error::from(NonZeroU32::from(r)), + None => Error::ERRNO_NOT_POSITIVE, + } + }) +} diff --git a/userland/upstream-src/getrandom-0.2.17/src/windows.rs b/userland/upstream-src/getrandom-0.2.17/src/windows.rs new file mode 100644 index 0000000000..fa4ba03cf4 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/src/windows.rs @@ -0,0 +1,60 @@ +//! Implementation for Windows +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr}; + +const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; + +#[link(name = "bcrypt")] +extern "system" { + fn BCryptGenRandom( + hAlgorithm: *mut c_void, + pBuffer: *mut u8, + cbBuffer: u32, + dwFlags: u32, + ) -> i32; +} + +// Forbidden when targetting UWP +#[cfg(not(target_vendor = "uwp"))] +#[link(name = "advapi32")] +extern "system" { + #[link_name = "SystemFunction036"] + fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8; +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Prevent overflow of u32 + for chunk in dest.chunks_mut(u32::max_value() as usize) { + // BCryptGenRandom was introduced in Windows Vista + let ret = unsafe { + BCryptGenRandom( + ptr::null_mut(), + chunk.as_mut_ptr() as *mut u8, + chunk.len() as u32, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ) + }; + let ret = ret as u32; + // NTSTATUS codes use the two highest bits for severity status. + if ret >> 30 == 0b11 { + // Failed. Try RtlGenRandom as a fallback. + #[cfg(not(target_vendor = "uwp"))] + { + let ret = + unsafe { RtlGenRandom(chunk.as_mut_ptr() as *mut c_void, chunk.len() as u32) }; + if ret != 0 { + continue; + } + } + // We zeroize the highest bit, so the error code will reside + // inside the range designated for OS codes. + let code = ret ^ (1 << 31); + // SAFETY: the second highest bit is always equal to one, + // so it's impossible to get zero. Unfortunately the type + // system does not have a way to express this yet. + let code = unsafe { NonZeroU32::new_unchecked(code) }; + return Err(Error::from(code)); + } + } + Ok(()) +} diff --git a/userland/upstream-src/getrandom-0.2.17/tests/common/mod.rs b/userland/upstream-src/getrandom-0.2.17/tests/common/mod.rs new file mode 100644 index 0000000000..666f7f5702 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/tests/common/mod.rs @@ -0,0 +1,100 @@ +use super::getrandom_impl; + +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +use wasm_bindgen_test::wasm_bindgen_test as test; + +#[cfg(feature = "test-in-browser")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[test] +fn test_zero() { + // Test that APIs are happy with zero-length requests + getrandom_impl(&mut [0u8; 0]).unwrap(); +} + +// Return the number of bits in which s1 and s2 differ +#[cfg(not(feature = "custom"))] +fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { + assert_eq!(s1.len(), s2.len()); + s1.iter() + .zip(s2.iter()) + .map(|(a, b)| (a ^ b).count_ones() as usize) + .sum() +} + +// Tests the quality of calling getrandom on two large buffers +#[test] +#[cfg(not(feature = "custom"))] +fn test_diff() { + let mut v1 = [0u8; 1000]; + getrandom_impl(&mut v1).unwrap(); + + let mut v2 = [0u8; 1000]; + getrandom_impl(&mut v2).unwrap(); + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d = num_diff_bits(&v1, &v2); + assert!(d > 3500); + assert!(d < 4500); +} + +// Tests the quality of calling getrandom repeatedly on small buffers +#[test] +#[cfg(not(feature = "custom"))] +fn test_small() { + // For each buffer size, get at least 256 bytes and check that between + // 3 and 5 bits per byte differ. Probability of failure: + // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] + for size in 1..=64 { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let mut s1 = vec![0u8; size]; + getrandom_impl(&mut s1).unwrap(); + let mut s2 = vec![0u8; size]; + getrandom_impl(&mut s2).unwrap(); + + num_bytes += size; + diff_bits += num_diff_bits(&s1, &s2); + } + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); + } +} + +#[test] +fn test_huge() { + let mut huge = [0u8; 100_000]; + getrandom_impl(&mut huge).unwrap(); +} + +// On WASM, the thread API always fails/panics +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn test_multithreading() { + extern crate std; + use std::{sync::mpsc::channel, thread, vec}; + + let mut txs = vec![]; + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move || { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + getrandom_impl(&mut v).unwrap(); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +} diff --git a/userland/upstream-src/getrandom-0.2.17/tests/custom.rs b/userland/upstream-src/getrandom-0.2.17/tests/custom.rs new file mode 100644 index 0000000000..b085094be8 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/tests/custom.rs @@ -0,0 +1,54 @@ +// Test that a custom handler works on wasm32-unknown-unknown +#![cfg(all( + target_arch = "wasm32", + target_os = "unknown", + feature = "custom", + not(feature = "js") +))] + +use wasm_bindgen_test::wasm_bindgen_test as test; + +use core::num::NonZeroU32; +use getrandom::{getrandom, register_custom_getrandom, Error}; + +fn len7_err() -> Error { + NonZeroU32::new(Error::INTERNAL_START + 7).unwrap().into() +} + +fn super_insecure_rng(buf: &mut [u8]) -> Result<(), Error> { + // `getrandom` guarantees it will not call any implementation if the output + // buffer is empty. + assert!(!buf.is_empty()); + // Length 7 buffers return a custom error + if buf.len() == 7 { + return Err(len7_err()); + } + // Otherwise, fill bytes based on input length + let mut start = buf.len() as u8; + for b in buf { + *b = start; + start = start.wrapping_mul(3); + } + Ok(()) +} + +register_custom_getrandom!(super_insecure_rng); + +use getrandom::getrandom as getrandom_impl; +mod common; + +#[test] +fn custom_rng_output() { + let mut buf = [0u8; 4]; + assert_eq!(getrandom(&mut buf), Ok(())); + assert_eq!(buf, [4, 12, 36, 108]); + + let mut buf = [0u8; 3]; + assert_eq!(getrandom(&mut buf), Ok(())); + assert_eq!(buf, [3, 9, 27]); +} + +#[test] +fn rng_err_output() { + assert_eq!(getrandom(&mut [0; 7]), Err(len7_err())); +} diff --git a/userland/upstream-src/getrandom-0.2.17/tests/normal.rs b/userland/upstream-src/getrandom-0.2.17/tests/normal.rs new file mode 100644 index 0000000000..5fff13b38e --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/tests/normal.rs @@ -0,0 +1,11 @@ +// Don't test on custom wasm32-unknown-unknown +#![cfg(not(all( + target_arch = "wasm32", + target_os = "unknown", + feature = "custom", + not(feature = "js") +)))] + +// Use the normal getrandom implementation on this architecture. +use getrandom::getrandom as getrandom_impl; +mod common; diff --git a/userland/upstream-src/getrandom-0.2.17/tests/rdrand.rs b/userland/upstream-src/getrandom-0.2.17/tests/rdrand.rs new file mode 100644 index 0000000000..a355c31ee8 --- /dev/null +++ b/userland/upstream-src/getrandom-0.2.17/tests/rdrand.rs @@ -0,0 +1,22 @@ +// We only test the RDRAND-based RNG source on supported architectures. +#![cfg(any(target_arch = "x86_64", target_arch = "x86"))] + +// rdrand.rs expects to be part of the getrandom main crate, so we need these +// additional imports to get rdrand.rs to compile. +use getrandom::Error; +#[macro_use] +extern crate cfg_if; +#[path = "../src/lazy.rs"] +mod lazy; +#[path = "../src/rdrand.rs"] +mod rdrand; +#[path = "../src/util.rs"] +mod util; + +// The rdrand implementation has the signature of getrandom_uninit(), but our +// tests expect getrandom_impl() to have the signature of getrandom(). +fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { + rdrand::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; + Ok(()) +} +mod common; diff --git a/userland/upstream-src/home-0.5.12/.cargo-ok b/userland/upstream-src/home-0.5.12/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/home-0.5.12/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/home-0.5.12/.cargo_vcs_info.json b/userland/upstream-src/home-0.5.12/.cargo_vcs_info.json new file mode 100644 index 0000000000..45a591ae37 --- /dev/null +++ b/userland/upstream-src/home-0.5.12/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "4f15cc8882fb34bf70c945e9c8ae91d6c8c91757" + }, + "path_in_vcs": "crates/home" +} \ No newline at end of file diff --git a/userland/upstream-src/home-0.5.12/Cargo.lock b/userland/upstream-src/home-0.5.12/Cargo.lock new file mode 100644 index 0000000000..bb24147912 --- /dev/null +++ b/userland/upstream-src/home-0.5.12/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "home" +version = "0.5.12" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] diff --git a/userland/upstream-src/home-0.5.12/Cargo.toml b/userland/upstream-src/home-0.5.12/Cargo.toml new file mode 100644 index 0000000000..3361c277c7 --- /dev/null +++ b/userland/upstream-src/home-0.5.12/Cargo.toml @@ -0,0 +1,70 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +rust-version = "1.88" +name = "home" +version = "0.5.12" +authors = ["Brian Anderson "] +build = false +include = [ + "/src", + "/Cargo.toml", + "/CHANGELOG", + "/LICENSE-*", + "/README.md", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Shared definitions of home directories." +homepage = "https://github.com/rust-lang/cargo" +documentation = "https://docs.rs/home" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/cargo" +resolver = "2" + +[lib] +name = "home" +path = "src/lib.rs" + +[target."cfg(windows)".dependencies.windows-sys] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_UI_Shell", + "Win32_System_Com", +] + +[lints.clippy] +dbg_macro = "warn" +disallowed_methods = "warn" +print_stderr = "warn" +print_stdout = "warn" +self_named_module_files = "warn" + +[lints.clippy.all] +level = "allow" +priority = -2 + +[lints.clippy.correctness] +level = "warn" +priority = -1 + +[lints.rust] +rust_2018_idioms = "warn" + +[lints.rustdoc] +private_intra_doc_links = "allow" diff --git a/userland/upstream-src/home-0.5.12/Cargo.toml.orig b/userland/upstream-src/home-0.5.12/Cargo.toml.orig new file mode 100644 index 0000000000..747316f16e --- /dev/null +++ b/userland/upstream-src/home-0.5.12/Cargo.toml.orig @@ -0,0 +1,24 @@ +[package] +name = "home" +version = "0.5.12" +authors = ["Brian Anderson "] +rust-version.workspace = true +documentation = "https://docs.rs/home" +edition.workspace = true +include = [ + "/src", + "/Cargo.toml", + "/CHANGELOG", + "/LICENSE-*", + "/README.md", +] +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Shared definitions of home directories." + +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_System_Com"] } + +[lints] +workspace = true diff --git a/userland/upstream-src/home-0.5.12/LICENSE-APACHE b/userland/upstream-src/home-0.5.12/LICENSE-APACHE new file mode 100644 index 0000000000..c98d27d4f3 --- /dev/null +++ b/userland/upstream-src/home-0.5.12/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/LICENSE-2.0 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/userland/upstream-src/home-0.5.12/LICENSE-MIT b/userland/upstream-src/home-0.5.12/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/userland/upstream-src/home-0.5.12/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/home-0.5.12/README.md b/userland/upstream-src/home-0.5.12/README.md new file mode 100644 index 0000000000..0211b5a0cd --- /dev/null +++ b/userland/upstream-src/home-0.5.12/README.md @@ -0,0 +1,38 @@ +[![Documentation](https://docs.rs/home/badge.svg)](https://docs.rs/home) +[![crates.io](https://img.shields.io/crates/v/home.svg)](https://crates.io/crates/home) + +Canonical definitions of `home_dir`, `cargo_home`, and `rustup_home`. + +This provides the definition of `home_dir` used by Cargo and rustup, +as well functions to find the correct value of `CARGO_HOME` and +`RUSTUP_HOME`. + +The definition of [`home_dir`] provided by the standard library is +incorrect because it considers the `HOME` environment variable on +Windows. This causes surprising situations where a Rust program will +behave differently depending on whether it is run under a Unix +emulation environment like Cygwin or MinGW. Neither Cargo nor rustup +use the standard library's definition - they use the definition here. + +**Note:** This has been fixed in Rust 1.85 to no longer use the `HOME` +environment variable on Windows. If you are still using this crate for the +purpose of getting a home directory, you are strongly encouraged to switch to +using the standard library's [`home_dir`] instead. It is planned to have the +deprecation notice removed in 1.87. + +This crate further provides two functions, `cargo_home` and +`rustup_home`, which are the canonical way to determine the location +that Cargo and rustup store their data. + +See [rust-lang/rust#43321]. + +> This crate is maintained by the Cargo team, primarily for use by Cargo and Rustup +> and not intended for external use. This +> crate may make major changes to its APIs or be deprecated without warning. + +[rust-lang/rust#43321]: https://github.com/rust-lang/rust/issues/43321 +[`home_dir`]: https://doc.rust-lang.org/nightly/std/env/fn.home_dir.html + +## License + +MIT OR Apache-2.0 diff --git a/userland/upstream-src/home-0.5.12/src/env.rs b/userland/upstream-src/home-0.5.12/src/env.rs new file mode 100644 index 0000000000..f5a6a2231a --- /dev/null +++ b/userland/upstream-src/home-0.5.12/src/env.rs @@ -0,0 +1,114 @@ +//! Lower-level utilities for mocking the process environment. + +use std::{ + ffi::OsString, + io, + path::{Path, PathBuf}, +}; + +/// Permits parameterizing the home functions via the _from variants - used for +/// in-process unit testing by rustup. +pub trait Env { + /// Return the path to the users home dir, or None if any error occurs: + /// see `home_inner`. + fn home_dir(&self) -> Option; + /// Return the current working directory. + fn current_dir(&self) -> io::Result; + /// Get an environment variable, as per `std::env::var_os`. + fn var_os(&self, key: &str) -> Option; +} + +/// Implements Env for the OS context, both Unix style and Windows. +/// +/// This is trait permits in-process testing by providing a control point to +/// allow in-process divergence on what is normally process wide state. +/// +/// Implementations should be provided by whatever testing framework the caller +/// is using. Code that is not performing in-process threaded testing requiring +/// isolated rustup/cargo directories does not need this trait or the _from +/// functions. +pub struct OsEnv; +impl Env for OsEnv { + fn home_dir(&self) -> Option { + crate::home_dir_inner() + } + fn current_dir(&self) -> io::Result { + std::env::current_dir() + } + fn var_os(&self, key: &str) -> Option { + std::env::var_os(key) + } +} + +pub const OS_ENV: OsEnv = OsEnv {}; + +/// Returns the path of the current user's home directory from [`Env::home_dir`]. +pub fn home_dir_with_env(env: &dyn Env) -> Option { + env.home_dir() +} + +/// Variant of `cargo_home` where the environment source is parameterized. +/// +/// This is +/// specifically to support in-process testing scenarios as environment +/// variables and user home metadata are normally process global state. See the +/// [`Env`] trait. +pub fn cargo_home_with_env(env: &dyn Env) -> io::Result { + let cwd = env.current_dir()?; + cargo_home_with_cwd_env(env, &cwd) +} + +/// Variant of `cargo_home_with_cwd` where the environment source is +/// parameterized. +/// +/// This is specifically to support in-process testing scenarios +/// as environment variables and user home metadata are normally process global +/// state. See the `OsEnv` trait. +pub fn cargo_home_with_cwd_env(env: &dyn Env, cwd: &Path) -> io::Result { + match env.var_os("CARGO_HOME").filter(|h| !h.is_empty()) { + Some(home) => { + let home = PathBuf::from(home); + if home.is_absolute() { + Ok(home) + } else { + Ok(cwd.join(&home)) + } + } + _ => home_dir_with_env(env) + .map(|p| p.join(".cargo")) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not find cargo home dir")), + } +} + +/// Variant of `cargo_home_with_cwd` where the environment source is +/// parameterized. +/// +/// This is specifically to support in-process testing scenarios +/// as environment variables and user home metadata are normally process global +/// state. See the `OsEnv` trait. +pub fn rustup_home_with_env(env: &dyn Env) -> io::Result { + let cwd = env.current_dir()?; + rustup_home_with_cwd_env(env, &cwd) +} + +/// Variant of `cargo_home_with_cwd` where the environment source is +/// parameterized. +/// +/// This is specifically to support in-process testing scenarios +/// as environment variables and user home metadata are normally process global +/// state. See the `OsEnv` trait. +pub fn rustup_home_with_cwd_env(env: &dyn Env, cwd: &Path) -> io::Result { + match env.var_os("RUSTUP_HOME").filter(|h| !h.is_empty()) { + Some(home) => { + let home = PathBuf::from(home); + if home.is_absolute() { + Ok(home) + } else { + Ok(cwd.join(&home)) + } + } + _ => home_dir_with_env(env) + .map(|d| d.join(".rustup")) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not find rustup home dir")), + } +} diff --git a/userland/upstream-src/home-0.5.12/src/lib.rs b/userland/upstream-src/home-0.5.12/src/lib.rs new file mode 100644 index 0000000000..a84d8fa03e --- /dev/null +++ b/userland/upstream-src/home-0.5.12/src/lib.rs @@ -0,0 +1,159 @@ +//! Canonical definitions of `home_dir`, `cargo_home`, and `rustup_home`. +//! +//! The definition of `home_dir` provided by the standard library is +//! incorrect because it considers the `HOME` environment variable on +//! Windows. This causes surprising situations where a Rust program +//! will behave differently depending on whether it is run under a +//! Unix emulation environment like Cygwin or MinGW. Neither Cargo nor +//! rustup use the standard libraries definition - they use the +//! definition here. +//! +//! This crate provides two additional functions, `cargo_home` and +//! `rustup_home`, which are the canonical way to determine the +//! location that Cargo and rustup use to store their data. +//! The `env` module contains utilities for mocking the process environment +//! by Cargo and rustup. +//! +//! See also this [discussion]. +//! +//! > This crate is maintained by the Cargo team, primarily for use by Cargo and Rustup +//! > and not intended for external use. This +//! > crate may make major changes to its APIs or be deprecated without warning. +//! +//! [discussion]: https://github.com/rust-lang/rust/pull/46799#issuecomment-361156935 + +#![allow(clippy::disallowed_methods)] + +pub mod env; + +#[cfg(target_os = "windows")] +mod windows; + +use std::io; +use std::path::{Path, PathBuf}; + +/// Returns the path of the current user's home directory using environment +/// variables or OS-specific APIs. +/// +/// # Unix +/// +/// Returns the value of the `HOME` environment variable if it is set +/// **even** if it is an empty string. Otherwise, it tries to determine the +/// home directory by invoking the [`getpwuid_r`][getpwuid] function with +/// the UID of the current user. +/// +/// [getpwuid]: https://linux.die.net/man/3/getpwuid_r +/// +/// # Windows +/// +/// Returns the value of the `USERPROFILE` environment variable if it is set +/// **and** it is not an empty string. Otherwise, it tries to determine the +/// home directory by invoking the [`SHGetKnownFolderPath`][shgkfp] function with +/// [`FOLDERID_Profile`][knownfolderid]. +/// +/// [shgkfp]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath +/// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid +/// +/// # Examples +/// +/// ``` +/// match home::home_dir() { +/// Some(path) if !path.as_os_str().is_empty() => println!("{}", path.display()), +/// _ => println!("Unable to get your home dir!"), +/// } +/// ``` +pub fn home_dir() -> Option { + env::home_dir_with_env(&env::OS_ENV) +} + +#[cfg(windows)] +use windows::home_dir_inner; + +#[cfg(unix)] +fn home_dir_inner() -> Option { + #[allow(deprecated)] + std::env::home_dir() +} + +// NONOS: a capsule's home is whatever the HOME environment variable names, and +// nothing if it is unset or empty, matching how the unix path behaves. +#[cfg(target_vendor = "nonos")] +fn home_dir_inner() -> Option { + std::env::var_os("HOME") + .map(PathBuf::from) + .filter(|p| !p.as_os_str().is_empty()) +} + +/// Returns the storage directory used by Cargo, often known as +/// `.cargo` or `CARGO_HOME`. +/// +/// It returns one of the following values, in this order of +/// preference: +/// +/// - The value of the `CARGO_HOME` environment variable, if it is +/// an absolute path. +/// - The value of the current working directory joined with the value +/// of the `CARGO_HOME` environment variable, if `CARGO_HOME` is a +/// relative directory. +/// - The `.cargo` directory in the user's home directory, as reported +/// by the `home_dir` function. +/// +/// # Errors +/// +/// This function fails if it fails to retrieve the current directory, +/// or if the home directory cannot be determined. +/// +/// # Examples +/// +/// ``` +/// match home::cargo_home() { +/// Ok(path) => println!("{}", path.display()), +/// Err(err) => eprintln!("Cannot get your cargo home dir: {:?}", err), +/// } +/// ``` +pub fn cargo_home() -> io::Result { + env::cargo_home_with_env(&env::OS_ENV) +} + +/// Returns the storage directory used by Cargo within `cwd`. +/// For more details, see [`cargo_home`](fn.cargo_home.html). +pub fn cargo_home_with_cwd(cwd: &Path) -> io::Result { + env::cargo_home_with_cwd_env(&env::OS_ENV, cwd) +} + +/// Returns the storage directory used by rustup, often known as +/// `.rustup` or `RUSTUP_HOME`. +/// +/// It returns one of the following values, in this order of +/// preference: +/// +/// - The value of the `RUSTUP_HOME` environment variable, if it is +/// an absolute path. +/// - The value of the current working directory joined with the value +/// of the `RUSTUP_HOME` environment variable, if `RUSTUP_HOME` is a +/// relative directory. +/// - The `.rustup` directory in the user's home directory, as reported +/// by the `home_dir` function. +/// +/// # Errors +/// +/// This function fails if it fails to retrieve the current directory, +/// or if the home directory cannot be determined. +/// +/// # Examples +/// +/// ``` +/// match home::rustup_home() { +/// Ok(path) => println!("{}", path.display()), +/// Err(err) => eprintln!("Cannot get your rustup home dir: {:?}", err), +/// } +/// ``` +pub fn rustup_home() -> io::Result { + env::rustup_home_with_env(&env::OS_ENV) +} + +/// Returns the storage directory used by rustup within `cwd`. +/// For more details, see [`rustup_home`](fn.rustup_home.html). +pub fn rustup_home_with_cwd(cwd: &Path) -> io::Result { + env::rustup_home_with_cwd_env(&env::OS_ENV, cwd) +} diff --git a/userland/upstream-src/home-0.5.12/src/windows.rs b/userland/upstream-src/home-0.5.12/src/windows.rs new file mode 100644 index 0000000000..652733ec0f --- /dev/null +++ b/userland/upstream-src/home-0.5.12/src/windows.rs @@ -0,0 +1,84 @@ +use std::env; +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use std::path::PathBuf; +use std::ptr; +use std::slice; + +use windows_sys::Win32::Foundation::S_OK; +use windows_sys::Win32::System::Com::CoTaskMemFree; +use windows_sys::Win32::UI::Shell::{FOLDERID_Profile, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath}; + +pub fn home_dir_inner() -> Option { + env::var_os("USERPROFILE") + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + .or_else(home_dir_crt) +} + +#[cfg(not(target_vendor = "uwp"))] +fn home_dir_crt() -> Option { + unsafe { + let mut path = ptr::null_mut(); + match SHGetKnownFolderPath( + &FOLDERID_Profile, + KF_FLAG_DONT_VERIFY as u32, + std::ptr::null_mut(), + &mut path, + ) { + S_OK => { + let path_slice = slice::from_raw_parts(path, wcslen(path)); + let s = OsString::from_wide(&path_slice); + CoTaskMemFree(path.cast()); + Some(PathBuf::from(s)) + } + _ => { + // Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`. + CoTaskMemFree(path.cast()); + None + } + } + } +} + +#[cfg(target_vendor = "uwp")] +fn home_dir_crt() -> Option { + None +} + +unsafe extern "C" { + fn wcslen(buf: *const u16) -> usize; +} + +#[cfg(not(target_vendor = "uwp"))] +#[cfg(test)] +mod tests { + use super::home_dir_inner; + use std::env; + use std::ops::Deref; + use std::path::{Path, PathBuf}; + + #[test] + fn test_with_without() { + let olduserprofile = env::var_os("USERPROFILE").unwrap(); + + unsafe { + env::remove_var("HOME"); + env::remove_var("USERPROFILE"); + } + + assert_eq!(home_dir_inner(), Some(PathBuf::from(olduserprofile))); + + let home = Path::new(r"C:\Users\foo tar baz"); + + unsafe { + env::set_var("HOME", home.as_os_str()); + } + assert_ne!(home_dir_inner().as_ref().map(Deref::deref), Some(home)); + + unsafe { + env::set_var("USERPROFILE", home.as_os_str()); + } + assert_eq!(home_dir_inner().as_ref().map(Deref::deref), Some(home)); + } +} diff --git a/userland/upstream-src/huniq/.cargo-ok b/userland/upstream-src/huniq/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/huniq/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/huniq/.cargo_vcs_info.json b/userland/upstream-src/huniq/.cargo_vcs_info.json new file mode 100644 index 0000000000..1b628f72aa --- /dev/null +++ b/userland/upstream-src/huniq/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "1e4d1f23bbb88c34f7c128f7eb4a500e97eabf4b" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/huniq/.gitignore b/userland/upstream-src/huniq/.gitignore new file mode 100644 index 0000000000..53eaa21960 --- /dev/null +++ b/userland/upstream-src/huniq/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/userland/upstream-src/huniq/.rustfmt.toml b/userland/upstream-src/huniq/.rustfmt.toml new file mode 100644 index 0000000000..53b00e602d --- /dev/null +++ b/userland/upstream-src/huniq/.rustfmt.toml @@ -0,0 +1 @@ +# Using default style currently diff --git a/userland/upstream-src/huniq/Cargo.lock b/userland/upstream-src/huniq/Cargo.lock new file mode 100644 index 0000000000..ba15c93b63 --- /dev/null +++ b/userland/upstream-src/huniq/Cargo.lock @@ -0,0 +1,314 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" + +[[package]] +name = "assert_cmd" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aad2534fad53df1cc12519c5cda696dd3e20e6118a027e24054aea14a0bdcbe" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "huniq" +version = "2.7.0" +dependencies = [ + "ahash", + "anyhow", + "assert_cmd", + "bstr", + "clap", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" + +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/userland/upstream-src/huniq/Cargo.toml b/userland/upstream-src/huniq/Cargo.toml new file mode 100644 index 0000000000..8b6b107642 --- /dev/null +++ b/userland/upstream-src/huniq/Cargo.toml @@ -0,0 +1,43 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "huniq" +version = "2.7.0" +authors = ["Karolin Varner "] +description = "Filter out duplicates on the command line. Replacement for `sort | uniq` optimized for speed (10x faster)." +readme = "readme.md" +keywords = ["cli", "uniq"] +categories = ["command-line-utilities"] +license = "BSD-3-Clause" +repository = "https://github.com/koraa/huniq" +[profile.release] +lto = "fat" +[dependencies.ahash] +version = "0.7.6" + +[dependencies.anyhow] +version = "1.0.56" + +[dependencies.bstr] +version = "0.2.17" + +[dependencies.clap] +version = "3.1.9" +[dev-dependencies.assert_cmd] +version = "2.0.4" + + +# nonos-app-patch +[patch.crates-io] +atty = { path = "../atty-0.2.14" } +os_str_bytes = { path = "../os_str_bytes-6.6.1" } diff --git a/userland/upstream-src/huniq/Cargo.toml.orig b/userland/upstream-src/huniq/Cargo.toml.orig new file mode 100644 index 0000000000..19f9c35ec3 --- /dev/null +++ b/userland/upstream-src/huniq/Cargo.toml.orig @@ -0,0 +1,23 @@ +[package] +name = "huniq" +version = "2.7.0" +authors = ["Karolin Varner "] +edition = "2018" +license = "BSD-3-Clause" +description = "Filter out duplicates on the command line. Replacement for `sort | uniq` optimized for speed (10x faster)." +readme = "readme.md" +repository = "https://github.com/koraa/huniq" +categories = ["command-line-utilities"] +keywords = ["cli", "uniq"] + +[dependencies] +clap = "3.1.9" +anyhow = "1.0.56" +ahash = "0.7.6" +bstr = "0.2.17" + +[dev-dependencies] +assert_cmd = "2.0.4" + +[profile.release] +lto = "fat" diff --git a/userland/upstream-src/huniq/benchmark.sh b/userland/upstream-src/huniq/benchmark.sh new file mode 100755 index 0000000000..9afc166725 --- /dev/null +++ b/userland/upstream-src/huniq/benchmark.sh @@ -0,0 +1,112 @@ +#! /usr/bin/env bash + +set -e +trap "exit" SIGINT SIGTERM # exit from loop + +cd "$(dirname "$0")" +huniq2bin="./target/release/huniq" +huniq1dir="./target/benchmark/huniq1" +huniq1bin="${huniq1dir}/huniq" + +measure() { + env time -f'%e %M' "$@" +} + +meas_datamash() { + if [[ "$@" = "" ]]; then + { measure datamash -s groupby 1 first 1 >/dev/null; } 2>&1 + else + { measure datamash -s groupby 1 count 1 >/dev/null; } 2>&1 + fi +} + +meas_awk() { + local cmd="$1"; shift + if [[ "$@" = "" ]]; then + { measure "$cmd" '!visited[$0]++' >/dev/null; } 2>&1 + else + { + measure "$cmd" ' + { + visited[$0]++; + } + END { + for (k in visited) + print(k, visited[k]); + }' >/dev/null + } 2>&1 + fi +} + +meas_shell() { + if [[ "$@" = "" ]]; then + { measure sort -u >/dev/null; } 2>&1 + else + { + measure sort | measure uniq -c >/dev/null + } 2>&1 | awk ' + { + elapsed=$1; + mem+=$2; + } + + END { + print(elapsed, mem); + }' + fi +} + +meas_exe() { + { measure "$@" >/dev/null; } 2>&1 +} + +bench() { + local name="$1"; shift + yes | head -n "$repeats" \ + | while read _; do cat /usr/share/dict/*; done \ + | "$@" ${modeargs[${mode}]} \ + | while read results; do echo "$mode $repeats $name $results"; done +} + +main() { + test -e "$huniq2bin" || { + cargo build --release + } + + test -e "$huniq1dir" || { + git clone --recursive "https://github.com/SoftwearDevelopment/huniq" "$huniq1dir" + } >&2 + + test -e "$huniq1bin" || { + cd "$huniq1dir" + make + cd - + } >&2 + + declare -A modeargs + modeargs[uniq]="" + modeargs[count]="-c" + + while true; do + for mode in "uniq" "count"; do + for repeats in 1 2 5 10 50 100; do + bench 'huniq2-rust' meas_exe "${huniq2bin}" + bench 'huniq1-c++ ' meas_exe "${huniq1bin}" + bench 'awk-sys ' meas_awk awk + if which gawk 2>/dev/null >/dev/null; then + bench 'gawk ' meas_awk gawk + fi + if which nawk 2>/dev/null >/dev/null; then + bench 'nawk ' meas_awk nawk + fi + if which datamash 2>/dev/null >/dev/null; then + bench 'datamash ' meas_datamash + fi + bench 'shell ' meas_shell + echo + done + done + done +} + +main diff --git a/userland/upstream-src/huniq/benchmark_results.txt b/userland/upstream-src/huniq/benchmark_results.txt new file mode 100644 index 0000000000..63030e843a --- /dev/null +++ b/userland/upstream-src/huniq/benchmark_results.txt @@ -0,0 +1,106 @@ +uniq 1 huniq2-rust 0.26 29524 +uniq 1 huniq1-c++ 1.67 26188 +uniq 1 awk 1.63 321936 +uniq 1 datamash 1.78 9644 +uniq 1 shell 7.30 9736 + +uniq 2 huniq2-rust 0.84 29592 +uniq 2 huniq1-c++ 3.28 26180 +uniq 2 awk 3.71 322012 +uniq 2 datamash 4.60 9636 +uniq 2 shell 16.68 9740 + +uniq 5 huniq2-rust 2.02 29648 +uniq 5 huniq1-c++ 6.21 26184 +uniq 5 awk 7.69 322012 +uniq 5 datamash 9.10 9992 +uniq 5 shell 44.71 10184 + +uniq 10 huniq2-rust 3.40 29676 +uniq 10 huniq1-c++ 12.84 26172 +uniq 10 awk 16.73 321940 +uniq 10 datamash 24.44 10032 +uniq 10 shell 93.75 10036 + +uniq 50 huniq2-rust 14.68 29612 +uniq 50 huniq1-c++ 55.32 26200 +uniq 50 awk 74.91 321940 +uniq 50 datamash 103.54 10936 +uniq 50 shell 453.94 10956 + +uniq 100 huniq2-rust 43.65 29492 +uniq 100 huniq1-c++ 154.99 26180 +uniq 100 awk 239.66 321956 +uniq 100 datamash 285.94 12148 +uniq 100 shell 1062.07 12208 + +count 1 huniq2-rust 1.47 132096 +count 1 huniq1-c++ 1.85 60196 +count 1 awk 2.79 362940 +count 1 datamash 2.28 9636 +count 1 shell 7.71 11716 + +count 2 huniq2-rust 2.32 132052 +count 2 huniq1-c++ 2.98 60156 +count 2 awk 4.65 363016 +count 2 datamash 5.27 9732 +count 2 shell 16.37 11680 + +count 5 huniq2-rust 4.98 132092 +count 5 huniq1-c++ 7.54 60128 +count 5 awk 9.37 363016 +count 5 datamash 11.22 9964 +count 5 shell 44.77 11948 + +count 10 huniq2-rust 8.81 132048 +count 10 huniq1-c++ 13.55 60196 +count 10 awk 16.19 363032 +count 10 datamash 25.12 9908 +count 10 shell 90.01 11976 + +count 50 huniq2-rust 45.89 132092 +count 50 huniq1-c++ 74.04 60104 +count 50 awk 85.43 362956 +count 50 datamash 141.90 10996 +count 50 shell 454.42 12876 + +count 100 huniq2-rust 90.80 132080 +count 100 huniq1-c++ 150.41 60196 +count 100 awk 163.13 363008 +count 100 datamash 322.70 12212 +count 100 shell 933.67 14100 + +uniq 1 huniq2-rust 0.38 29672 +uniq 1 huniq1-c++ 2.39 26144 +uniq 1 awk 2.23 321972 +uniq 1 datamash 1.96 9644 +uniq 1 shell 10.40 9884 + +uniq 2 huniq2-rust 0.70 29764 +uniq 2 huniq1-c++ 3.56 26176 +uniq 2 awk 3.69 321972 +uniq 2 datamash 3.50 9640 +uniq 2 shell 20.67 9936 + +uniq 5 huniq2-rust 1.71 29640 +uniq 5 huniq1-c++ 7.60 26124 +uniq 5 awk 8.61 322036 +uniq 5 datamash 13.94 10000 +uniq 5 shell 53.19 10048 + +uniq 10 huniq2-rust 3.53 29580 +uniq 10 huniq1-c++ 13.81 26120 +uniq 10 awk 16.63 322036 +uniq 10 datamash 24.32 10100 +uniq 10 shell 97.46 10052 + +uniq 50 huniq2-rust 16.41 29676 +uniq 50 huniq1-c++ 65.71 26120 +uniq 50 awk 82.96 321988 +uniq 50 datamash 138.09 11060 +uniq 50 shell 507.67 11032 + +uniq 100 huniq2-rust 32.65 29548 +uniq 100 huniq1-c++ 130.76 26140 +uniq 100 awk 167.15 321960 +uniq 100 datamash 288.22 11976 diff --git a/userland/upstream-src/huniq/readme.md b/userland/upstream-src/huniq/readme.md new file mode 100644 index 0000000000..e485a5dab7 --- /dev/null +++ b/userland/upstream-src/huniq/readme.md @@ -0,0 +1,180 @@ +# huniq version 2 + +Command line utility to remove duplicates from the given input. +Note that huniq does not sort the input, it just removes duplicates. + +``` +SYNOPSIS: huniq -h # Shows help +SYNOPSIS: huniq [-c|--count] [-0|--null|-d DELIM|--delim DELIM] +``` + +``` +$ echo -e "foo\nbar\nfoo\nbaz" | huniq +foo +bar +baz + +$ echo -e "foo\nbar\nfoo\nbaz" | huniq -c +1 baz +2 foo +1 bar +``` + +`huniq` replaces `sort | uniq` (or `sort -u` with gnu sort) and `huniq -c` replaces `sort | uniq -c`, assuming the data is sorted just so it can be passed to `uniq`. If having sorted output is desired, `sort | uniq` should still be used. + +The order of the output is stable when in normal mode, but it is not stable when in -c/count mode. + +## Installation + +``` +$ cargo install huniq +``` + +## Motivation + +Sorting is slow. By using hash tables/hash sets instead of sorting +the input huniq is generally faster than `sort -u` or `sort | uniq -c` when testing with gnu sort/gnu uniq. + +## Version History + +Version 1 can be found [here](https://github.com/SoftwearDevelopment/huniq). + +Changes made in version 2: + +* The -d/-0 flags where added so you can specify custom delimiters +* Completely rewritten in rust. +* Version two is (depending on which benchmark you look at below) between 3.5x and 6.5x faster than version 1 + +## Build + +```sh +cargo build --release +``` + +To run the tests execute: + +```sh +bash ./test.sh +``` + +## Benchmark + +You can use `bash ./benchmark.sh` to execute the benchmarks. They will execute until you manually abort them (e.g. by pressing Ctrl-C). + +The benchmarks work by repeatedly feeding the implementations with data +from /usr/share/dict/* and measuring memory usage and time needed to process +the data with the unix `time` tool. + +For the `uniq` algorithm, the results are posted below: We can see that the +rust implementation blows pretty much anything else out of the water in terms +of performance. Use sort only if you really need a coffee break, because you +won't get it with huniq! It beats the C++ implementation by a factor +of between 6.5 (for very few duplicates) and 3.5 (around 98% duplicates). +Compared to `sort -u`: huniq is around 30 times faster. + +If memory efficiency is what you are looking for, use datamash which is not as fast as huniq +but uses the least memory (by a factor of around 3); failing that use `sort|uniq` which is a +lot slower but uses just very slightly more memory than datamash. + +``` +repetitions implementation seconds memory/kb +1 huniq2-rust 0.26 29524 +1 huniq1-c++ 1.67 26188 +1 awk 1.63 321936 +1 datamash 1.78 9644 +1 shell 7.30 9736 +2 huniq2-rust 0.84 29592 +2 huniq1-c++ 3.28 26180 +2 awk 3.71 322012 +2 datamash 4.60 9636 +2 shell 16.68 9740 +5 huniq2-rust 2.02 29648 +5 huniq1-c++ 6.21 26184 +5 awk 7.69 322012 +5 datamash 9.10 9992 +5 shell 44.71 10184 +10 huniq2-rust 3.40 29676 +10 huniq1-c++ 12.84 26172 +10 awk 16.73 321940 +10 datamash 24.44 10032 +10 shell 93.75 10036 +50 huniq2-rust 14.68 29612 +50 huniq1-c++ 55.32 26200 +50 awk 74.91 321940 +50 datamash 103.54 10936 +50 shell 453.94 10956 +100 huniq2-rust 43.65 29492 +100 huniq1-c++ 154.99 26180 +100 awk 239.66 321956 +100 datamash 285.94 12148 +100 shell 1062.07 12208 +``` + +For the counting `huniq -c` implementation, the speed advantage +was less pronounced: Here the rust implementation is between 25% +and 50% faster than the C++ implementation and between 5x and 10x +faster than `sort | uniq -c`. + +The increased memory usage of the rust implementation is much worse though: +The rust implementation needs about 2.2x more memory than the C++ implementation +and between 10x and 12x more memory than `sort | uniq`. + +``` +repetitions implemetation seconds memory/kb +1 huniq2-rust 1.47 132096 +1 huniq1-c++ 1.85 60196 +1 awk 2.79 362940 +1 datamash 2.28 9636 +1 shell 7.71 11716 +2 huniq2-rust 2.32 132052 +2 huniq1-c++ 2.98 60156 +2 awk 4.65 363016 +2 datamash 5.27 9732 +2 shell 16.37 11680 +5 huniq2-rust 4.98 132092 +5 huniq1-c++ 7.54 60128 +5 awk 9.37 363016 +5 datamash 11.22 9964 +5 shell 44.77 11948 +10 huniq2-rust 8.81 132048 +10 huniq1-c++ 13.55 60196 +10 awk 16.19 363032 +10 datamash 25.12 9908 +10 shell 90.01 11976 +50 huniq2-rust 45.89 132092 +50 huniq1-c++ 74.04 60104 +50 awk 85.43 362956 +50 datamash 141.90 10996 +50 shell 454.42 12876 +100 huniq2-rust 90.80 132080 +100 huniq1-c++ 150.41 60196 +100 awk 163.13 363008 +100 datamash 322.70 12212 +100 shell 933.67 14100 +``` + +## Future direction + +Feature wise huniq is pretty much complete, but the performance and memory usage should be improved in the future. + +This first of all involves a better benchmarking setup which will probably consist +of an extra rust application that will use RNGs to generate test data for huniq and +take parameters like the number of elements to create, the rate of duplicates (0-1) +the length of strings to output and so on… + +Then based on the improved benchmarking capabilities, some optimizations should be tried +like short string optimization, arena allocation, different hash functions, using +memory optimized hash tables, using an identity function for the `uniq` function +(we already feed it with hashes, so a second round of hashing is not necessary). + +## License + +Copyright © (C) 2020, Karolin Varner. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Karolin Varner nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Softwear, BV BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/userland/upstream-src/huniq/src/main.rs b/userland/upstream-src/huniq/src/main.rs new file mode 100644 index 0000000000..2fd9afc67f --- /dev/null +++ b/userland/upstream-src/huniq/src/main.rs @@ -0,0 +1,217 @@ +use ahash::RandomState as ARandomState; +use anyhow::{anyhow, Result}; +use bstr::{io::BufReadExt, ByteSlice}; +use clap::{Arg, Command}; +use std::cmp::Ordering; +use std::collections::{hash_map, HashMap, HashSet}; +use std::hash::BuildHasherDefault; +use std::hash::{BuildHasher, Hasher}; +use std::io::{stdin, stdout, BufRead, Write}; +use std::mem; +use std::{default::Default, slice}; + +/// A no-operation hasher. Used as part of the uniq implementation, +/// because in there we manually hash the data and just store the +/// hashes of the data in the hash set. No need to hash twice +#[derive(Default)] +struct IdentityHasher { + off: u8, + buf: [u8; 8], +} + +impl Hasher for IdentityHasher { + fn write(&mut self, bytes: &[u8]) { + self.off += (&mut self.buf[self.off as usize..]) + .write(bytes) + .unwrap_or(0) as u8; + } + + fn finish(&self) -> u64 { + u64::from_ne_bytes(self.buf) + } +} + +/// Hash the given value with the given BuildHasher. Now. +fn hash(build: &T, v: &U) -> u64 { + let mut s = build.build_hasher(); + v.hash(&mut s); + s.finish() +} + +enum Sort { + Ascending, + Descending, +} + +/// Remove duplicates from stdin and print to stdout, counting +/// the number of occurrences. +fn count_cmd(delim: u8, sort: Option) -> Result<()> { + let mut set = HashMap::, u64, ARandomState>::default(); + for line in stdin().lock().split(delim) { + match set.entry(line?) { + hash_map::Entry::Occupied(mut e) => { + *e.get_mut() += 1; + } + hash_map::Entry::Vacant(e) => { + e.insert(1); + } + } + } + + let result = if let Some(sort) = sort { + sort_and_print(delim, sort, &set) + } else { + print_out(delim, set.iter().map(|(k, v)| (k.as_slice(), *v))) + }; + + mem::forget(set); // app can now exit, so we don't need to wait for this memory to be freed piecemeal + + result +} + +type DataAndCount<'a> = (&'a [u8], u64); + +/// Sorts the lines by occurence, then prints them +// TODO: this could be done more efficiently by reusing the memory of the HashMap +fn sort_and_print(delim: u8, sort: Sort, set: &HashMap, u64, ARandomState>) -> Result<()> { + let mut seq: Vec = set.iter().map(|(k, v)| (k.as_slice(), *v)).collect(); + + let comparator: fn(&DataAndCount, &DataAndCount) -> Ordering = match sort { + Sort::Ascending => |a, b| a.1.cmp(&b.1), + Sort::Descending => |a, b| b.1.cmp(&a.1), + }; + seq.as_mut_slice().sort_by(comparator); + print_out(delim, seq) +} + +/// Prints the sequence of counts and data items, separated by delim +fn print_out<'a, I>(delim: u8, data: I) -> Result<()> +where + I: IntoIterator>, +{ + let out = stdout(); + let mut out = out.lock(); + for (line, count) in data { + write!(out, "{} ", count)?; + out.write_all(line)?; + out.write_all(slice::from_ref(&delim))?; + } + + Ok(()) +} + +/// Remove duplicates from stdin and print to stdout. +fn uniq_cmd(delim: u8, include_trailing: bool) -> Result<()> { + // Line processing/output /////////////////////// + let out = stdout(); + let inp = stdin(); + let hasher = ARandomState::new(); + let mut out = out.lock(); + let mut set = HashSet::>::default(); + + inp.lock().for_byte_record_with_terminator(delim, |line| { + let tok = trim_end(line, delim); + if set.insert(hash(&hasher, &tok)) { + out.write_all(line)?; + + if include_trailing && tok.len() == line.len() { + out.write_all(&[delim])?; + } + } + Ok(true) + })?; + + mem::forget(set); // app can now exit, so we don't need to wait for this memory to be freed piecemeal + + Ok(()) +} + +fn trim_end(mut record: &[u8], delim: u8) -> &[u8] { + if record.last_byte() == Some(delim) { + record = &record[..record.len() - 1]; + } + record +} + +fn try_main() -> Result<()> { + let argspec = Command::new("huniq") + .version(env!("CARGO_PKG_VERSION")) + .about("Remove duplicates from stdin, using a hash table") + .author("Karolin Varner Ok(()), + _ => Err(String::from( + "\ +Only ascii characters are supported as delimiters. \ +Use sed to turn your delimiter into zero bytes? + + $ echo -n \"1λ1λ2λ3\" | sed 's@λ@\x00@g' | huniq -0 | sed 's@\x00@λ@g' + 1λ2λ3λ", + )), + }), + ) + .arg( + Arg::new("null") + .help("Use the \\0 character as the record delimiter.") + .long("null") + .short('0') + .conflicts_with("delimiter"), + ) + .arg( + Arg::new("no-trailing-delimiter") + .help("Prevent adding a delimiter to the last record if missing") + .long("no-trailing-delimiter") + .short('t'), + ); + + let args = argspec.get_matches(); + + let delim = match args.is_present("null") { + true => b'\0', + false => args.value_of("delimiter").unwrap().as_bytes()[0], + }; + + let sort = match (args.is_present("sort"), args.is_present("sort-descending")) { + (true, true) => return Err(anyhow!("cannot specify both --sort and --sort-descending")), + (true, false) => Some(Sort::Ascending), + (false, true) => Some(Sort::Descending), + (false, false) => None, + }; + + match args.is_present("count") || sort.is_some() { + true => count_cmd(delim, sort), + false => uniq_cmd(delim, !args.is_present("no-trailing-delimiter")), + } +} + +fn main() { + if let Err(er) = try_main() { + println!("{}", er); + } +} diff --git a/userland/upstream-src/huniq/test.sh b/userland/upstream-src/huniq/test.sh new file mode 100644 index 0000000000..0a79fbc8d5 --- /dev/null +++ b/userland/upstream-src/huniq/test.sh @@ -0,0 +1,33 @@ +#! /bin/bash + +cd "$(dirname "$0")" +bin="./target/debug/huniq" + +failures=0 +count=0 + +assert() { + local desc="$1"; shift + local ref="$1"; shift + + (( count++ )) + diff <(eval "$@") "$ref" >/dev/null || { + echo >&2 "Assertion failed \"$desc\": \`$@\`" + diff <(eval "$@") "$ref" >&2 + (( failures++ )) + } +} + +main() { + test -e "$huniq2bin" || { + cargo build + } + + assert uniq test/expect_uniq.txt "$bin &2 "$count tests $failures failures" + test "$failures" -eq 0 +} + +main diff --git a/userland/upstream-src/huniq/test/expect_count.txt b/userland/upstream-src/huniq/test/expect_count.txt new file mode 100644 index 0000000000..9a9c459dda --- /dev/null +++ b/userland/upstream-src/huniq/test/expect_count.txt @@ -0,0 +1,3 @@ +3 hello +2 bar +1 foo diff --git a/userland/upstream-src/huniq/test/expect_uniq.txt b/userland/upstream-src/huniq/test/expect_uniq.txt new file mode 100644 index 0000000000..a73df941f3 --- /dev/null +++ b/userland/upstream-src/huniq/test/expect_uniq.txt @@ -0,0 +1,3 @@ +hello +foo +bar diff --git a/userland/upstream-src/huniq/test/input.txt b/userland/upstream-src/huniq/test/input.txt new file mode 100644 index 0000000000..e60394ded8 --- /dev/null +++ b/userland/upstream-src/huniq/test/input.txt @@ -0,0 +1,6 @@ +hello +foo +hello +hello +bar +bar diff --git a/userland/upstream-src/huniq/tests/tests.rs b/userland/upstream-src/huniq/tests/tests.rs new file mode 100644 index 0000000000..235fbd80d4 --- /dev/null +++ b/userland/upstream-src/huniq/tests/tests.rs @@ -0,0 +1,35 @@ +use assert_cmd::{assert::Assert, Command}; + +#[test] +fn noargs() { + assert("", &[]).success().stdout(""); +} + +#[test] +fn duplicate_line() { + assert("a\na\na\n", &[]).success().stdout("a\n"); +} + +#[test] +fn unique_lines() { + assert("a\nb\nc\n", &[]).success().stdout("a\nb\nc\n"); +} + +#[test] +fn count_sort() { + assert("a\na\nb\n", &["-c", "-s"]) + .success() + .stdout("1 b\n2 a\n"); +} + +#[test] +fn count_sort_descending() { + assert("a\na\nb\n", &["-c", "-S"]) + .success() + .stdout("2 a\n1 b\n"); +} + +fn assert(input: &str, args: &[&str]) -> Assert { + let mut cmd = Command::cargo_bin("huniq").unwrap(); + cmd.args(args).write_stdin(input).assert() +} diff --git a/userland/upstream-src/is-terminal-0.4.17/.cargo-ok b/userland/upstream-src/is-terminal-0.4.17/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/is-terminal-0.4.17/.cargo_vcs_info.json b/userland/upstream-src/is-terminal-0.4.17/.cargo_vcs_info.json new file mode 100644 index 0000000000..a198ff270f --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "6ce920a451436433a34e4cae89318d049f8c439c" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/is-terminal-0.4.17/Cargo.lock b/userland/upstream-src/is-terminal-0.4.17/Cargo.lock new file mode 100644 index 0000000000..89044e4847 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/Cargo.lock @@ -0,0 +1,183 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "is-terminal" +version = "0.4.17" +dependencies = [ + "atty", + "hermit-abi 0.5.2", + "libc", + "rustix", + "tempfile", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/userland/upstream-src/is-terminal-0.4.17/Cargo.toml b/userland/upstream-src/is-terminal-0.4.17/Cargo.toml new file mode 100644 index 0000000000..433c55f762 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/Cargo.toml @@ -0,0 +1,80 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.63" +name = "is-terminal" +version = "0.4.17" +authors = [ + "softprops ", + "Dan Gohman ", +] +build = false +include = [ + "src", + "build.rs", + "Cargo.toml", + "COPYRIGHT", + "LICENSE*", + "/*.md", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Test whether a given stream is a terminal" +documentation = "https://docs.rs/is-terminal" +readme = "README.md" +keywords = [ + "terminal", + "tty", + "isatty", +] +categories = ["command-line-interface"] +license = "MIT" +repository = "https://github.com/sunfishcode/is-terminal" + +[lib] +name = "is_terminal" +path = "src/lib.rs" + +[dev-dependencies.atty] +version = "0.2.14" + +[target.'cfg(any(unix, target_os = "wasi"))'.dependencies.libc] +version = "0.2" + +[target.'cfg(any(unix, target_os = "wasi"))'.dev-dependencies.libc] +version = "0.2.110" + +[target.'cfg(any(unix, target_os = "wasi"))'.dev-dependencies.rustix] +version = "1.0.0" +features = ["termios"] + +[target.'cfg(not(any(windows, target_os = "hermit", target_os = "unknown")))'.dev-dependencies.rustix] +version = "1.0.0" +features = ["stdio"] + +[target.'cfg(target_os = "hermit")'.dependencies.hermit-abi] +version = "0.5.0" + +[target."cfg(windows)".dependencies.windows-sys] +version = ">=0.52, <0.62" +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_Console", +] + +[target."cfg(windows)".dev-dependencies.tempfile] +version = "3" diff --git a/userland/upstream-src/is-terminal-0.4.17/Cargo.toml.orig b/userland/upstream-src/is-terminal-0.4.17/Cargo.toml.orig new file mode 100644 index 0000000000..c80be32e62 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/Cargo.toml.orig @@ -0,0 +1,43 @@ +[package] +name = "is-terminal" +version = "0.4.17" +authors = [ + "softprops ", + "Dan Gohman " +] +description = "Test whether a given stream is a terminal" +documentation = "https://docs.rs/is-terminal" +repository = "https://github.com/sunfishcode/is-terminal" +keywords = ["terminal", "tty", "isatty"] +categories = ["command-line-interface"] +license = "MIT" +edition = "2018" +include = ["src", "build.rs", "Cargo.toml", "COPYRIGHT", "LICENSE*", "/*.md"] +rust-version = "1.63" + +[target.'cfg(any(unix, target_os = "wasi"))'.dependencies] +libc = "0.2" + +[target.'cfg(target_os = "hermit")'.dependencies] +hermit-abi = "0.5.0" + +[target.'cfg(windows)'.dependencies.windows-sys] +version = ">=0.52, <0.62" +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_Console", +] + +[dev-dependencies] +atty = "0.2.14" + +[target.'cfg(any(unix, target_os = "wasi"))'.dev-dependencies] +rustix = { version = "1.0.0", features = ["termios"] } +libc = "0.2.110" + +[target.'cfg(not(any(windows, target_os = "hermit", target_os = "unknown")))'.dev-dependencies] +rustix = { version = "1.0.0", features = ["stdio"] } + +[target.'cfg(windows)'.dev-dependencies] +tempfile = "3" diff --git a/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT b/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT-atty b/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT-atty new file mode 100644 index 0000000000..b2319d8081 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/LICENSE-MIT-atty @@ -0,0 +1,23 @@ +Portions of this project are derived from atty, which bears the following +copyright notice and permission notice: + +Copyright (c) 2015-2019 Doug Tangren + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/userland/upstream-src/is-terminal-0.4.17/README.md b/userland/upstream-src/is-terminal-0.4.17/README.md new file mode 100644 index 0000000000..0d9b4aae7e --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/README.md @@ -0,0 +1,116 @@ +
+

is-terminal

+ +

+ Test whether a given stream is a terminal +

+ +

+ Github Actions CI Status + crates.io page + docs.rs docs +

+
+ +As of Rust 1.70, most users should use the [`IsTerminal`] trait in the Rust +standard library instead of this crate. + +On Unix platforms, this crate now uses libc, so that the implementation +matches what's in std. Users wishing to use the rustix-based implementation +can use the [rustix-is-terminal] crate instead. + +[rustix-is-terminal]: https://crates.io/crates/rustix-is-terminal + +
+ +is-terminal is a simple utility that answers one question: + +> Is this a terminal? + +A "terminal", also known as a "tty", is an I/O device which may be interactive +and may support color and other special features. This crate doesn't provide +any of those features; it just answers this one question. + +On Unix-family platforms, this is effectively the same as the [`isatty`] +function for testing whether a given stream is a terminal, though it accepts +high-level stream types instead of raw file descriptors. + +On Windows, it uses a variety of techniques to determine whether the given +stream is a terminal. + +This crate is derived from [the atty crate] with [PR \#51] bug fix and +[PR \#54] port to windows-sys applied. The only additional difference is that +the atty crate only accepts stdin, stdout, or stderr, while this crate accepts +any stream. In particular, this crate does not access any stream that is not +passed to it, in accordance with [I/O safety]. + +[PR \#51]: https://github.com/softprops/atty/pull/51 +[PR \#54]: https://github.com/softprops/atty/pull/54 + +## Example + +```rust +use is_terminal::IsTerminal; + +fn main() { + if std::io::stdout().is_terminal() { + println!("Stdout is a terminal"); + } else { + println!("Stdout is not a terminal"); + } +} +``` + +## Testing + +This library is tested on both Unix-family and Windows platforms. + +To test it on a platform manually, use the provided `stdio` example program. +When run normally, it prints this: + +```bash +$ cargo run --example stdio +stdin? true +stdout? true +stderr? true +``` + +To test stdin, pipe some text to the program: + +```bash +$ cat | cargo run --example stdio +stdin? false +stdout? true +stderr? true +``` + +To test stdout, pipe the program to something: + +```bash +$ cargo run --example stdio | cat +stdin? true +stdout? false +stderr? true +``` + +To test stderr, pipe the program to something redirecting stderr: + +```bash +$ cargo run --example stdio 2>&1 | cat +stdin? true +stdout? false +stderr? false +``` + +# Minimum Supported Rust Version (MSRV) + +This crate currently works on the version of [Rust on Debian stable], which is +currently Rust 1.63. This policy may change in the future, in minor version +releases, so users using a fixed version of Rust should pin to a specific +version of this crate. + +[`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html +[the atty crate]: https://crates.io/crates/atty +[I/O safety]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md +[Rust on Debian stable]: https://packages.debian.org/stable/rust/rustc +[`IsTerminal`]: https://doc.rust-lang.org/stable/std/io/trait.IsTerminal.html diff --git a/userland/upstream-src/is-terminal-0.4.17/src/lib.rs b/userland/upstream-src/is-terminal-0.4.17/src/lib.rs new file mode 100644 index 0000000000..bf8dc6bce7 --- /dev/null +++ b/userland/upstream-src/is-terminal-0.4.17/src/lib.rs @@ -0,0 +1,395 @@ +//! is-terminal is a simple utility that answers one question: +//! +//! > Is this a terminal? +//! +//! A "terminal", also known as a "tty", is an I/O device which may be +//! interactive and may support color and other special features. This crate +//! doesn't provide any of those features; it just answers this one question. +//! +//! On Unix-family platforms, this is effectively the same as the [`isatty`] +//! function for testing whether a given stream is a terminal, though it +//! accepts high-level stream types instead of raw file descriptors. +//! +//! On Windows, it uses a variety of techniques to determine whether the +//! given stream is a terminal. +//! +//! # Example +//! +//! ```rust +//! use is_terminal::IsTerminal; +//! +//! if std::io::stdout().is_terminal() { +//! println!("stdout is a terminal") +//! } +//! ``` +//! +//! [`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html + +#![cfg_attr( + not(any( + unix, + windows, + target_os = "wasi", + target_os = "hermit", + target_os = "unknown", + // NONOS has a full std, so it uses the std file-descriptor path. + target_vendor = "nonos" + )), + no_std +)] + +#[cfg(target_os = "wasi")] +use std::os::fd::{AsFd, AsRawFd}; +#[cfg(target_os = "hermit")] +use std::os::hermit::io::AsFd; +#[cfg(unix)] +use std::os::unix::io::{AsFd, AsRawFd}; +// NONOS exposes the file-descriptor traits under os::fd, like wasi does. +#[cfg(target_vendor = "nonos")] +use std::os::fd::AsFd; +#[cfg(windows)] +use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; +#[cfg(windows)] +use windows_sys::Win32::Foundation::HANDLE; + +/// Extension trait to check whether something is a terminal. +pub trait IsTerminal { + /// Returns true if this is a terminal. + /// + /// # Example + /// + /// ``` + /// use is_terminal::IsTerminal; + /// + /// if std::io::stdout().is_terminal() { + /// println!("stdout is a terminal") + /// } + /// ``` + fn is_terminal(&self) -> bool; +} + +/// Returns `true` if `this` is a terminal. +/// +/// This is equivalent to calling `this.is_terminal()` and exists only as a +/// convenience to calling the trait method [`IsTerminal::is_terminal`] +/// without importing the trait. +/// +/// # Example +/// +/// ``` +/// if is_terminal::is_terminal(&std::io::stdout()) { +/// println!("stdout is a terminal") +/// } +/// ``` +pub fn is_terminal(this: T) -> bool { + this.is_terminal() +} + +#[cfg(not(any(windows, target_os = "unknown")))] +impl IsTerminal for Stream { + #[inline] + fn is_terminal(&self) -> bool { + #[cfg(any(unix, target_os = "wasi"))] + { + let fd = self.as_fd(); + unsafe { libc::isatty(fd.as_raw_fd()) != 0 } + } + + #[cfg(target_os = "hermit")] + { + use std::os::hermit::io::AsRawFd; + hermit_abi::isatty(self.as_fd().as_fd().as_raw_fd()) + } + + // A NONOS capsule runs attached to the terminal surface it was launched + // from, so its standard streams are interactive by construction. + #[cfg(target_vendor = "nonos")] + { + let _ = self.as_fd(); + true + } + } +} + +#[cfg(windows)] +impl IsTerminal for Stream { + #[inline] + fn is_terminal(&self) -> bool { + handle_is_console(self.as_handle()) + } +} + +// The Windows implementation here is copied from `handle_is_console` in +// library/std/src/sys/pal/windows/io.rs in Rust at revision +// e74c667a53c6368579867a74494e6fb7a7f17d13. + +#[cfg(windows)] +fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { + use windows_sys::Win32::System::Console::GetConsoleMode; + + let handle = handle.as_raw_handle(); + + // A null handle means the process has no console. + if handle.is_null() { + return false; + } + + unsafe { + let mut out = 0; + if GetConsoleMode(handle as HANDLE, &mut out) != 0 { + // False positives aren't possible. If we got a console then we definitely have a console. + return true; + } + + // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty. + msys_tty_on(handle as HANDLE) + } +} + +/// Returns true if there is an MSYS tty on the given handle. +#[cfg(windows)] +unsafe fn msys_tty_on(handle: HANDLE) -> bool { + use std::ffi::c_void; + use windows_sys::Win32::{ + Foundation::MAX_PATH, + Storage::FileSystem::{ + FileNameInfo, GetFileInformationByHandleEx, GetFileType, FILE_TYPE_PIPE, + }, + }; + + // Early return if the handle is not a pipe. + if GetFileType(handle) != FILE_TYPE_PIPE { + return false; + } + + /// Mirrors windows_sys::Win32::Storage::FileSystem::FILE_NAME_INFO, giving + /// it a fixed length that we can stack allocate + #[repr(C)] + #[allow(non_snake_case)] + struct FILE_NAME_INFO { + FileNameLength: u32, + FileName: [u16; MAX_PATH as usize], + } + let mut name_info = FILE_NAME_INFO { + FileNameLength: 0, + FileName: [0; MAX_PATH as usize], + }; + // Safety: buffer length is fixed. + let res = GetFileInformationByHandleEx( + handle, + FileNameInfo, + &mut name_info as *mut _ as *mut c_void, + std::mem::size_of::() as u32, + ); + if res == 0 { + return false; + } + + // Use `get` because `FileNameLength` can be out of range. + let s = match name_info + .FileName + .get(..name_info.FileNameLength as usize / 2) + { + None => return false, + Some(s) => s, + }; + let name = String::from_utf16_lossy(s); + // Get the file name only. + let name = name.rsplit('\\').next().unwrap_or(&name); + // This checks whether 'pty' exists in the file name, which indicates that + // a pseudo-terminal is attached. To mitigate against false positives + // (e.g., an actual file name that contains 'pty'), we also require that + // the file name begins with either the strings 'msys-' or 'cygwin-'.) + let is_msys = name.starts_with("msys-") || name.starts_with("cygwin-"); + let is_pty = name.contains("-pty"); + is_msys && is_pty +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::io::Stdin { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::io::Stdout { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::io::Stderr { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl<'a> IsTerminal for std::io::StdinLock<'a> { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl<'a> IsTerminal for std::io::StdoutLock<'a> { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl<'a> IsTerminal for std::io::StderrLock<'a> { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl<'a> IsTerminal for std::fs::File { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::process::ChildStdin { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::process::ChildStdout { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(target_os = "unknown")] +impl IsTerminal for std::process::ChildStderr { + #[inline] + fn is_terminal(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(target_os = "unknown"))] + use super::IsTerminal; + + #[test] + #[cfg(windows)] + fn stdin() { + assert_eq!( + atty::is(atty::Stream::Stdin), + std::io::stdin().is_terminal() + ) + } + + #[test] + #[cfg(windows)] + fn stdout() { + assert_eq!( + atty::is(atty::Stream::Stdout), + std::io::stdout().is_terminal() + ) + } + + #[test] + #[cfg(windows)] + fn stderr() { + assert_eq!( + atty::is(atty::Stream::Stderr), + std::io::stderr().is_terminal() + ) + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stdin() { + assert_eq!( + atty::is(atty::Stream::Stdin), + rustix::stdio::stdin().is_terminal() + ) + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stdout() { + assert_eq!( + atty::is(atty::Stream::Stdout), + rustix::stdio::stdout().is_terminal() + ) + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stderr() { + assert_eq!( + atty::is(atty::Stream::Stderr), + rustix::stdio::stderr().is_terminal() + ) + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stdin_vs_libc() { + unsafe { + assert_eq!( + libc::isatty(libc::STDIN_FILENO) != 0, + rustix::stdio::stdin().is_terminal() + ) + } + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stdout_vs_libc() { + unsafe { + assert_eq!( + libc::isatty(libc::STDOUT_FILENO) != 0, + rustix::stdio::stdout().is_terminal() + ) + } + } + + #[test] + #[cfg(any(unix, target_os = "wasi"))] + fn stderr_vs_libc() { + unsafe { + assert_eq!( + libc::isatty(libc::STDERR_FILENO) != 0, + rustix::stdio::stderr().is_terminal() + ) + } + } + + // Verify that the msys_tty_on function works with long path. + #[test] + #[cfg(windows)] + fn msys_tty_on_path_length() { + use std::{fs::File, os::windows::io::AsRawHandle}; + use windows_sys::Win32::Foundation::MAX_PATH; + + let dir = tempfile::tempdir().expect("Unable to create temporary directory"); + let file_path = dir.path().join("ten_chars_".repeat(25)); + // Ensure that the path is longer than MAX_PATH. + assert!(file_path.to_string_lossy().len() > MAX_PATH as usize); + let file = File::create(file_path).expect("Unable to create file"); + + assert!(!unsafe { crate::msys_tty_on(file.as_raw_handle()) }); + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/.cargo-ok b/userland/upstream-src/os_str_bytes-6.6.1/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/os_str_bytes-6.6.1/.cargo_vcs_info.json b/userland/upstream-src/os_str_bytes-6.6.1/.cargo_vcs_info.json new file mode 100644 index 0000000000..ae36b69561 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "7611c39da54e3255c78befcc31850097bcde67ec" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/os_str_bytes-6.6.1/COPYRIGHT b/userland/upstream-src/os_str_bytes-6.6.1/COPYRIGHT new file mode 100644 index 0000000000..65dfcfc57f --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/COPYRIGHT @@ -0,0 +1,5 @@ +Copyright (c) 2019 dylni (https://github.com/dylni) + +Licensed under the Apache License, Version 2.0 or the MIT +license , at your option. All files in this project may not be +copied, modified, or distributed except according to those terms. diff --git a/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml b/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml new file mode 100644 index 0000000000..90191e80ee --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml @@ -0,0 +1,83 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.61.0" +name = "os_str_bytes" +version = "6.6.1" +authors = ["dylni"] +exclude = [ + ".*", + "tests.rs", + "/rustfmt.toml", + "/src/bin", + "/tests", +] +description = """ +Convert between byte sequences and platform-native strings +""" +readme = "README.md" +keywords = [ + "bytes", + "osstr", + "osstring", + "path", + "windows", +] +categories = [ + "command-line-interface", + "development-tools::ffi", + "encoding", + "os", + "rust-patterns", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dylni/os_str_bytes" + +[package.metadata.docs.rs] +all-features = true +rustc-args = [ + "--cfg", + "os_str_bytes_docs_rs", +] +rustdoc-args = [ + "--cfg", + "os_str_bytes_docs_rs", +] + +[dependencies.memchr] +version = "2.4" +optional = true + +[dependencies.print_bytes] +version = "1.0" +optional = true + +[dependencies.uniquote] +version = "3.0" +optional = true + +[dev-dependencies.fastrand] +version = "2.0" + +[dev-dependencies.lazy_static] +version = "1.4" + +[features] +checked_conversions = ["conversions"] +conversions = [] +default = [ + "memchr", + "raw_os_str", +] +nightly = [] +raw_os_str = [] diff --git a/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml.orig b/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml.orig new file mode 100644 index 0000000000..841b059a9d --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/Cargo.toml.orig @@ -0,0 +1,38 @@ +[package] +name = "os_str_bytes" +version = "6.6.1" +authors = ["dylni"] +edition = "2021" +rust-version = "1.61.0" +description = """ +Convert between byte sequences and platform-native strings +""" +readme = "README.md" +repository = "https://github.com/dylni/os_str_bytes" +license = "MIT OR Apache-2.0" +keywords = ["bytes", "osstr", "osstring", "path", "windows"] +categories = ["command-line-interface", "development-tools::ffi", "encoding", "os", "rust-patterns"] +exclude = [".*", "tests.rs", "/rustfmt.toml", "/src/bin", "/tests"] + +[package.metadata.docs.rs] +all-features = true +rustc-args = ["--cfg", "os_str_bytes_docs_rs"] +rustdoc-args = ["--cfg", "os_str_bytes_docs_rs"] + +[dependencies] +memchr = { version = "2.4", optional = true } +print_bytes = { version = "1.0", optional = true } +uniquote = { version = "3.0", optional = true } + +[dev-dependencies] +fastrand = "2.0" +lazy_static = "1.4" + +[features] +default = ["memchr", "raw_os_str"] + +nightly = [] + +checked_conversions = ["conversions"] +conversions = [] +raw_os_str = [] diff --git a/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-APACHE b/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-MIT b/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-MIT new file mode 100644 index 0000000000..fd9dc88821 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 dylni (https://github.com/dylni) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/userland/upstream-src/os_str_bytes-6.6.1/README.md b/userland/upstream-src/os_str_bytes-6.6.1/README.md new file mode 100644 index 0000000000..4c676bb581 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/README.md @@ -0,0 +1,103 @@ +# OsStr Bytes + +This crate allows interacting with the data stored by [`OsStr`] and +[`OsString`], without resorting to panics or corruption for invalid UTF-8. +Thus, methods can be used that are already defined on [`[u8]`][slice] and +[`Vec`]. + +Typically, the only way to losslessly construct [`OsStr`] or [`OsString`] from +a byte sequence is to use `OsStr::new(str::from_utf8(bytes)?)`, which requires +the bytes to be valid in UTF-8. However, since this crate makes conversions +directly between the platform encoding and raw bytes, even some strings invalid +in UTF-8 can be converted. + +[![GitHub Build Status](https://github.com/dylni/os_str_bytes/workflows/build/badge.svg?branch=master)](https://github.com/dylni/os_str_bytes/actions?query=branch%3Amaster) + +## Usage + +Add the following lines to your "Cargo.toml" file: + +```toml +[dependencies] +os_str_bytes = "6.6" +``` + +See the [documentation] for available functionality and examples. + +## Rust version support + +The minimum supported Rust toolchain version depends on the platform: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TargetTarget TripleMinimum Version
Fortanix*-fortanix-*-sgxnightly (sgx_platform)
HermitCore*-*-hermitnightly (rust-toolchain.toml)
SOLID*-*-solid_asp3(-*)1.61.0
UnixUnix1.61.0
WASI*-wasi1.61.0
WebAssemblywasm32-*-unknown1.61.0
Windows*-*-windows-*1.61.0
Xous*-*-xous-*1.74 (rust-lang/rust#104101)
+ +Minor version updates may increase these version requirements. However, the +previous two Rust releases will always be supported. If the minimum Rust +version must not be increased, use a tilde requirement to prevent updating this +crate's minor version: + +```toml +[dependencies] +os_str_bytes = "~6.6" +``` + +## License + +Licensing terms are specified in [COPYRIGHT]. + +Unless you explicitly state otherwise, any contribution submitted for inclusion +in this crate, as defined in [LICENSE-APACHE], shall be licensed according to +[COPYRIGHT], without any additional terms or conditions. + +[COPYRIGHT]: https://github.com/dylni/os_str_bytes/blob/master/COPYRIGHT +[documentation]: https://docs.rs/os_str_bytes +[LICENSE-APACHE]: https://github.com/dylni/os_str_bytes/blob/master/LICENSE-APACHE +[slice]: https://doc.rust-lang.org/std/primitive.slice.html +[`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html +[`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html +[`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/common/mod.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/common/mod.rs new file mode 100644 index 0000000000..310b3730b8 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/common/mod.rs @@ -0,0 +1,49 @@ +use std::borrow::Cow; +use std::convert::Infallible; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::result; + +#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] +use std::os::fortanix_sgx as os; +#[cfg(target_os = "hermit")] +use std::os::hermit as os; +#[cfg(target_os = "solid_asp3")] +use std::os::solid as os; +#[cfg(unix)] +use std::os::unix as os; +// NONOS OsStr is byte-based like unix, and its std exposes the same +// os::unix::ffi extension traits, so the unix encoding path applies verbatim. +#[cfg(target_vendor = "nonos")] +use std::os::unix as os; +#[cfg(target_os = "wasi")] +use std::os::wasi as os; +#[cfg(target_os = "xous")] +use std::os::xous as os; + +use os::ffi::OsStrExt; +use os::ffi::OsStringExt; + +if_raw_str! { + pub(super) mod raw; +} + +pub(super) type EncodingError = Infallible; + +pub(super) type Result = result::Result; + +pub(super) fn os_str_from_bytes(string: &[u8]) -> Result> { + Ok(Cow::Borrowed(OsStrExt::from_bytes(string))) +} + +pub(super) fn os_str_to_bytes(os_string: &OsStr) -> Cow<'_, [u8]> { + Cow::Borrowed(OsStrExt::as_bytes(os_string)) +} + +pub(super) fn os_string_from_vec(string: Vec) -> Result { + Ok(OsStringExt::from_vec(string)) +} + +pub(super) fn os_string_into_vec(os_string: OsString) -> Vec { + OsStringExt::into_vec(os_string) +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/common/raw.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/common/raw.rs new file mode 100644 index 0000000000..e5e8904b00 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/common/raw.rs @@ -0,0 +1,66 @@ +use std::fmt; +use std::fmt::Formatter; + +use crate::RawOsStr; + +if_not_nightly! { + use super::Result; +} + +if_not_nightly! { + #[inline(always)] + pub(crate) const fn is_continuation(_: u8) -> bool { + false + } +} + +if_not_nightly! { + #[inline(always)] + pub(crate) fn validate_bytes(_: &[u8]) -> Result<()> { + Ok(()) + } +} + +if_not_nightly! { + #[inline(always)] + pub(crate) fn decode_code_point(_: &[u8]) -> u32 { + unreachable!(); + } +} + +pub(crate) fn ends_with(string: &[u8], suffix: &[u8]) -> bool { + string.ends_with(suffix) +} + +pub(crate) fn starts_with(string: &[u8], prefix: &[u8]) -> bool { + string.starts_with(prefix) +} + +#[allow(deprecated)] +#[cfg_attr(feature = "nightly", allow(unreachable_code))] +fn as_inner(string: &RawOsStr) -> &[u8] { + if_nightly_return! {{ + string.as_encoded_bytes() + }} + string.as_raw_bytes() +} + +pub(crate) fn debug(string: &RawOsStr, f: &mut Formatter<'_>) -> fmt::Result { + for byte in as_inner(string) { + write!(f, "\\x{:02X}", byte)?; + } + Ok(()) +} + +#[cfg(feature = "uniquote")] +pub(crate) mod uniquote { + use uniquote::Formatter; + use uniquote::Quote; + use uniquote::Result; + + use crate::RawOsStr; + + pub(crate) fn escape(string: &RawOsStr, f: &mut Formatter<'_>) -> Result { + super::as_inner(string).escape(f) + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/iter.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/iter.rs new file mode 100644 index 0000000000..265ec5fde2 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/iter.rs @@ -0,0 +1,117 @@ +//! Iterators provided by this crate. + +#![cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] + +use std::fmt; +use std::fmt::Debug; +use std::fmt::Formatter; +use std::iter::FusedIterator; +use std::mem; + +use super::pattern::Encoded; +use super::Pattern; +use super::RawOsStr; + +// [memchr::memmem::FindIter] is not currently used, since this struct would +// become self-referential. Additionally, that iterator does not implement +// [DoubleEndedIterator], and its implementation would likely require +// significant changes to implement that trait. +/// The iterator returned by [`RawOsStr::split`]. +pub struct RawSplit<'a, P> +where + P: Pattern, +{ + string: Option<&'a RawOsStr>, + pat: P::__Encoded, +} + +impl<'a, P> RawSplit<'a, P> +where + P: Pattern, +{ + #[track_caller] + pub(super) fn new(string: &'a RawOsStr, pat: P) -> Self { + let pat = pat.__encode(); + assert!( + !pat.__get().is_empty(), + "cannot split using an empty pattern", + ); + Self { + string: Some(string), + pat, + } + } +} + +macro_rules! impl_next { + ( $self:ident , $split_method:ident , $swap:expr ) => {{ + $self + .string? + .$split_method(&$self.pat) + .map(|(mut substring, mut string)| { + if $swap { + mem::swap(&mut substring, &mut string); + } + $self.string = Some(string); + substring + }) + .or_else(|| $self.string.take()) + }}; +} + +impl

Clone for RawSplit<'_, P> +where + P: Pattern, +{ + #[inline] + fn clone(&self) -> Self { + Self { + string: self.string, + pat: self.pat.clone(), + } + } +} + +impl

Debug for RawSplit<'_, P> +where + P: Pattern, +{ + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("RawSplit") + .field("string", &self.string) + .field("pat", &self.pat) + .finish() + } +} + +impl

DoubleEndedIterator for RawSplit<'_, P> +where + P: Pattern, +{ + fn next_back(&mut self) -> Option { + impl_next!(self, rsplit_once_raw, true) + } +} + +impl

FusedIterator for RawSplit<'_, P> where P: Pattern {} + +impl<'a, P> Iterator for RawSplit<'a, P> +where + P: Pattern, +{ + type Item = &'a RawOsStr; + + #[inline] + fn last(mut self) -> Option { + self.next_back() + } + + fn next(&mut self) -> Option { + impl_next!(self, split_once_raw, false) + } +} + +/// A temporary type alias providing backward compatibility. +#[deprecated(since = "6.6.0", note = "use `RawSplit` instead")] +pub type Split<'a, P> = RawSplit<'a, P>; diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/lib.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/lib.rs new file mode 100644 index 0000000000..c724b0c09c --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/lib.rs @@ -0,0 +1,1189 @@ +//! This crate allows interacting with the data stored by [`OsStr`] and +//! [`OsString`], without resorting to panics or corruption for invalid UTF-8. +//! Thus, methods can be used that are already defined on [`[u8]`][slice] and +//! [`Vec`]. +//! +//! Typically, the only way to losslessly construct [`OsStr`] or [`OsString`] +//! from a byte sequence is to use `OsStr::new(str::from_utf8(bytes)?)`, which +//! requires the bytes to be valid in UTF-8. However, since this crate makes +//! conversions directly between the platform encoding and raw bytes, even some +//! strings invalid in UTF-8 can be converted. +//! +//! In most cases, [`RawOsStr`] and [`RawOsString`] should be used. +//! [`OsStrBytes`] and [`OsStringBytes`] provide lower-level APIs that are +//! easier to misuse. +//! +//! # Encoding +//! +//! The encoding of bytes returned or accepted by methods of this crate is +//! intentionally left unspecified. It may vary for different platforms, so +//! defining it would run contrary to the goal of generic string handling. +//! However, the following invariants will always be upheld: +//! +//! - The encoding will be compatible with UTF-8. In particular, splitting an +//! encoded byte sequence by a UTF-8–encoded character always produces +//! other valid byte sequences. They can be re-encoded without error using +//! [`RawOsString::into_os_string`] and similar methods. +//! +//! - All characters valid in platform strings are representable. [`OsStr`] and +//! [`OsString`] can always be losslessly reconstructed from extracted bytes. +//! +//! Note that the chosen encoding may not match how Rust stores these strings +//! internally, which is undocumented. For instance, the result of calling +//! [`OsStr::len`] will not necessarily match the number of bytes this crate +//! uses to represent the same string. +//! +//! Additionally, concatenation may yield unexpected results without a UTF-8 +//! separator. If two platform strings need to be concatenated, the only safe +//! way to do so is using [`OsString::push`]. This limitation also makes it +//! undesirable to use the bytes in interchange. +//! +//! Since this encoding can change between versions and platforms, it should +//! not be used for storage. The standard library provides implementations of +//! [`OsStrExt`] and [`OsStringExt`] for various platforms, which should be +//! preferred for that use case. +//! +//! # User Input +//! +//! Traits in this crate should ideally not be used to convert byte sequences +//! that did not originate from [`OsStr`] or a related struct. The encoding +//! used by this crate is an implementation detail, so it does not make sense +//! to expose it to users. +//! +//! Crate [bstr] offers some useful alternative methods, such as +//! [`ByteSlice::to_os_str`] and [`ByteVec::into_os_string`], that are meant +//! for user input. But, they reject some byte sequences used to represent +//! valid platform strings, which would be undesirable for reliable path +//! handling. They are best used only when accepting unknown input. +//! +//! This crate is meant to help when you already have an instance of [`OsStr`] +//! and need to modify the data in a lossless way. +//! +//! # Features +//! +//! These features are optional and can be enabled or disabled in a +//! "Cargo.toml" file. +//! +//! ### Default Features +//! +//! - **memchr** - +//! Changes the implementation to use crate [memchr] for better performance. +//! This feature is useless when "raw\_os\_str" is disabled. +//! +//! For more information, see [`RawOsStr`][memchr complexity]. +//! +//! - **raw\_os\_str** - +//! Provides: +//! - [`iter`] +//! - [`Pattern`] +//! - [`OsStrBytesExt`] +//! - [`RawOsStr`] +//! - [`RawOsStrCow`] +//! - [`RawOsString`] +//! +//! ### Optional Features +//! +//! - **checked\_conversions** - +//! Provides: +//! - [`EncodingError`] +//! - [`OsStrBytes::from_raw_bytes`] +//! - [`OsStringBytes::from_raw_vec`] +//! - [`RawOsStr::cow_from_raw_bytes`] +//! - [`RawOsString::from_raw_vec`] +//! +//! Because this feature should not be used in libraries, the +//! "OS_STR_BYTES_CHECKED_CONVERSIONS" environment variable must be defined +//! during compilation. +//! +//! - **conversions** - +//! Provides methods that require encoding conversion and may be expensive: +//! - [`OsStrBytesExt::ends_with_os`] +//! - [`OsStrBytesExt::starts_with_os`] +//! - [`RawOsStr::assert_cow_from_raw_bytes`] +//! - [`RawOsStr::ends_with_os`] +//! - [`RawOsStr::starts_with_os`] +//! - [`RawOsStr::to_raw_bytes`] +//! - [`RawOsString::assert_from_raw_vec`] +//! - [`RawOsString::into_raw_vec`] +//! - [`OsStrBytes`] +//! - [`OsStringBytes`] +//! +//! - **print\_bytes** - +//! Provides implementations of [`print_bytes::ToBytes`] for [`RawOsStr`] and +//! [`RawOsString`]. +//! +//! - **uniquote** - +//! Provides implementations of [`uniquote::Quote`] for [`RawOsStr`] and +//! [`RawOsString`]. +//! +//! ### Nightly Features +//! +//! These features are unstable, since they rely on unstable Rust features. +//! +//! - **nightly** - +//! Changes the implementation to use the ["os\_str\_bytes" nightly +//! feature][feature] and provides: +//! - [`RawOsStr::as_encoded_bytes`] +//! - [`RawOsStr::as_os_str`] +//! - [`RawOsStr::from_encoded_bytes_unchecked`] +//! - [`RawOsStr::from_os_str`] +//! - [`RawOsString::from_encoded_vec_unchecked`] +//! - [`RawOsString::into_encoded_vec`] +//! - additional trait implementations for [`RawOsStr`] and [`RawOsString`] +//! +//! When applicable, a "Nightly Notes" section will be added to documentation +//! descriptions, indicating differences when this feature is enabled. +//! However, it will not cause any breaking changes. +//! +//! This feature will cause memory leaks for some newly deprecated methods. +//! Therefore, it is not recommended to use this feature until the next major +//! version, when those methods will be removed. However, it can be used to +//! prepare for upgrading and determine impact of the new feature. +//! +//! Because this feature should not be used in libraries, the +//! "OS_STR_BYTES_NIGHTLY" environment variable must be defined during +//! compilation. +//! +//! # Implementation +//! +//! Some methods return [`Cow`] to account for platform differences. However, +//! no guarantee is made that the same variant of that enum will always be +//! returned for the same platform. Whichever can be constructed most +//! efficiently will be returned. +//! +//! All traits are [sealed], meaning that they can only be implemented by this +//! crate. Otherwise, backward compatibility would be more difficult to +//! maintain for new features. +//! +//! # Complexity +//! +//! Conversion method complexities will vary based on what functionality is +//! available for the platform. At worst, they will all be linear, but some can +//! take constant time. For example, [`RawOsString::into_os_string`] might be +//! able to reuse its allocation. +//! +//! # Examples +//! +//! ``` +//! # use std::io; +//! # +//! # #[cfg(feature = "raw_os_str")] +//! # { +//! # #[cfg(any())] +//! use std::env; +//! use std::fs; +//! +//! use os_str_bytes::RawOsStr; +//! +//! # mod env { +//! # use std::env; +//! # use std::ffi::OsString; +//! # +//! # pub fn args_os() -> impl Iterator { +//! # let mut file = env::temp_dir(); +//! # file.push("os_str_bytes\u{E9}.txt"); +//! # return vec![OsString::new(), file.into_os_string()].into_iter(); +//! # } +//! # } +//! # +//! for file in env::args_os().skip(1) { +//! if !RawOsStr::new(&file).starts_with('-') { +//! let string = "Hello, world!"; +//! fs::write(&file, string)?; +//! assert_eq!(string, fs::read_to_string(file)?); +//! } +//! } +//! # } +//! # +//! # Ok::<_, io::Error>(()) +//! ``` +//! +//! [bstr]: https://crates.io/crates/bstr +//! [`ByteSlice::to_os_str`]: https://docs.rs/bstr/0.2.12/bstr/trait.ByteSlice.html#method.to_os_str +//! [`ByteVec::into_os_string`]: https://docs.rs/bstr/0.2.12/bstr/trait.ByteVec.html#method.into_os_string +//! [feature]: https://doc.rust-lang.org/unstable-book/library-features/os-str-bytes.html +//! [memchr complexity]: RawOsStr#complexity +//! [memchr]: https://crates.io/crates/memchr +//! [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt +//! [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt +//! [print\_bytes]: https://crates.io/crates/print_bytes +//! [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed + +#![cfg_attr(not(feature = "checked_conversions"), allow(deprecated))] +// Only require a nightly compiler when building documentation for docs.rs. +// This is a private option that should not be used. +// https://github.com/rust-lang/docs.rs/issues/147#issuecomment-389544407 +// https://github.com/dylni/os_str_bytes/issues/2 +#![cfg_attr(os_str_bytes_docs_rs, feature(doc_cfg))] +// Nightly is also currently required for the SGX platform. +#![cfg_attr( + all(target_vendor = "fortanix", target_env = "sgx"), + feature(sgx_platform) +)] +#![warn(unused_results)] + +use std::borrow::Cow; +use std::error::Error; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; +use std::path::Path; +use std::path::PathBuf; +use std::result; + +macro_rules! if_checked_conversions { + ( $($item:item)+ ) => { + $( + #[cfg(feature = "checked_conversions")] + $item + )+ + }; +} + +#[cfg(not(os_str_bytes_docs_rs))] +if_checked_conversions! { + const _: &str = env!( + "OS_STR_BYTES_CHECKED_CONVERSIONS", + "The 'OS_STR_BYTES_CHECKED_CONVERSIONS' environment variable must be \ + defined to use the 'checked_conversions' feature.", + ); +} + +macro_rules! if_nightly { + ( $($item:item)+ ) => { + $( + #[cfg(feature = "nightly")] + $item + )+ + }; +} + +#[cfg(not(os_str_bytes_docs_rs))] +if_nightly! { + const _: &str = env!( + "OS_STR_BYTES_NIGHTLY", + "The 'OS_STR_BYTES_NIGHTLY' environment variable must be defined to \ + use the 'nightly' feature.", + ); +} + +#[rustfmt::skip] +macro_rules! deprecated_checked_conversion { + ( $message:expr , $item:item ) => { + #[cfg_attr( + not(feature = "checked_conversions"), + deprecated = $message + )] + $item + }; +} + +#[rustfmt::skip] +macro_rules! deprecated_conversions { + ( $($item:item)+ ) => { + $( + #[cfg_attr( + not(feature = "conversions"), + deprecated = "enable the 'conversions' feature" + )] + $item + )+ + }; +} + +macro_rules! if_raw_str { + ( $($item:item)+ ) => { + $( + #[cfg(feature = "raw_os_str")] + $item + )+ + }; +} + +if_raw_str! { + macro_rules! if_not_nightly { + ( $($item:item)+ ) => { + $( + #[cfg(not(feature = "nightly"))] + $item + )+ + }; + } + + macro_rules! if_nightly_return { + ( $nightly_value:block $($not_nightly_token:tt)* ) => { + #[cfg(feature = "nightly")] + return $nightly_value; + #[cfg(not(feature = "nightly"))] + { + $($not_nightly_token)* + } + }; + } +} + +if_raw_str! { + if_nightly! { + macro_rules! if_conversions { + ( $($item:item)+ ) => { + $( + #[cfg(feature = "conversions")] + $item + )+ + }; + } + } +} + +macro_rules! expect_encoded { + ( $result:expr ) => { + $result.expect("invalid raw bytes") + }; +} + +#[cfg_attr( + all(target_family = "wasm", target_os = "unknown"), + path = "wasm/mod.rs" +)] +#[cfg_attr(windows, path = "windows/mod.rs")] +#[cfg_attr( + not(any(all(target_family = "wasm", target_os = "unknown"), windows)), + path = "common/mod.rs" +)] +mod imp; + +#[cfg(any( + all( + feature = "raw_os_str", + any( + feature = "nightly", + all(target_family = "wasm", target_os = "unknown"), + ), + ), + windows, +))] +mod util; + +if_raw_str! { + pub mod iter; + + mod pattern; + pub use pattern::Pattern; + + mod raw_str; + pub use raw_str::RawOsStr; + pub use raw_str::RawOsStrCow; + pub use raw_str::RawOsString; +} + +deprecated_checked_conversion! { + "use `OsStrBytes::assert_from_raw_bytes` or \ + `OsStringBytes::assert_from_raw_vec` instead, or enable the \ + 'checked_conversions' feature", + /// The error that occurs when a byte sequence is not representable in the + /// platform encoding. + /// + /// [`Result::unwrap`] should almost always be called on results containing + /// this error. It should be known whether or not byte sequences are + /// properly encoded for the platform, since [the module-level + /// documentation][encoding] discourages using encoded bytes in + /// interchange. Results are returned primarily to make panicking behavior + /// explicit. + /// + /// On Unix, this error is never returned, but [`OsStrExt`] or + /// [`OsStringExt`] should be used instead if that needs to be guaranteed. + /// + /// [encoding]: self#encoding + /// [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt + /// [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt + /// [`Result::unwrap`]: ::std::result::Result::unwrap + #[derive(Clone, Debug, Eq, PartialEq)] + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + pub struct EncodingError(imp::EncodingError); +} + +impl Display for EncodingError { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Error for EncodingError {} + +type Result = result::Result; + +fn from_raw_bytes<'a, S>(string: S) -> imp::Result> +where + S: Into>, +{ + match string.into() { + Cow::Borrowed(string) => imp::os_str_from_bytes(string), + Cow::Owned(string) => imp::os_string_from_vec(string).map(Cow::Owned), + } +} + +fn cow_os_str_into_path(string: Cow<'_, OsStr>) -> Cow<'_, Path> { + match string { + Cow::Borrowed(string) => Cow::Borrowed(Path::new(string)), + Cow::Owned(string) => Cow::Owned(string.into()), + } +} + +deprecated_conversions! { + /// A platform agnostic variant of [`OsStrExt`]. + /// + /// For more information, see [the module-level documentation][module]. + /// + /// [module]: self + /// [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + pub trait OsStrBytes: private::Sealed + ToOwned { + /// Converts a byte string into an equivalent platform-native string. + /// + /// # Panics + /// + /// Panics if the string is not valid for the [unspecified encoding] + /// used by this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// use std::ffi::OsStr; + /// # use std::io; + /// + /// use os_str_bytes::OsStrBytes; + /// + /// let os_string = env::current_exe()?; + /// let os_bytes = os_string.to_raw_bytes(); + /// assert_eq!(os_string, OsStr::assert_from_raw_bytes(os_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: self#encoding + #[must_use = "method should not be used for validation"] + #[track_caller] + fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self> + where + S: Into>; + + deprecated_checked_conversion! { + "use `assert_from_raw_bytes` instead, or enable the \ + 'checked_conversions' feature", + /// Converts a byte string into an equivalent platform-native + /// string. + /// + /// [`assert_from_raw_bytes`] should almost always be used instead. + /// For more information, see [`EncodingError`]. + /// + /// # Errors + /// + /// See documentation for [`EncodingError`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// use std::ffi::OsStr; + /// # use std::io; + /// + /// use os_str_bytes::OsStrBytes; + /// + /// let os_string = env::current_exe()?; + /// let os_bytes = os_string.to_raw_bytes(); + /// assert_eq!(os_string, OsStr::from_raw_bytes(os_bytes).unwrap()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_from_raw_bytes`]: Self::assert_from_raw_bytes + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + fn from_raw_bytes<'a, S>(string: S) -> Result> + where + S: Into>; + } + + /// Converts a platform-native string into an equivalent byte string. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytes; + /// + /// let string = "foobar"; + /// let os_string = OsStr::new(string); + /// assert_eq!(string.as_bytes(), &*os_string.to_raw_bytes()); + /// ``` + /// + /// [unspecified encoding]: self#encoding + #[must_use] + fn to_raw_bytes(&self) -> Cow<'_, [u8]>; + } + + #[cfg_attr(not(feature = "conversions"), allow(useless_deprecated))] + impl OsStrBytes for OsStr { + #[inline] + fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self> + where + S: Into>, + { + expect_encoded!(from_raw_bytes(string)) + } + + #[inline] + fn from_raw_bytes<'a, S>(string: S) -> Result> + where + S: Into>, + { + from_raw_bytes(string).map_err(EncodingError) + } + + #[inline] + fn to_raw_bytes(&self) -> Cow<'_, [u8]> { + imp::os_str_to_bytes(self) + } + } + + #[cfg_attr(not(feature = "conversions"), allow(useless_deprecated))] + impl OsStrBytes for Path { + #[inline] + fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self> + where + S: Into>, + { + cow_os_str_into_path(OsStr::assert_from_raw_bytes(string)) + } + + #[inline] + fn from_raw_bytes<'a, S>(string: S) -> Result> + where + S: Into>, + { + OsStr::from_raw_bytes(string).map(cow_os_str_into_path) + } + + #[inline] + fn to_raw_bytes(&self) -> Cow<'_, [u8]> { + self.as_os_str().to_raw_bytes() + } + } +} + +if_raw_str! { + if_nightly! { + /// An extension trait providing methods from [`RawOsStr`]. + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(all(feature = "nightly", feature = "raw_os_str"))) + )] + pub trait OsStrBytesExt: OsStrBytes { + /// Equivalent to [`str::contains`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert!(os_string.contains("oo")); + /// assert!(!os_string.contains("of")); + /// ``` + #[must_use] + fn contains

(&self, pat: P) -> bool + where + P: Pattern; + + /// Equivalent to [`str::ends_with`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert!(os_string.ends_with("bar")); + /// assert!(!os_string.ends_with("foo")); + /// ``` + #[must_use] + fn ends_with

(&self, pat: P) -> bool + where + P: Pattern; + + if_conversions! { + /// Equivalent to [`str::ends_with`] but accepts this type for + /// the pattern. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert!(os_string.ends_with_os(OsStr::new("bar"))); + /// assert!(!os_string.ends_with_os(OsStr::new("foo"))); + /// ``` + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "conversions")) + )] + #[must_use] + fn ends_with_os(&self, pat: &Self) -> bool; + } + + /// Equivalent to [`str::find`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert_eq!(Some(1), os_string.find("o")); + /// assert_eq!(None, os_string.find("of")); + /// ``` + #[must_use] + fn find

(&self, pat: P) -> Option + where + P: Pattern; + + /// Equivalent to [`str::rfind`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert_eq!(Some(2), os_string.rfind("o")); + /// assert_eq!(None, os_string.rfind("of")); + /// ``` + #[must_use] + fn rfind

(&self, pat: P) -> Option + where + P: Pattern; + + /// Equivalent to [`str::rsplit_once`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert_eq!( + /// Some((OsStr::new("fo"), OsStr::new("bar"))), + /// os_string.rsplit_once("o"), + /// ); + /// assert_eq!(None, os_string.rsplit_once("of")); + /// ``` + #[must_use] + fn rsplit_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern; + + /// Equivalent to [`str::split_at`]. + /// + /// # Panics + /// + /// Panics if the index is not a [valid boundary]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert_eq!( + /// ((OsStr::new("fo"), OsStr::new("obar"))), + /// os_string.split_at(2), + /// ); + /// ``` + /// + /// [valid boundary]: RawOsStr#indices + #[must_use] + #[track_caller] + fn split_at(&self, mid: usize) -> (&Self, &Self); + + /// Equivalent to [`str::split_once`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert_eq!( + /// Some((OsStr::new("f"), OsStr::new("obar"))), + /// os_string.split_once("o"), + /// ); + /// assert_eq!(None, os_string.split_once("of")); + /// ``` + #[must_use] + fn split_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern; + + /// Equivalent to [`str::starts_with`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert!(os_string.starts_with("foo")); + /// assert!(!os_string.starts_with("bar")); + /// ``` + #[must_use] + fn starts_with

(&self, pat: P) -> bool + where + P: Pattern; + + if_conversions! { + /// Equivalent to [`str::starts_with`] but accepts this type + /// for the pattern. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("foobar"); + /// assert!(os_string.starts_with_os(OsStr::new("foo"))); + /// assert!(!os_string.starts_with_os(OsStr::new("bar"))); + /// ``` + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "conversions")) + )] + #[must_use] + fn starts_with_os(&self, pat: &Self) -> bool; + } + + /// Equivalent to [`str::strip_prefix`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("111foo1bar111"); + /// assert_eq!( + /// Some(OsStr::new("11foo1bar111")), + /// os_string.strip_prefix("1"), + /// ); + /// assert_eq!(None, os_string.strip_prefix("o")); + /// ``` + #[must_use] + fn strip_prefix

(&self, pat: P) -> Option<&Self> + where + P: Pattern; + + /// Equivalent to [`str::strip_suffix`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("111foo1bar111"); + /// assert_eq!( + /// Some(OsStr::new("111foo1bar11")), + /// os_string.strip_suffix("1"), + /// ); + /// assert_eq!(None, os_string.strip_suffix("o")); + /// ``` + #[must_use] + fn strip_suffix

(&self, pat: P) -> Option<&Self> + where + P: Pattern; + + /// Equivalent to [`str::trim_end_matches`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("111foo1bar111"); + /// assert_eq!("111foo1bar", os_string.trim_end_matches("1")); + /// assert_eq!("111foo1bar111", os_string.trim_end_matches("o")); + /// ``` + #[must_use] + fn trim_end_matches

(&self, pat: P) -> &Self + where + P: Pattern; + + /// Equivalent to [`str::trim_matches`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("111foo1bar111"); + /// assert_eq!("foo1bar", os_string.trim_matches("1")); + /// assert_eq!("111foo1bar111", os_string.trim_matches("o")); + /// ``` + #[must_use] + fn trim_matches

(&self, pat: P) -> &Self + where + P: Pattern; + + /// Equivalent to [`str::trim_start_matches`]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use os_str_bytes::OsStrBytesExt; + /// + /// let os_string = OsStr::new("111foo1bar111"); + /// assert_eq!("foo1bar111", os_string.trim_start_matches("1")); + /// assert_eq!("111foo1bar111", os_string.trim_start_matches("o")); + /// ``` + #[must_use] + fn trim_start_matches

(&self, pat: P) -> &Self + where + P: Pattern; + } + + impl OsStrBytesExt for OsStr { + #[inline] + fn contains

(&self, pat: P) -> bool + where + P: Pattern, + { + RawOsStr::from_os_str(self).contains(pat) + } + + #[inline] + fn ends_with

(&self, pat: P) -> bool + where + P: Pattern, + { + RawOsStr::from_os_str(self).ends_with(pat) + } + + if_conversions! { + #[inline] + fn ends_with_os(&self, pat: &Self) -> bool { + RawOsStr::from_os_str(self) + .ends_with_os(RawOsStr::from_os_str(pat)) + } + } + + #[inline] + fn find

(&self, pat: P) -> Option + where + P: Pattern, + { + RawOsStr::from_os_str(self).find(pat) + } + + #[inline] + fn rfind

(&self, pat: P) -> Option + where + P: Pattern, + { + RawOsStr::from_os_str(self).rfind(pat) + } + + #[inline] + fn rsplit_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern, + { + RawOsStr::from_os_str(self) + .rsplit_once(pat) + .map(|(prefix, suffix)| { + (prefix.as_os_str(), suffix.as_os_str()) + }) + } + + #[inline] + fn split_at(&self, mid: usize) -> (&Self, &Self) { + let (prefix, suffix) = + RawOsStr::from_os_str(self).split_at(mid); + (prefix.as_os_str(), suffix.as_os_str()) + } + + #[inline] + fn split_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern, + { + RawOsStr::from_os_str(self) + .split_once(pat) + .map(|(prefix, suffix)| { + (prefix.as_os_str(), suffix.as_os_str()) + }) + } + + #[inline] + fn starts_with

(&self, pat: P) -> bool + where + P: Pattern, + { + RawOsStr::from_os_str(self).starts_with(pat) + } + + if_conversions! { + #[inline] + fn starts_with_os(&self, pat: &Self) -> bool { + RawOsStr::from_os_str(self) + .starts_with_os(RawOsStr::from_os_str(pat)) + } + } + + #[inline] + fn strip_prefix

(&self, pat: P) -> Option<&Self> + where + P: Pattern, + { + RawOsStr::from_os_str(self) + .strip_prefix(pat) + .map(RawOsStr::as_os_str) + } + + #[inline] + fn strip_suffix

(&self, pat: P) -> Option<&Self> + where + P: Pattern, + { + RawOsStr::from_os_str(self) + .strip_suffix(pat) + .map(RawOsStr::as_os_str) + } + + #[inline] + fn trim_end_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + RawOsStr::from_os_str(self).trim_end_matches(pat).as_os_str() + } + + #[inline] + fn trim_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + RawOsStr::from_os_str(self).trim_matches(pat).as_os_str() + } + + #[inline] + fn trim_start_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + RawOsStr::from_os_str(self).trim_start_matches(pat).as_os_str() + } + } + } +} + +deprecated_conversions! { + /// A platform agnostic variant of [`OsStringExt`]. + /// + /// For more information, see [the module-level documentation][module]. + /// + /// [module]: self + /// [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(any(feature = "conversions"))))] + pub trait OsStringBytes: private::Sealed + Sized { + /// Converts a byte string into an equivalent platform-native string. + /// + /// # Panics + /// + /// Panics if the string is not valid for the [unspecified encoding] + /// used by this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// use std::ffi::OsString; + /// # use std::io; + /// + /// use os_str_bytes::OsStringBytes; + /// + /// let os_string = env::current_exe()?; + /// let os_bytes = os_string.clone().into_raw_vec(); + /// assert_eq!(os_string, OsString::assert_from_raw_vec(os_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: self#encoding + #[must_use = "method should not be used for validation"] + #[track_caller] + fn assert_from_raw_vec(string: Vec) -> Self; + + deprecated_checked_conversion! { + "use `assert_from_raw_vec` instead, or enable the \ + 'checked_conversions' feature", + /// Converts a byte string into an equivalent platform-native + /// string. + /// + /// [`assert_from_raw_vec`] should almost always be used instead. + /// For more information, see [`EncodingError`]. + /// + /// # Errors + /// + /// See documentation for [`EncodingError`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// use std::ffi::OsString; + /// # use std::io; + /// + /// use os_str_bytes::OsStringBytes; + /// + /// let os_string = env::current_exe()?; + /// let os_bytes = os_string.clone().into_raw_vec(); + /// assert_eq!( + /// os_string, + /// OsString::from_raw_vec(os_bytes).unwrap(), + /// ); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_from_raw_vec`]: Self::assert_from_raw_vec + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + fn from_raw_vec(string: Vec) -> Result; + } + + /// Converts a platform-native string into an equivalent byte string. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsString; + /// + /// use os_str_bytes::OsStringBytes; + /// + /// let string = "foobar".to_owned(); + /// let os_string: OsString = string.clone().into(); + /// assert_eq!(string.into_bytes(), os_string.into_raw_vec()); + /// ``` + /// + /// [unspecified encoding]: self#encoding + #[must_use] + fn into_raw_vec(self) -> Vec; + } + + #[cfg_attr(not(feature = "conversions"), allow(useless_deprecated))] + impl OsStringBytes for OsString { + #[inline] + fn assert_from_raw_vec(string: Vec) -> Self { + expect_encoded!(imp::os_string_from_vec(string)) + } + + #[inline] + fn from_raw_vec(string: Vec) -> Result { + imp::os_string_from_vec(string).map_err(EncodingError) + } + + #[inline] + fn into_raw_vec(self) -> Vec { + imp::os_string_into_vec(self) + } + } + + #[cfg_attr(not(feature = "conversions"), allow(useless_deprecated))] + impl OsStringBytes for PathBuf { + #[inline] + fn assert_from_raw_vec(string: Vec) -> Self { + OsString::assert_from_raw_vec(string).into() + } + + #[inline] + fn from_raw_vec(string: Vec) -> Result { + OsString::from_raw_vec(string).map(Into::into) + } + + #[inline] + fn into_raw_vec(self) -> Vec { + self.into_os_string().into_raw_vec() + } + } +} + +mod private { + use std::ffi::OsStr; + use std::ffi::OsString; + use std::path::Path; + use std::path::PathBuf; + + if_raw_str! { + use std::borrow::Cow; + + use super::RawOsStr; + } + + pub trait Sealed {} + + impl Sealed for char {} + impl Sealed for OsStr {} + impl Sealed for OsString {} + impl Sealed for Path {} + impl Sealed for PathBuf {} + impl Sealed for &str {} + impl Sealed for &String {} + + if_raw_str! { + impl Sealed for Cow<'_, RawOsStr> {} + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/pattern.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/pattern.rs new file mode 100644 index 0000000000..11f86bf31d --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/pattern.rs @@ -0,0 +1,71 @@ +use std::fmt::Debug; + +use super::private; + +pub trait Encoded { + fn __get(&self) -> &[u8]; +} + +#[derive(Clone, Debug)] +pub struct EncodedChar { + buffer: [u8; 4], + length: usize, +} + +impl Encoded for EncodedChar { + fn __get(&self) -> &[u8] { + &self.buffer[..self.length] + } +} + +impl Encoded for &str { + fn __get(&self) -> &[u8] { + self.as_bytes() + } +} + +/// Allows a type to be used for searching by [`RawOsStr`] and [`RawOsString`]. +/// +/// This trait is very similar to [`str::pattern::Pattern`], but its methods +/// are private and it is implemented for different types. +/// +/// [`RawOsStr`]: super::RawOsStr +/// [`RawOsString`]: super::RawOsString +/// [`str::pattern::Pattern`]: ::std::str::pattern::Pattern +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] +pub trait Pattern: private::Sealed { + #[doc(hidden)] + type __Encoded: Clone + Debug + Encoded; + + #[doc(hidden)] + fn __encode(self) -> Self::__Encoded; +} + +impl Pattern for char { + type __Encoded = EncodedChar; + + fn __encode(self) -> Self::__Encoded { + let mut encoded = EncodedChar { + buffer: [0; 4], + length: 0, + }; + encoded.length = self.encode_utf8(&mut encoded.buffer).len(); + encoded + } +} + +impl Pattern for &str { + type __Encoded = Self; + + fn __encode(self) -> Self::__Encoded { + self + } +} + +impl<'a> Pattern for &'a String { + type __Encoded = <&'a str as Pattern>::__Encoded; + + fn __encode(self) -> Self::__Encoded { + (**self).__encode() + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/raw_str.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/raw_str.rs new file mode 100644 index 0000000000..9b1f8bcd23 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/raw_str.rs @@ -0,0 +1,2218 @@ +#![cfg_attr(feature = "nightly", allow(unreachable_code))] + +use std::borrow::Borrow; +use std::borrow::Cow; +use std::borrow::ToOwned; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::fmt; +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Formatter; +use std::mem; +use std::ops::Deref; +use std::ops::Index; +use std::ops::Range; +use std::ops::RangeFrom; +use std::ops::RangeFull; +use std::ops::RangeInclusive; +use std::ops::RangeTo; +use std::ops::RangeToInclusive; +use std::result; +use std::str; + +#[cfg(feature = "memchr")] +use memchr::memmem::find; +#[cfg(feature = "memchr")] +use memchr::memmem::rfind; + +use super::imp; +use super::imp::raw; +use super::iter::RawSplit; +use super::pattern::Encoded as EncodedPattern; +use super::private; +use super::Pattern; + +if_checked_conversions! { + use super::EncodingError; + use super::Result; +} + +if_nightly! { + use super::util; +} + +#[cfg(not(feature = "memchr"))] +fn find(string: &[u8], pat: &[u8]) -> Option { + (0..=string.len().checked_sub(pat.len())?) + .find(|&x| string[x..].starts_with(pat)) +} + +#[cfg(not(feature = "memchr"))] +fn rfind(string: &[u8], pat: &[u8]) -> Option { + (pat.len()..=string.len()) + .rfind(|&x| string[..x].ends_with(pat)) + .map(|x| x - pat.len()) +} + +#[allow(clippy::missing_safety_doc)] +unsafe trait TransmuteBox { + fn transmute_box(self: Box) -> Box + where + R: ?Sized + TransmuteBox, + { + let value = Box::into_raw(self); + // SAFETY: This trait is only implemented for types that can be + // transmuted. + unsafe { Box::from_raw(mem::transmute_copy(&value)) } + } +} + +// SAFETY: This struct has a layout that makes this operation safe. +unsafe impl TransmuteBox for RawOsStr {} +unsafe impl TransmuteBox for [u8] {} + +#[inline] +fn leak_cow(string: Cow<'_, RawOsStr>) -> &'_ RawOsStr { + match string { + Cow::Borrowed(string) => string, + #[cfg_attr(not(feature = "nightly"), allow(unused_variables))] + Cow::Owned(string) => { + if_nightly_return! {{ + Box::leak(string.into_box()) + }} + unreachable!(); + } + } +} + +/// A container for borrowed byte strings converted by this crate. +/// +/// This wrapper is intended to prevent violating the invariants of the +/// [unspecified encoding] used by this crate and minimize encoding +/// conversions. +/// +/// # Indices +/// +/// Methods of this struct that accept indices require that the index lie on a +/// UTF-8 boundary. Although it is possible to manipulate platform strings +/// based on other indices, this crate currently does not support them for +/// slicing methods. They would add significant complication to the +/// implementation and are generally not necessary. However, all indices +/// returned by this struct can be used for slicing. +/// +/// On Unix, all indices are permitted, to avoid false positives. However, +/// relying on this implementation detail is discouraged. Platform-specific +/// indices are error-prone. +/// +/// # Complexity +/// +/// All searching methods have worst-case multiplicative time complexity (i.e., +/// `O(self.raw_len() * pat.len())`). Enabling the "memchr" feature allows +/// these methods to instead run in linear time in the worst case (documented +/// for [`memchr::memmem::find`][memchr complexity]). +/// +/// # Safety +/// +/// Although this type is annotated with `#[repr(transparent)]`, the inner +/// representation is not stable. Transmuting between this type and any other +/// causes immediate undefined behavior. +/// +/// # Nightly Notes +/// +/// Indices are validated on all platforms. +/// +/// [memchr complexity]: memchr::memmem::find#complexity +/// [unspecified encoding]: super#encoding +#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] +#[repr(transparent)] +pub struct RawOsStr([u8]); + +impl RawOsStr { + const fn from_inner(string: &[u8]) -> &Self { + // SAFETY: This struct has a layout that makes this operation safe. + unsafe { mem::transmute(string) } + } + + /// Converts a platform-native string into a representation that can be + /// more easily manipulated. + /// + /// This method performs the necessary conversion immediately, so it can be + /// expensive to call. It is recommended to continue using the returned + /// instance as long as possible (instead of the original [`OsStr`]), to + /// avoid repeated conversions. + /// + /// # Nightly Notes + /// + /// This method is deprecated. Use [`from_os_str`] instead. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// println!("{:?}", RawOsStr::new(&os_string)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`from_os_str`]: Self::from_os_str + #[cfg_attr( + all(not(os_str_bytes_docs_rs), feature = "nightly"), + deprecated(since = "6.6.0", note = "use `from_os_str` instead") + )] + #[inline] + #[must_use] + pub fn new(string: &OsStr) -> Cow<'_, Self> { + if_nightly_return! {{ + Cow::Borrowed(Self::from_os_str(string)) + }} + match imp::os_str_to_bytes(string) { + Cow::Borrowed(string) => Cow::Borrowed(Self::from_inner(string)), + Cow::Owned(string) => Cow::Owned(RawOsString(string)), + } + } + + if_nightly! { + /// Wraps a platform-native string, without copying or encoding + /// conversion. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// println!("{:?}", RawOsStr::from_os_str(&os_string)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub fn from_os_str(string: &OsStr) -> &Self { + Self::from_inner(string.as_encoded_bytes()) + } + } + + /// Wraps a string, without copying or encoding conversion. + /// + /// This method is much more efficient than [`RawOsStr::new`], since the + /// [encoding] used by this crate is compatible with UTF-8. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let string = "foobar"; + /// let raw = RawOsStr::from_str(string); + /// assert_eq!(string, raw); + /// ``` + /// + /// [encoding]: super#encoding + #[allow(clippy::should_implement_trait)] + #[inline] + #[must_use] + pub fn from_str(string: &str) -> &Self { + Self::from_inner(string.as_bytes()) + } + + if_nightly! { + /// Equivalent to [`OsStr::from_encoded_bytes_unchecked`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::from_os_str(&os_string); + /// let raw_bytes = raw.as_encoded_bytes(); + /// assert_eq!(raw, unsafe { + /// RawOsStr::from_encoded_bytes_unchecked(raw_bytes) + /// }); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub unsafe fn from_encoded_bytes_unchecked(string: &[u8]) -> &Self { + Self::from_inner(string) + } + } + + fn cow_from_raw_bytes_checked( + string: &[u8], + ) -> imp::Result> { + if_nightly_return! { + { + imp::os_str_from_bytes(string).map(RawOsStrCow::from_os_str) + } + raw::validate_bytes(string) + .map(|()| Cow::Borrowed(Self::from_inner(string))) + } + } + + deprecated_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// # Panics + /// + /// Panics if the string is not valid for the [unspecified encoding] + /// used by this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!(&*raw, RawOsStr::assert_from_raw_bytes(&raw_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr( + feature = "conversions", + deprecated( + since = "6.6.0", + note = "use `assert_cow_from_raw_bytes` instead" + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use = "method should not be used for validation"] + #[track_caller] + pub fn assert_from_raw_bytes(string: &[u8]) -> &Self { + leak_cow(Self::assert_cow_from_raw_bytes(string)) + } + + /// Converts and wraps a byte string. + /// + /// This method should be avoided if other safe methods can be used. + /// + /// # Panics + /// + /// Panics if the string is not valid for the [unspecified encoding] + /// used by this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!(raw, RawOsStr::assert_cow_from_raw_bytes(&raw_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use = "method should not be used for validation"] + #[track_caller] + pub fn assert_cow_from_raw_bytes(string: &[u8]) -> Cow<'_, Self> { + expect_encoded!(Self::cow_from_raw_bytes_checked(string)) + } + } + + if_checked_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// [`assert_from_raw_bytes`] should almost always be used instead. For + /// more information, see [`EncodingError`]. + /// + /// # Errors + /// + /// See documentation for [`EncodingError`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!(Ok(&*raw), RawOsStr::from_raw_bytes(&raw_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_from_raw_bytes`]: Self::assert_from_raw_bytes + #[deprecated( + since = "6.6.0", + note = "use `cow_from_raw_bytes` instead", + )] + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + #[inline] + pub fn from_raw_bytes(string: &[u8]) -> Result<&Self> { + Self::cow_from_raw_bytes(string).map(leak_cow) + } + + /// Converts and wraps a byte string. + /// + /// [`assert_cow_from_raw_bytes`] should almost always be used instead. + /// For more information, see [`EncodingError`]. + /// + /// # Errors + /// + /// See documentation for [`EncodingError`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!( + /// Ok(&raw), + /// RawOsStr::cow_from_raw_bytes(&raw_bytes).as_ref(), + /// ); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_cow_from_raw_bytes`]: Self::assert_cow_from_raw_bytes + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + #[inline] + pub fn cow_from_raw_bytes(string: &[u8]) -> Result> { + Self::cow_from_raw_bytes_checked(string).map_err(EncodingError) + } + } + + deprecated_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// # Safety + /// + /// The string must be valid for the [unspecified encoding] used by + /// this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!(&*raw, unsafe { + /// RawOsStr::from_raw_bytes_unchecked(&raw_bytes) + /// }); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(feature = "nightly", allow(deprecated))] + #[cfg_attr( + feature = "conversions", + deprecated( + since = "6.6.0", + note = "use `cow_from_raw_bytes_unchecked` instead" + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + #[track_caller] + pub unsafe fn from_raw_bytes_unchecked(string: &[u8]) -> &Self { + // SAFETY: This method has equivalent safety requirements. + leak_cow(unsafe { Self::cow_from_raw_bytes_unchecked(string) }) + } + + /// Converts and wraps a byte string. + /// + /// # Safety + /// + /// The string must be valid for the [unspecified encoding] used by + /// this crate. + /// + /// # Nightly Notes + /// + /// This method is deprecated. Use [`assert_cow_from_raw_bytes`] or + /// [`from_encoded_bytes_unchecked`] instead. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// let raw_bytes = raw.to_raw_bytes(); + /// assert_eq!(raw, unsafe { + /// RawOsStr::cow_from_raw_bytes_unchecked(&raw_bytes) + /// }); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_cow_from_raw_bytes`]: Self::assert_cow_from_raw_bytes + /// [`from_encoded_bytes_unchecked`]: Self::from_encoded_bytes_unchecked + /// [unspecified encoding]: super#encoding + #[cfg_attr(feature = "nightly", allow(deprecated))] + #[cfg_attr( + all( + not(os_str_bytes_docs_rs), + feature = "conversions", + feature = "nightly", + ), + deprecated( + since = "6.6.0", + note = "use `assert_cow_from_raw_bytes` or + `from_encoded_bytes_unchecked` instead", + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + #[track_caller] + pub unsafe fn cow_from_raw_bytes_unchecked( + string: &[u8], + ) -> Cow<'_, Self> { + if_nightly_return! { + { + Self::assert_cow_from_raw_bytes(string) + } + if cfg!(debug_assertions) { + expect_encoded!(raw::validate_bytes(string)); + } + } + Cow::Borrowed(Self::from_inner(string)) + } + } + + if_nightly! { + /// Equivalent to [`OsStr::as_encoded_bytes`]. + /// + /// The returned string will not use the [unspecified encoding]. It can + /// only be passed to methods accepting the encoding from the standard + /// library, such as [`from_encoded_bytes_unchecked`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let string = "foobar"; + /// let raw = RawOsStr::from_str(string); + /// assert_eq!(string.as_bytes(), raw.as_encoded_bytes()); + /// ``` + /// + /// [`from_encoded_bytes_unchecked`]: Self::from_encoded_bytes_unchecked + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub fn as_encoded_bytes(&self) -> &[u8] { + &self.0 + } + } + + deprecated_conversions! { + /// Returns the byte string stored by this container. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let string = "foobar"; + /// let raw = RawOsStr::from_str(string); + /// assert_eq!(string.as_bytes(), raw.as_raw_bytes()); + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr( + feature = "conversions", + deprecated(since = "6.6.0", note = "use `to_raw_bytes` instead") + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn as_raw_bytes(&self) -> &[u8] { + match self.to_raw_bytes() { + Cow::Borrowed(string) => string, + #[cfg_attr(not(feature = "nightly"), allow(unused_variables))] + Cow::Owned(string) => { + if_nightly_return! {{ + string.leak() + }} + unreachable!(); + } + } + } + } + + if_nightly! { + /// Converts this representation back to a platform-native string, + /// without copying or encoding conversion. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::from_os_str(&os_string); + /// assert_eq!(os_string, raw.as_os_str()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub fn as_os_str(&self) -> &OsStr { + // SAFETY: This wrapper prevents violating the invariants of the + // encoding used by the standard library. + unsafe { OsStr::from_encoded_bytes_unchecked(&self.0) } + } + } + + /// Equivalent to [`str::contains`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.contains("oo")); + /// assert!(!raw.contains("of")); + /// ``` + #[inline] + #[must_use] + pub fn contains

(&self, pat: P) -> bool + where + P: Pattern, + { + self.find(pat).is_some() + } + + /// Equivalent to [`str::ends_with`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.ends_with("bar")); + /// assert!(!raw.ends_with("foo")); + /// ``` + #[inline] + #[must_use] + pub fn ends_with

(&self, pat: P) -> bool + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + self.0.ends_with(pat) + } + + deprecated_conversions! { + /// Equivalent to [`str::ends_with`] but accepts this type for the + /// pattern. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.ends_with_os(RawOsStr::from_str("bar"))); + /// assert!(!raw.ends_with_os(RawOsStr::from_str("foo"))); + /// ``` + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn ends_with_os(&self, pat: &Self) -> bool { + raw::ends_with(&self.to_raw_bytes(), &pat.to_raw_bytes()) + } + } + + /// Equivalent to [`str::find`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert_eq!(Some(1), raw.find("o")); + /// assert_eq!(None, raw.find("of")); + /// ``` + #[inline] + #[must_use] + pub fn find

(&self, pat: P) -> Option + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + find(&self.0, pat) + } + + /// Equivalent to [`str::is_empty`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// assert!(RawOsStr::from_str("").is_empty()); + /// assert!(!RawOsStr::from_str("foobar").is_empty()); + /// ``` + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + deprecated_conversions! { + /// Returns the length of the byte string stored by this container. + /// + /// Only the following assumptions can be made about the result: + /// - The length of any Unicode character is the length of its UTF-8 + /// representation (i.e., [`char::len_utf8`]). + /// - Splitting a string at a UTF-8 boundary will return two strings + /// with lengths that sum to the length of the original string. + /// + /// This method may return a different result than would [`OsStr::len`] + /// when called on same string, since [`OsStr`] uses an unspecified + /// encoding. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// assert_eq!(6, RawOsStr::from_str("foobar").raw_len()); + /// assert_eq!(0, RawOsStr::from_str("").raw_len()); + /// ``` + #[cfg_attr( + feature = "conversions", + deprecated( + since = "6.6.0", + note = "use `as_encoded_bytes` or `to_raw_bytes` instead", + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn raw_len(&self) -> usize { + self.to_raw_bytes().len() + } + } + + /// Equivalent to [`str::rfind`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert_eq!(Some(2), raw.rfind("o")); + /// assert_eq!(None, raw.rfind("of")); + /// ``` + #[inline] + #[must_use] + pub fn rfind

(&self, pat: P) -> Option + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + rfind(&self.0, pat) + } + + fn split_once_raw_with( + &self, + pat: &P, + find_fn: F, + ) -> Option<(&Self, &Self)> + where + F: FnOnce(&[u8], &[u8]) -> Option, + P: EncodedPattern, + { + let pat = pat.__get(); + + let index = find_fn(&self.0, pat)?; + let prefix = &self.0[..index]; + let suffix = &self.0[index + pat.len()..]; + Some((Self::from_inner(prefix), Self::from_inner(suffix))) + } + + pub(super) fn rsplit_once_raw

(&self, pat: &P) -> Option<(&Self, &Self)> + where + P: EncodedPattern, + { + self.split_once_raw_with(pat, rfind) + } + + /// Equivalent to [`str::rsplit_once`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert_eq!( + /// Some((RawOsStr::from_str("fo"), RawOsStr::from_str("bar"))), + /// raw.rsplit_once("o"), + /// ); + /// assert_eq!(None, raw.rsplit_once("of")); + /// ``` + #[inline] + #[must_use] + pub fn rsplit_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern, + { + self.rsplit_once_raw(&pat.__encode()) + } + + fn is_boundary(&self, index: usize) -> bool { + debug_assert!(index < self.0.len()); + + if_nightly_return! { + { + const MAX_LENGTH: usize = 4; + + if index == 0 { + return true; + } + let byte = self.0[index]; + if byte.is_ascii() { + return true; + } + + if !util::is_continuation(byte) { + let bytes = &self.0[index..]; + let valid = + str::from_utf8(&bytes[..bytes.len().min(MAX_LENGTH)]) + .err() + .map(|x| x.valid_up_to() != 0) + .unwrap_or(true); + if valid { + return true; + } + } + let mut start = index; + for _ in 0..MAX_LENGTH { + if let Some(index) = start.checked_sub(1) { + start = index; + } else { + return false; + } + if !util::is_continuation(self.0[start]) { + break; + } + } + str::from_utf8(&self.0[start..index]).is_ok() + } + !raw::is_continuation(self.0[index]) + } + } + + #[cfg_attr(feature = "nightly", allow(clippy::diverging_sub_expression))] + #[cold] + #[inline(never)] + #[track_caller] + fn index_boundary_error(&self, index: usize) -> ! { + debug_assert!(!self.is_boundary(index)); + + if_nightly_return! { + { + panic!("byte index {} is not a valid boundary", index); + } + let start = expect_encoded!(self.0[..index] + .iter() + .rposition(|&x| !raw::is_continuation(x))); + let mut end = index + 1; + end += self.0[end..] + .iter() + .take_while(|&&x| raw::is_continuation(x)) + .count(); + + let code_point = raw::decode_code_point(&self.0[start..end]); + panic!( + "byte index {} is not a valid boundary; it is inside U+{:04X} \ + (bytes {}..{})", + index, code_point, start, end, + ); + } + } + + #[track_caller] + fn check_bound(&self, index: usize) { + if index < self.0.len() && !self.is_boundary(index) { + self.index_boundary_error(index); + } + } + + /// Equivalent to [`str::split`], but empty patterns are not accepted. + /// + /// # Panics + /// + /// Panics if the pattern is empty. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.split("o").eq(["f", "", "bar"])); + /// ``` + #[inline] + #[must_use] + #[track_caller] + pub fn split

(&self, pat: P) -> RawSplit<'_, P> + where + P: Pattern, + { + RawSplit::new(self, pat) + } + + /// Equivalent to [`str::split_at`]. + /// + /// # Panics + /// + /// Panics if the index is not a [valid boundary]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert_eq!( + /// ((RawOsStr::from_str("fo"), RawOsStr::from_str("obar"))), + /// raw.split_at(2), + /// ); + /// ``` + /// + /// [valid boundary]: #indices + #[inline] + #[must_use] + #[track_caller] + pub fn split_at(&self, mid: usize) -> (&Self, &Self) { + self.check_bound(mid); + + let (prefix, suffix) = self.0.split_at(mid); + (Self::from_inner(prefix), Self::from_inner(suffix)) + } + + pub(super) fn split_once_raw

(&self, pat: &P) -> Option<(&Self, &Self)> + where + P: EncodedPattern, + { + self.split_once_raw_with(pat, find) + } + + /// Equivalent to [`str::split_once`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert_eq!( + /// Some((RawOsStr::from_str("f"), RawOsStr::from_str("obar"))), + /// raw.split_once("o"), + /// ); + /// assert_eq!(None, raw.split_once("of")); + /// ``` + #[inline] + #[must_use] + pub fn split_once

(&self, pat: P) -> Option<(&Self, &Self)> + where + P: Pattern, + { + self.split_once_raw(&pat.__encode()) + } + + /// Equivalent to [`str::starts_with`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.starts_with("foo")); + /// assert!(!raw.starts_with("bar")); + /// ``` + #[inline] + #[must_use] + pub fn starts_with

(&self, pat: P) -> bool + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + self.0.starts_with(pat) + } + + deprecated_conversions! { + /// Equivalent to [`str::starts_with`] but accepts this type for the + /// pattern. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("foobar"); + /// assert!(raw.starts_with_os(RawOsStr::from_str("foo"))); + /// assert!(!raw.starts_with_os(RawOsStr::from_str("bar"))); + /// ``` + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn starts_with_os(&self, pat: &Self) -> bool { + raw::starts_with(&self.to_raw_bytes(), &pat.to_raw_bytes()) + } + } + + /// Equivalent to [`str::strip_prefix`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("111foo1bar111"); + /// assert_eq!( + /// Some(RawOsStr::from_str("11foo1bar111")), + /// raw.strip_prefix("1"), + /// ); + /// assert_eq!(None, raw.strip_prefix("o")); + /// ``` + #[inline] + #[must_use] + pub fn strip_prefix

(&self, pat: P) -> Option<&Self> + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + self.0.strip_prefix(pat).map(Self::from_inner) + } + + /// Equivalent to [`str::strip_suffix`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("111foo1bar111"); + /// assert_eq!( + /// Some(RawOsStr::from_str("111foo1bar11")), + /// raw.strip_suffix("1"), + /// ); + /// assert_eq!(None, raw.strip_suffix("o")); + /// ``` + #[inline] + #[must_use] + pub fn strip_suffix

(&self, pat: P) -> Option<&Self> + where + P: Pattern, + { + let pat = pat.__encode(); + let pat = pat.__get(); + + self.0.strip_suffix(pat).map(Self::from_inner) + } + + /// Converts this representation back to a platform-native string. + /// + /// When possible, use [`RawOsStrCow::into_os_str`] for a more efficient + /// conversion on some platforms. + /// + /// # Nightly Notes + /// + /// This method is deprecated. Use [`as_os_str`] instead. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// assert_eq!(os_string, raw.to_os_str()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`as_os_str`]: Self::as_os_str + #[cfg_attr( + all(not(os_str_bytes_docs_rs), feature = "nightly"), + deprecated(since = "6.6.0", note = "use `as_os_str` instead") + )] + #[inline] + #[must_use] + pub fn to_os_str(&self) -> Cow<'_, OsStr> { + if_nightly_return! {{ + Cow::Borrowed(self.as_os_str()) + }} + expect_encoded!(imp::os_str_from_bytes(&self.0)) + } + + deprecated_conversions! { + /// Converts and returns the byte string stored by this container. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let string = "foobar"; + /// let raw = RawOsStr::from_str(string); + /// assert_eq!(string.as_bytes(), &*raw.to_raw_bytes()); + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn to_raw_bytes(&self) -> Cow<'_, [u8]> { + if_nightly_return! {{ + imp::os_str_to_bytes(self.as_os_str()) + }} + Cow::Borrowed(&self.0) + } + } + + /// Equivalent to [`OsStr::to_str`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let string = "foobar"; + /// let raw = RawOsStr::from_str(string); + /// assert_eq!(Some(string), raw.to_str()); + /// ``` + #[inline] + #[must_use] + pub fn to_str(&self) -> Option<&str> { + str::from_utf8(&self.0).ok() + } + + /// Converts this string to the best UTF-8 representation possible. + /// + /// Invalid sequences will be replaced with + /// [`char::REPLACEMENT_CHARACTER`]. + /// + /// This method may return a different result than would + /// [`OsStr::to_string_lossy`] when called on same string, since [`OsStr`] + /// uses an unspecified encoding. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// println!("{}", raw.to_str_lossy()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn to_str_lossy(&self) -> Cow<'_, str> { + String::from_utf8_lossy(&self.0) + } + + fn trim_matches_raw_with(&self, pat: &P, strip_fn: F) -> &Self + where + F: for<'a> Fn(&'a [u8], &[u8]) -> Option<&'a [u8]>, + P: EncodedPattern, + { + let pat = pat.__get(); + if pat.is_empty() { + return self; + } + + let mut string = &self.0; + while let Some(substring) = strip_fn(string, pat) { + string = substring; + } + Self::from_inner(string) + } + + fn trim_end_matches_raw

(&self, pat: &P) -> &Self + where + P: EncodedPattern, + { + self.trim_matches_raw_with(pat, <[_]>::strip_suffix) + } + + /// Equivalent to [`str::trim_end_matches`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("111foo1bar111"); + /// assert_eq!("111foo1bar", raw.trim_end_matches("1")); + /// assert_eq!("111foo1bar111", raw.trim_end_matches("o")); + /// ``` + #[inline] + #[must_use] + pub fn trim_end_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + self.trim_end_matches_raw(&pat.__encode()) + } + + /// Equivalent to [`str::trim_matches`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("111foo1bar111"); + /// assert_eq!("foo1bar", raw.trim_matches("1")); + /// assert_eq!("111foo1bar111", raw.trim_matches("o")); + /// ``` + #[inline] + #[must_use] + pub fn trim_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + let pat = pat.__encode(); + self.trim_start_matches_raw(&pat).trim_end_matches_raw(&pat) + } + + fn trim_start_matches_raw

(&self, pat: &P) -> &Self + where + P: EncodedPattern, + { + self.trim_matches_raw_with(pat, <[_]>::strip_prefix) + } + + /// Equivalent to [`str::trim_start_matches`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsStr; + /// + /// let raw = RawOsStr::from_str("111foo1bar111"); + /// assert_eq!("foo1bar111", raw.trim_start_matches("1")); + /// assert_eq!("111foo1bar111", raw.trim_start_matches("o")); + /// ``` + #[inline] + #[must_use] + pub fn trim_start_matches

(&self, pat: P) -> &Self + where + P: Pattern, + { + self.trim_start_matches_raw(&pat.__encode()) + } +} + +impl AsRef for RawOsStr { + #[inline] + fn as_ref(&self) -> &Self { + self + } +} + +if_nightly! { + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl AsRef for RawOsStr { + #[inline] + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } + } + + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl AsRef for OsStr { + #[inline] + fn as_ref(&self) -> &RawOsStr { + RawOsStr::from_os_str(self) + } + } +} + +if_nightly! { + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl AsRef for OsString { + #[inline] + fn as_ref(&self) -> &RawOsStr { + (**self).as_ref() + } + } +} + +impl AsRef for str { + #[inline] + fn as_ref(&self) -> &RawOsStr { + RawOsStr::from_str(self) + } +} + +impl AsRef for String { + #[inline] + fn as_ref(&self) -> &RawOsStr { + (**self).as_ref() + } +} + +impl Default for &RawOsStr { + #[inline] + fn default() -> Self { + RawOsStr::from_str("") + } +} + +impl<'a> From<&'a RawOsStr> for Cow<'a, RawOsStr> { + #[inline] + fn from(value: &'a RawOsStr) -> Self { + Cow::Borrowed(value) + } +} + +impl From> for Box { + #[inline] + fn from(value: Box) -> Self { + value.into_boxed_bytes().transmute_box() + } +} + +impl ToOwned for RawOsStr { + type Owned = RawOsString; + + #[inline] + fn to_owned(&self) -> Self::Owned { + RawOsString(self.0.to_owned()) + } +} + +/// Extensions to [`Cow`] for additional conversions. +/// +/// [`Cow`]: Cow +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] +pub trait RawOsStrCow<'a>: private::Sealed { + /// Converts a platform-native string back to this representation. + /// + /// # Nightly Notes + /// + /// This method does not require copying or encoding conversion. + /// + /// # Examples + /// + /// ``` + /// use std::borrow::Cow; + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// use os_str_bytes::RawOsStrCow; + /// + /// let os_string = Cow::Owned(env::current_exe()?.into_os_string()); + /// println!("{:?}", Cow::from_os_str(os_string)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[must_use] + fn from_os_str(string: Cow<'a, OsStr>) -> Self; + + /// Converts this representation back to a platform-native string. + /// + /// # Nightly Notes + /// + /// This method does not require copying or encoding conversion. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsStr; + /// use os_str_bytes::RawOsStrCow; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsStr::new(&os_string); + /// assert_eq!(os_string, raw.into_os_str()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[must_use] + fn into_os_str(self) -> Cow<'a, OsStr>; + + deprecated_conversions! { + /// Returns the byte string stored by this container. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use std::borrow::Cow; + /// + /// use os_str_bytes::RawOsStr; + /// use os_str_bytes::RawOsStrCow; + /// + /// let string = "foobar"; + /// let raw = Cow::Borrowed(RawOsStr::from_str(string)); + /// assert_eq!(string.as_bytes(), &*raw.into_raw_bytes()); + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr( + feature = "conversions", + deprecated( + since = "6.6.0", + note = "removal planned due to low usage", + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[must_use] + fn into_raw_bytes(self) -> Cow<'a, [u8]>; + } +} + +impl<'a> RawOsStrCow<'a> for Cow<'a, RawOsStr> { + #[cfg_attr(feature = "nightly", allow(deprecated))] + #[inline] + fn from_os_str(string: Cow<'a, OsStr>) -> Self { + match string { + Cow::Borrowed(string) => RawOsStr::new(string), + Cow::Owned(string) => Cow::Owned(RawOsString::new(string)), + } + } + + #[cfg_attr(feature = "nightly", allow(deprecated))] + #[inline] + fn into_os_str(self) -> Cow<'a, OsStr> { + match self { + Cow::Borrowed(string) => string.to_os_str(), + Cow::Owned(string) => Cow::Owned(string.into_os_string()), + } + } + + #[inline] + fn into_raw_bytes(self) -> Cow<'a, [u8]> { + match self { + Cow::Borrowed(string) => string.to_raw_bytes(), + Cow::Owned(string) => Cow::Owned(string.into_raw_vec()), + } + } +} + +/// A container for owned byte strings converted by this crate. +/// +/// For more information, see [`RawOsStr`]. +#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] +pub struct RawOsString(Vec); + +impl RawOsString { + /// Converts a platform-native string into a representation that can be + /// more easily manipulated. + /// + /// For more information, see [`RawOsStr::new`]. + /// + /// # Nightly Notes + /// + /// This method does not require copying or encoding conversion. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// println!("{:?}", RawOsString::new(os_string)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn new(string: OsString) -> Self { + if_nightly_return! {{ + Self(string.into_encoded_bytes()) + }} + Self(imp::os_string_into_vec(string)) + } + + /// Wraps a string, without copying or encoding conversion. + /// + /// This method is much more efficient than [`RawOsString::new`], since the + /// [encoding] used by this crate is compatible with UTF-8. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let raw = RawOsString::from_string(string.clone()); + /// assert_eq!(string, raw); + /// ``` + /// + /// [encoding]: super#encoding + #[inline] + #[must_use] + pub fn from_string(string: String) -> Self { + Self(string.into_bytes()) + } + + if_nightly! { + /// Equivalent to [`OsString::from_encoded_bytes_unchecked`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsString::new(os_string); + /// let raw_bytes = raw.clone().into_encoded_vec(); + /// assert_eq!(raw, unsafe { + /// RawOsString::from_encoded_vec_unchecked(raw_bytes) + /// }); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub unsafe fn from_encoded_vec_unchecked(string: Vec) -> Self { + Self(string) + } + } + + fn from_raw_vec_checked(string: Vec) -> imp::Result { + if_nightly_return! { + { + imp::os_string_from_vec(string).map(Self::new) + } + raw::validate_bytes(&string).map(|()| Self(string)) + } + } + + deprecated_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// # Panics + /// + /// Panics if the string is not valid for the [unspecified encoding] + /// used by this crate. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsString::new(os_string); + /// let raw_bytes = raw.clone().into_raw_vec(); + /// assert_eq!(raw, RawOsString::assert_from_raw_vec(raw_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use = "method should not be used for validation"] + #[track_caller] + pub fn assert_from_raw_vec(string: Vec) -> Self { + expect_encoded!(Self::from_raw_vec_checked(string)) + } + } + + if_checked_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// [`assert_from_raw_vec`] should almost always be used instead. For + /// more information, see [`EncodingError`]. + /// + /// # Errors + /// + /// See documentation for [`EncodingError`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsString::new(os_string); + /// let raw_bytes = raw.clone().into_raw_vec(); + /// assert_eq!(Ok(raw), RawOsString::from_raw_vec(raw_bytes)); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_from_raw_vec`]: Self::assert_from_raw_vec + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "checked_conversions")) + )] + #[inline] + pub fn from_raw_vec(string: Vec) -> Result { + Self::from_raw_vec_checked(string).map_err(EncodingError) + } + } + + deprecated_conversions! { + /// Wraps a byte string, without copying or encoding conversion. + /// + /// # Safety + /// + /// The string must be valid for the [unspecified encoding] used by + /// this crate. + /// + /// # Nightly Notes + /// + /// This method is deprecated. Use [`assert_from_raw_vec`] or + /// [`from_encoded_vec_unchecked`] instead. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsString::new(os_string); + /// let raw_bytes = raw.clone().into_raw_vec(); + /// assert_eq!(raw, unsafe { + /// RawOsString::from_raw_vec_unchecked(raw_bytes) + /// }); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + /// + /// [`assert_from_raw_vec`]: Self::assert_from_raw_vec + /// [`from_encoded_vec_unchecked`]: Self::from_encoded_vec_unchecked + /// [unspecified encoding]: super#encoding + #[cfg_attr( + all( + not(os_str_bytes_docs_rs), + feature = "conversions", + feature = "nightly", + ), + deprecated( + since = "6.6.0", + note = "use `assert_from_raw_vec` or + `from_encoded_vec_unchecked` instead", + ) + )] + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + #[track_caller] + pub unsafe fn from_raw_vec_unchecked(string: Vec) -> Self { + if_nightly_return! { + { + Self::assert_from_raw_vec(string) + } + if cfg!(debug_assertions) { + expect_encoded!(raw::validate_bytes(&string)); + } + } + + Self(string) + } + } + + /// Equivalent to [`String::clear`]. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let mut raw = RawOsString::new(os_string); + /// raw.clear(); + /// assert!(raw.is_empty()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Equivalent to [`String::into_boxed_str`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let raw = RawOsString::from_string(string.clone()); + /// assert_eq!(string, *raw.into_box()); + /// ``` + #[inline] + #[must_use] + pub fn into_box(self) -> Box { + self.0.into_boxed_slice().transmute_box() + } + + if_nightly! { + /// Equivalent to [`OsString::into_encoded_bytes`]. + /// + /// The returned string will not use the [unspecified encoding]. It can + /// only be passed to methods accepting the encoding from the standard + /// library, such as [`from_encoded_vec_unchecked`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let raw = RawOsString::from_string(string.clone()); + /// assert_eq!(string.into_bytes(), raw.into_encoded_vec()); + /// ``` + /// + /// [`from_encoded_vec_unchecked`]: Self::from_encoded_vec_unchecked + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + #[inline] + #[must_use] + pub fn into_encoded_vec(self) -> Vec { + self.0 + } + } + + /// Converts this representation back to a platform-native string. + /// + /// # Nightly Notes + /// + /// This method does not require copying or encoding conversion. + /// + /// # Examples + /// + /// ``` + /// use std::env; + /// # use std::io; + /// + /// use os_str_bytes::RawOsString; + /// + /// let os_string = env::current_exe()?.into_os_string(); + /// let raw = RawOsString::new(os_string.clone()); + /// assert_eq!(os_string, raw.into_os_string()); + /// # + /// # Ok::<_, io::Error>(()) + /// ``` + #[inline] + #[must_use] + pub fn into_os_string(self) -> OsString { + if_nightly_return! {{ + // SAFETY: This wrapper prevents violating the invariants of the + // encoding used by the standard library. + unsafe { OsString::from_encoded_bytes_unchecked(self.0) } + }} + expect_encoded!(imp::os_string_from_vec(self.0)) + } + + deprecated_conversions! { + /// Returns the byte string stored by this container. + /// + /// The returned string will use an [unspecified encoding]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let raw = RawOsString::from_string(string.clone()); + /// assert_eq!(string.into_bytes(), raw.into_raw_vec()); + /// ``` + /// + /// [unspecified encoding]: super#encoding + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))] + #[inline] + #[must_use] + pub fn into_raw_vec(self) -> Vec { + if_nightly_return! {{ + imp::os_string_into_vec(self.into_os_string()) + }} + self.0 + } + } + + /// Equivalent to [`OsString::into_string`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let raw = RawOsString::from_string(string.clone()); + /// assert_eq!(Ok(string), raw.into_string()); + /// ``` + #[inline] + pub fn into_string(self) -> result::Result { + String::from_utf8(self.0).map_err(|x| Self(x.into_bytes())) + } + + /// Equivalent to [`String::shrink_to_fit`]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let string = "foobar".to_owned(); + /// let mut raw = RawOsString::from_string(string.clone()); + /// raw.shrink_to_fit(); + /// assert_eq!(string, raw); + /// ``` + #[inline] + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit(); + } + + /// Equivalent to [`String::split_off`]. + /// + /// # Panics + /// + /// Panics if the index is not a [valid boundary]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let mut raw = RawOsString::from_string("foobar".to_owned()); + /// assert_eq!("bar", raw.split_off(3)); + /// assert_eq!("foo", raw); + /// ``` + /// + /// [valid boundary]: RawOsStr#indices + #[inline] + #[must_use] + #[track_caller] + pub fn split_off(&mut self, at: usize) -> Self { + self.check_bound(at); + + Self(self.0.split_off(at)) + } + + /// Equivalent to [`String::truncate`]. + /// + /// # Panics + /// + /// Panics if the index is not a [valid boundary]. + /// + /// # Examples + /// + /// ``` + /// use os_str_bytes::RawOsString; + /// + /// let mut raw = RawOsString::from_string("foobar".to_owned()); + /// raw.truncate(3); + /// assert_eq!("foo", raw); + /// ``` + /// + /// [valid boundary]: RawOsStr#indices + #[inline] + #[track_caller] + pub fn truncate(&mut self, new_len: usize) { + self.check_bound(new_len); + + self.0.truncate(new_len); + } +} + +if_nightly! { + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl AsRef for RawOsString { + #[inline] + fn as_ref(&self) -> &OsStr { + (**self).as_ref() + } + } +} + +impl AsRef for RawOsString { + #[inline] + fn as_ref(&self) -> &RawOsStr { + self + } +} + +impl Borrow for RawOsString { + #[inline] + fn borrow(&self) -> &RawOsStr { + self + } +} + +impl Deref for RawOsString { + type Target = RawOsStr; + + #[inline] + fn deref(&self) -> &Self::Target { + RawOsStr::from_inner(&self.0) + } +} + +impl From for Box { + #[inline] + fn from(value: RawOsString) -> Self { + value.into_box() + } +} + +impl From> for RawOsString { + #[inline] + fn from(value: Box) -> Self { + Self(value.transmute_box::<[_]>().into_vec()) + } +} + +impl From for Cow<'_, RawOsStr> { + #[inline] + fn from(value: RawOsString) -> Self { + Cow::Owned(value) + } +} + +if_nightly! { + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl From for RawOsString { + #[inline] + fn from(value: OsString) -> Self { + Self::new(value) + } + } + + #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "nightly")))] + impl From for OsString { + #[inline] + fn from(value: RawOsString) -> Self { + value.into_os_string() + } + } +} + +impl From for RawOsString { + #[inline] + fn from(value: String) -> Self { + Self::from_string(value) + } +} + +struct DebugBuffer<'a>(&'a [u8]); + +impl Debug for DebugBuffer<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("\"")?; + + let mut string = self.0; + let mut invalid_length = 0; + while !string.is_empty() { + let (invalid, substring) = string.split_at(invalid_length); + + let valid = match str::from_utf8(substring) { + Ok(valid) => { + string = b""; + valid + } + Err(error) => { + let (valid, substring) = + substring.split_at(error.valid_up_to()); + + let invalid_char_length = + error.error_len().unwrap_or_else(|| substring.len()); + if valid.is_empty() { + invalid_length += invalid_char_length; + continue; + } + string = substring; + invalid_length = invalid_char_length; + + // SAFETY: This slice was validated to be UTF-8. + unsafe { str::from_utf8_unchecked(valid) } + } + }; + + raw::debug(RawOsStr::from_inner(invalid), f)?; + Display::fmt(&valid.escape_debug(), f)?; + } + + f.write_str("\"") + } +} + +macro_rules! r#impl { + ( $type:ty ) => { + impl Debug for $type { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple(stringify!($type)) + .field(&DebugBuffer(&self.0)) + .finish() + } + } + }; +} +r#impl!(RawOsStr); +r#impl!(RawOsString); + +macro_rules! r#impl { + ( $index_type:ty $(, $index_var:ident , $($bound:expr),+)? ) => { + impl Index<$index_type> for RawOsStr { + type Output = Self; + + #[inline] + fn index(&self, idx: $index_type) -> &Self::Output { + $( + let $index_var = &idx; + $(self.check_bound($bound);)+ + )? + + Self::from_inner(&self.0[idx]) + } + } + + impl Index<$index_type> for RawOsString { + type Output = RawOsStr; + + #[inline] + fn index(&self, idx: $index_type) -> &Self::Output { + &(**self)[idx] + } + } + }; +} +r#impl!(Range, x, x.start, x.end); +r#impl!(RangeFrom, x, x.start); +r#impl!(RangeFull); +// [usize::MAX] will always be a valid inclusive end index. +#[rustfmt::skip] +r#impl!(RangeInclusive, x, *x.start(), x.end().wrapping_add(1)); +r#impl!(RangeTo, x, x.end); +r#impl!(RangeToInclusive, x, x.end.wrapping_add(1)); + +macro_rules! r#impl { + ( $(#[$attr:meta])* $type:ty , $other_type:ty ) => { + $(#[$attr])* + impl PartialEq<$other_type> for $type { + #[inline] + fn eq(&self, other: &$other_type) -> bool { + let raw: &RawOsStr = self; + let other: &RawOsStr = other.as_ref(); + raw == other + } + } + + $(#[$attr])* + impl PartialEq<$type> for $other_type { + #[inline] + fn eq(&self, other: &$type) -> bool { + other == self + } + } + }; +} +r#impl!(RawOsStr, RawOsString); +r#impl!(RawOsStr, str); +r#impl!(RawOsStr, String); +r#impl!(&RawOsStr, RawOsString); +r#impl!(&RawOsStr, String); +r#impl!(RawOsString, str); +r#impl!(RawOsString, &str); +r#impl!(RawOsString, String); + +if_nightly! { + macro_rules! impl_nightly { + ( $type:ty , $other_type:ty ) => { + r#impl! { + #[cfg_attr( + os_str_bytes_docs_rs, + doc(cfg(feature = "nightly")) + )] + $type, $other_type + } + }; + } + impl_nightly!(RawOsStr, OsStr); + impl_nightly!(RawOsStr, OsString); + impl_nightly!(&RawOsStr, OsString); + impl_nightly!(RawOsString, OsStr); + impl_nightly!(RawOsString, &OsStr); + impl_nightly!(RawOsString, OsString); +} + +#[cfg(feature = "print_bytes")] +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "print_bytes")))] +mod print_bytes { + use print_bytes::ByteStr; + use print_bytes::ToBytes; + #[cfg(windows)] + use print_bytes::WideStr; + + #[cfg(windows)] + use crate::imp::raw; + + use super::RawOsStr; + use super::RawOsString; + + impl ToBytes for RawOsStr { + #[inline] + fn to_bytes(&self) -> ByteStr<'_> { + self.0.to_bytes() + } + + #[cfg(windows)] + #[inline] + fn to_wide(&self) -> Option { + Some(WideStr::new(raw::encode_wide(self).collect())) + } + } + + impl ToBytes for RawOsString { + #[inline] + fn to_bytes(&self) -> ByteStr<'_> { + (**self).to_bytes() + } + + #[cfg(windows)] + #[inline] + fn to_wide(&self) -> Option { + (**self).to_wide() + } + } +} + +#[cfg(feature = "uniquote")] +#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "uniquote")))] +mod uniquote { + use uniquote::Formatter; + use uniquote::Quote; + use uniquote::Result; + + use crate::imp::raw; + + use super::RawOsStr; + use super::RawOsString; + + impl Quote for RawOsStr { + #[inline] + fn escape(&self, f: &mut Formatter<'_>) -> Result { + raw::uniquote::escape(self, f) + } + } + + impl Quote for RawOsString { + #[inline] + fn escape(&self, f: &mut Formatter<'_>) -> Result { + (**self).escape(f) + } + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/util.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/util.rs new file mode 100644 index 0000000000..f931969c52 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/util.rs @@ -0,0 +1,9 @@ +pub(super) const BYTE_SHIFT: u8 = 6; + +pub(super) const CONT_MASK: u8 = (1 << BYTE_SHIFT) - 1; + +pub(super) const CONT_TAG: u8 = 0b1000_0000; + +pub(super) const fn is_continuation(byte: u8) -> bool { + byte & !CONT_MASK == CONT_TAG +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/mod.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/mod.rs new file mode 100644 index 0000000000..366e95026a --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/mod.rs @@ -0,0 +1,58 @@ +use std::borrow::Cow; +use std::error::Error; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; +use std::result; +use std::str; +use std::str::Utf8Error; + +if_raw_str! { + pub(super) mod raw; +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) struct EncodingError(Utf8Error); + +impl Display for EncodingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "os_str_bytes: {}", self.0) + } +} + +impl Error for EncodingError {} + +pub(super) type Result = result::Result; + +macro_rules! expect_utf8 { + ( $result:expr ) => { + $result.expect( + "platform string contains invalid UTF-8, which should not be \ + possible", + ) + }; +} + +fn from_bytes(string: &[u8]) -> Result<&str> { + str::from_utf8(string).map_err(EncodingError) +} + +pub(super) fn os_str_from_bytes(string: &[u8]) -> Result> { + from_bytes(string).map(|x| Cow::Borrowed(OsStr::new(x))) +} + +pub(super) fn os_str_to_bytes(os_string: &OsStr) -> Cow<'_, [u8]> { + Cow::Borrowed(expect_utf8!(os_string.to_str()).as_bytes()) +} + +pub(super) fn os_string_from_vec(string: Vec) -> Result { + String::from_utf8(string) + .map(Into::into) + .map_err(|x| EncodingError(x.utf8_error())) +} + +pub(super) fn os_string_into_vec(os_string: OsString) -> Vec { + expect_utf8!(os_string.into_string()).into_bytes() +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/raw.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/raw.rs new file mode 100644 index 0000000000..972ba4a8e7 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/wasm/raw.rs @@ -0,0 +1,45 @@ +use std::fmt; +use std::fmt::Formatter; + +use crate::RawOsStr; + +if_not_nightly! { + use std::str; + + use super::Result; + + if_raw_str! { + pub(crate) use crate::util::is_continuation; + } +} + +#[allow(dead_code)] +#[path = "../common/raw.rs"] +mod common_raw; +pub(crate) use common_raw::ends_with; +pub(crate) use common_raw::starts_with; +#[cfg(feature = "uniquote")] +pub(crate) use common_raw::uniquote; + +if_not_nightly! { + pub(crate) fn validate_bytes(string: &[u8]) -> Result<()> { + super::from_bytes(string).map(drop) + } +} + +if_not_nightly! { + pub(crate) fn decode_code_point(string: &[u8]) -> u32 { + let string = expect_encoded!(str::from_utf8(string)); + let mut chars = string.chars(); + let ch = chars + .next() + .expect("cannot parse code point from empty string"); + assert_eq!(None, chars.next(), "multiple code points found"); + ch.into() + } +} + +pub(crate) fn debug(string: &RawOsStr, _: &mut Formatter<'_>) -> fmt::Result { + assert!(string.is_empty()); + Ok(()) +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/mod.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/mod.rs new file mode 100644 index 0000000000..7315e2b3db --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/mod.rs @@ -0,0 +1,113 @@ +// These functions are necessarily inefficient, because they must revert +// encoding conversions performed by the standard library. However, there is +// currently no better alternative. + +use std::borrow::Cow; +use std::error::Error; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; +use std::ops::Not; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::ffi::OsStringExt; +use std::result; +use std::str; + +if_raw_str! { + pub(super) mod raw; +} + +mod wtf8; +use wtf8::DecodeWide; + +#[cfg(test)] +mod tests; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) enum EncodingError { + Byte(u8), + CodePoint(u32), + End(), +} + +impl EncodingError { + fn position(&self) -> Cow<'_, str> { + match self { + Self::Byte(byte) => Cow::Owned(format!("byte b'\\x{:02X}'", byte)), + Self::CodePoint(code_point) => { + Cow::Owned(format!("code point U+{:04X}", code_point)) + } + Self::End() => Cow::Borrowed("end of string"), + } + } +} + +impl Display for EncodingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "byte sequence is not representable in the platform encoding; \ + error at {}", + self.position(), + ) + } +} + +impl Error for EncodingError {} + +pub(super) type Result = result::Result; + +fn from_bytes(string: &[u8]) -> Result> { + let mut encoder = wtf8::encode_wide(string); + + // Collecting an iterator into a result ignores the size hint: + // https://github.com/rust-lang/rust/issues/48994 + let mut encoded_string = Vec::with_capacity(encoder.size_hint().0); + for wchar in &mut encoder { + encoded_string.push(wchar?); + } + + debug_assert_eq!(str::from_utf8(string).is_ok(), encoder.is_still_utf8()); + Ok(encoder + .is_still_utf8() + .not() + .then(|| OsStringExt::from_wide(&encoded_string))) +} + +fn to_bytes(os_string: &OsStr) -> Vec { + let encoder = OsStrExt::encode_wide(os_string); + + let mut string = Vec::with_capacity(encoder.size_hint().0); + string.extend(DecodeWide::new(encoder)); + string +} + +pub(super) fn os_str_from_bytes(string: &[u8]) -> Result> { + from_bytes(string).map(|os_string| { + os_string.map(Cow::Owned).unwrap_or_else(|| { + // SAFETY: This slice was validated to be UTF-8. + Cow::Borrowed(OsStr::new(unsafe { + str::from_utf8_unchecked(string) + })) + }) + }) +} + +pub(super) fn os_str_to_bytes(os_string: &OsStr) -> Cow<'_, [u8]> { + Cow::Owned(to_bytes(os_string)) +} + +pub(super) fn os_string_from_vec(string: Vec) -> Result { + from_bytes(&string).map(|os_string| { + os_string.unwrap_or_else(|| { + // SAFETY: This slice was validated to be UTF-8. + unsafe { String::from_utf8_unchecked(string) }.into() + }) + }) +} + +pub(super) fn os_string_into_vec(os_string: OsString) -> Vec { + to_bytes(&os_string) +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/raw.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/raw.rs new file mode 100644 index 0000000000..81cfc04a38 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/raw.rs @@ -0,0 +1,67 @@ +use std::fmt; +use std::fmt::Formatter; + +use crate::RawOsStr; + +pub(crate) use super::wtf8::ends_with; +pub(crate) use super::wtf8::starts_with; + +if_nightly! { + use std::os::windows::ffi::OsStrExt; +} + +if_not_nightly! { + pub(crate) use crate::util::is_continuation; + + use super::wtf8; + use super::wtf8::CodePoints; + use super::Result; +} + +if_not_nightly! { + pub(crate) fn validate_bytes(string: &[u8]) -> Result<()> { + wtf8::encode_wide(string).try_for_each(|x| x.map(drop)) + } +} + +#[cfg_attr(not(feature = "nightly"), allow(deprecated))] +pub(crate) fn encode_wide( + string: &RawOsStr, +) -> impl '_ + Iterator { + if_nightly_return! { + { + string.as_os_str().encode_wide() + } + wtf8::encode_wide(string.as_raw_bytes()).map(|x| expect_encoded!(x)) + } +} + +if_not_nightly! { + pub(crate) fn decode_code_point(string: &[u8]) -> u32 { + let mut code_points = CodePoints::new(string.iter().copied()); + let code_point = expect_encoded!(code_points + .next() + .expect("cannot parse code point from empty string")); + assert_eq!(None, code_points.next(), "multiple code points found"); + code_point + } +} + +pub(crate) fn debug(string: &RawOsStr, f: &mut Formatter<'_>) -> fmt::Result { + for wchar in encode_wide(string) { + write!(f, "\\u{{{:X}}}", wchar)?; + } + Ok(()) +} + +#[cfg(feature = "uniquote")] +pub(crate) mod uniquote { + use uniquote::Formatter; + use uniquote::Result; + + use crate::RawOsStr; + + pub(crate) fn escape(string: &RawOsStr, f: &mut Formatter<'_>) -> Result { + f.escape_utf16(super::encode_wide(string)) + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/code_points.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/code_points.rs new file mode 100644 index 0000000000..231a6a17d3 --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/code_points.rs @@ -0,0 +1,129 @@ +use std::iter::FusedIterator; +use std::iter::Peekable; +use std::mem; + +use crate::util::is_continuation; +use crate::util::BYTE_SHIFT; +use crate::util::CONT_MASK; + +use super::EncodingError; +use super::Result; + +pub(in super::super) struct CodePoints +where + I: Iterator, +{ + iter: Peekable, + surrogate: bool, + still_utf8: bool, +} + +impl CodePoints +where + I: Iterator, +{ + pub(in super::super) fn new(string: S) -> Self + where + S: IntoIterator, + { + Self { + iter: string.into_iter().peekable(), + surrogate: false, + still_utf8: true, + } + } + + pub(super) const fn is_still_utf8(&self) -> bool { + self.still_utf8 + } + + fn consume_next(&mut self, code_point: &mut u32) -> Result<()> { + let &byte = self.iter.peek().ok_or(EncodingError::End())?; + + if !is_continuation(byte) { + self.surrogate = false; + // Not consuming this byte will be useful if this crate ever offers + // a way to encode lossily. + return Err(EncodingError::Byte(byte)); + } + *code_point = + (*code_point << BYTE_SHIFT) | u32::from(byte & CONT_MASK); + + let removed = self.iter.next(); + debug_assert_eq!(Some(byte), removed); + + Ok(()) + } + + pub(super) fn inner_size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl FusedIterator for CodePoints where + I: FusedIterator + Iterator +{ +} + +impl Iterator for CodePoints +where + I: Iterator, +{ + type Item = Result; + + fn next(&mut self) -> Option { + let byte = self.iter.next()?; + let mut code_point: u32 = byte.into(); + + macro_rules! consume_next { + () => {{ + if let Err(error) = self.consume_next(&mut code_point) { + return Some(Err(error)); + } + }}; + } + + let prev_surrogate = mem::replace(&mut self.surrogate, false); + + let mut invalid = false; + if !byte.is_ascii() { + if byte < 0xC2 { + return Some(Err(EncodingError::Byte(byte))); + } + + if byte < 0xE0 { + code_point &= 0x1F; + } else { + code_point &= 0x0F; + consume_next!(); + + if byte >= 0xF0 { + if code_point.wrapping_sub(0x10) >= 0x100 { + invalid = true; + } + consume_next!(); + + // This condition is optimized to detect surrogate code points. + } else if code_point & 0xFE0 == 0x360 { + self.still_utf8 = false; + if code_point & 0x10 == 0 { + self.surrogate = true; + } else if prev_surrogate { + // Decoding a broken surrogate pair would be lossy. + invalid = true; + } + } + + if code_point < 0x20 { + invalid = true; + } + } + consume_next!(); + } + if invalid { + return Some(Err(EncodingError::CodePoint(code_point))); + } + + Some(Ok(code_point)) + } +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/convert.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/convert.rs new file mode 100644 index 0000000000..a7e3d35efd --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/convert.rs @@ -0,0 +1,181 @@ +use std::char; +use std::char::DecodeUtf16; +use std::iter::FusedIterator; +use std::num::NonZeroU16; + +use crate::util::BYTE_SHIFT; +use crate::util::CONT_MASK; +use crate::util::CONT_TAG; + +use super::CodePoints; +use super::Result; + +const MIN_HIGH_SURROGATE: u16 = 0xD800; + +const MIN_LOW_SURROGATE: u16 = 0xDC00; + +const MIN_SURROGATE_CODE: u32 = (u16::MAX as u32) + 1; + +macro_rules! static_assert { + ( $condition:expr ) => { + const _: () = assert!($condition, "static assertion failed"); + }; +} + +pub(in super::super) struct DecodeWide +where + I: Iterator, +{ + iter: DecodeUtf16, + code_point: u32, + shifts: u8, +} + +impl DecodeWide +where + I: Iterator, +{ + pub(in super::super) fn new(string: S) -> Self + where + S: IntoIterator, + { + Self { + iter: char::decode_utf16(string), + code_point: 0, + shifts: 0, + } + } + + #[inline(always)] + const fn get_raw_byte(&self) -> u8 { + (self.code_point >> (self.shifts * BYTE_SHIFT)) as u8 + } +} + +impl Iterator for DecodeWide +where + I: Iterator, +{ + type Item = u8; + + fn next(&mut self) -> Option { + if let Some(shifts) = self.shifts.checked_sub(1) { + self.shifts = shifts; + return Some((self.get_raw_byte() & CONT_MASK) | CONT_TAG); + } + + self.code_point = self + .iter + .next()? + .map(Into::into) + .unwrap_or_else(|x| x.unpaired_surrogate().into()); + + macro_rules! decode { + ( $tag:expr ) => { + Some(self.get_raw_byte() | $tag) + }; + } + macro_rules! try_decode { + ( $tag:expr , $upper_bound:expr ) => { + if self.code_point < $upper_bound { + return decode!($tag); + } + self.shifts += 1; + }; + } + try_decode!(0, 0x80); + try_decode!(0xC0, 0x800); + try_decode!(0xE0, MIN_SURROGATE_CODE); + decode!(0xF0) + } + + fn size_hint(&self) -> (usize, Option) { + let (low, high) = self.iter.size_hint(); + let shifts = self.shifts.into(); + ( + low.saturating_add(shifts), + high.and_then(|x| x.checked_mul(4)) + .and_then(|x| x.checked_add(shifts)), + ) + } +} + +pub(in super::super) struct EncodeWide +where + I: Iterator, +{ + iter: CodePoints, + surrogate: Option, +} + +impl EncodeWide +where + I: Iterator, +{ + fn new(string: S) -> Self + where + S: IntoIterator, + { + Self { + iter: CodePoints::new(string), + surrogate: None, + } + } + + pub(in super::super) const fn is_still_utf8(&self) -> bool { + self.iter.is_still_utf8() + } +} + +impl FusedIterator for EncodeWide where + I: FusedIterator + Iterator +{ +} + +impl Iterator for EncodeWide +where + I: Iterator, +{ + type Item = Result; + + fn next(&mut self) -> Option { + if let Some(surrogate) = self.surrogate.take() { + return Some(Ok(surrogate.get())); + } + + self.iter.next().map(|code_point| { + code_point.map(|code_point| { + code_point + .checked_sub(MIN_SURROGATE_CODE) + .map(|offset| { + static_assert!(MIN_LOW_SURROGATE != 0); + + // SAFETY: The above static assertion guarantees that + // this value will not be zero. + self.surrogate = Some(unsafe { + NonZeroU16::new_unchecked( + (offset & 0x3FF) as u16 | MIN_LOW_SURROGATE, + ) + }); + (offset >> 10) as u16 | MIN_HIGH_SURROGATE + }) + .unwrap_or(code_point as u16) + }) + }) + } + + fn size_hint(&self) -> (usize, Option) { + let (low, high) = self.iter.inner_size_hint(); + let additional = self.surrogate.is_some().into(); + ( + (low.saturating_add(2) / 3).saturating_add(additional), + high.and_then(|x| x.checked_add(additional)), + ) + } +} + +pub(in super::super) fn encode_wide( + string: &[u8], +) -> EncodeWide> { + EncodeWide::new(string.iter().copied()) +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/mod.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/mod.rs new file mode 100644 index 0000000000..d8b0dc4a7f --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/mod.rs @@ -0,0 +1,18 @@ +// This module implements the WTF-8 encoding specification: +// https://simonsapin.github.io/wtf-8/ + +use super::EncodingError; +use super::Result; + +mod code_points; +pub(super) use code_points::CodePoints; + +mod convert; +pub(super) use convert::encode_wide; +pub(super) use convert::DecodeWide; + +if_raw_str! { + mod string; + pub(crate) use string::ends_with; + pub(crate) use string::starts_with; +} diff --git a/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/string.rs b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/string.rs new file mode 100644 index 0000000000..b3523a2eff --- /dev/null +++ b/userland/upstream-src/os_str_bytes-6.6.1/src/windows/wtf8/string.rs @@ -0,0 +1,67 @@ +use crate::util; + +const SURROGATE_LENGTH: usize = 3; + +pub(crate) fn ends_with(string: &[u8], mut suffix: &[u8]) -> bool { + let index = if let Some(index) = string.len().checked_sub(suffix.len()) { + index + } else { + return false; + }; + if let Some(&byte) = string.get(index) { + if util::is_continuation(byte) { + let index = expect_encoded!(index.checked_sub(1)); + let mut wide_surrogate = + if let Some(surrogate) = suffix.get(..SURROGATE_LENGTH) { + super::encode_wide(surrogate) + } else { + return false; + }; + let surrogate_wchar = wide_surrogate + .next() + .expect("failed decoding non-empty suffix"); + + if wide_surrogate.next().is_some() + || super::encode_wide(&string[index..]) + .take_while(Result::is_ok) + .nth(1) + != Some(surrogate_wchar) + { + return false; + } + suffix = &suffix[SURROGATE_LENGTH..]; + } + } + string.ends_with(suffix) +} + +pub(crate) fn starts_with(string: &[u8], mut prefix: &[u8]) -> bool { + if let Some(&byte) = string.get(prefix.len()) { + if util::is_continuation(byte) { + let index = if let Some(index) = + prefix.len().checked_sub(SURROGATE_LENGTH) + { + index + } else { + return false; + }; + let (substring, surrogate) = prefix.split_at(index); + let mut wide_surrogate = super::encode_wide(surrogate); + let surrogate_wchar = wide_surrogate + .next() + .expect("failed decoding non-empty prefix"); + + if surrogate_wchar.is_err() + || wide_surrogate.next().is_some() + || super::encode_wide(&string[index..]) + .next() + .expect("failed decoding non-empty substring") + != surrogate_wchar + { + return false; + } + prefix = substring; + } + } + string.starts_with(prefix) +} diff --git a/userland/upstream-src/pastel/.cargo-ok b/userland/upstream-src/pastel/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/pastel/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/pastel/.cargo_vcs_info.json b/userland/upstream-src/pastel/.cargo_vcs_info.json new file mode 100644 index 0000000000..78148eee5a --- /dev/null +++ b/userland/upstream-src/pastel/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5e9f9c01fe167f59ecc0888975c7ddc095816ca2" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/pastel/.github/dependabot.yml b/userland/upstream-src/pastel/.github/dependabot.yml new file mode 100644 index 0000000000..1f17094ffc --- /dev/null +++ b/userland/upstream-src/pastel/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: monthly + time: "04:00" + timezone: Europe/Berlin +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + time: "04:00" + timezone: Europe/Berlin diff --git a/userland/upstream-src/pastel/.github/workflows/CICD.yml b/userland/upstream-src/pastel/.github/workflows/CICD.yml new file mode 100644 index 0000000000..acb04c3704 --- /dev/null +++ b/userland/upstream-src/pastel/.github/workflows/CICD.yml @@ -0,0 +1,352 @@ +name: CICD + +env: + CICD_INTERMEDIATES_DIR: "_cicd-intermediates" + MSRV_FEATURES: "" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + tags: + - '*' + +jobs: + crate_metadata: + name: Extract crate metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Extract crate information + id: crate_metadata + run: | + cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT + outputs: + name: ${{ steps.crate_metadata.outputs.name }} + version: ${{ steps.crate_metadata.outputs.version }} + maintainer: ${{ steps.crate_metadata.outputs.maintainer }} + homepage: ${{ steps.crate_metadata.outputs.homepage }} + msrv: ${{ steps.crate_metadata.outputs.msrv }} + + ensure_cargo_fmt: + name: Ensure 'cargo fmt' has been run + runs-on: ubuntu-latest + steps: + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: actions/checkout@v6 + - run: cargo fmt -- --check + + min_version: + name: Minimum supported rust version + runs-on: ubuntu-latest + needs: crate_metadata + steps: + - name: Checkout source code + uses: actions/checkout@v6 + + - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ needs.crate_metadata.outputs.msrv }} + components: clippy + - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) + run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} + - name: Run tests + run: cargo test --locked ${{ env.MSRV_FEATURES }} + + build: + name: ${{ matrix.job.target }} (${{ matrix.job.os }}) + runs-on: ${{ matrix.job.os }} + needs: crate_metadata + strategy: + fail-fast: false + matrix: + job: + - { target: aarch64-apple-darwin , os: macos-latest } + - { target: aarch64-unknown-linux-gnu , os: ubuntu-latest, use-cross: true } + - { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest, use-cross: true } + - { target: arm-unknown-linux-musleabihf, os: ubuntu-latest, use-cross: true } + - { target: i686-pc-windows-msvc , os: windows-latest } + - { target: i686-unknown-linux-gnu , os: ubuntu-latest, use-cross: true } + - { target: i686-unknown-linux-musl , os: ubuntu-latest, use-cross: true } + - { target: x86_64-apple-darwin , os: macos-latest } + - { target: x86_64-pc-windows-gnu , os: windows-latest } + - { target: x86_64-pc-windows-msvc , os: windows-latest } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-latest, use-cross: true } + - { target: x86_64-unknown-linux-musl , os: ubuntu-latest, use-cross: true } + env: + BUILD_CMD: cargo + steps: + - name: Checkout source code + uses: actions/checkout@v6 + + - name: Install prerequisites + shell: bash + run: | + case ${{ matrix.job.target }} in + arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.job.target }} + + - name: Install cross + if: matrix.job.use-cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Overwrite build command env variable + if: matrix.job.use-cross + shell: bash + run: echo "BUILD_CMD=cross" >> $GITHUB_ENV + + - name: Show version information (Rust, cargo, GCC) + shell: bash + run: | + gcc --version || true + rustup -V + rustup toolchain list + rustup default + cargo -V + rustc -V + + - name: Build + shell: bash + run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} + + - name: Set binary name & path + id: bin + shell: bash + run: | + # Figure out suffix of binary + EXE_suffix="" + case ${{ matrix.job.target }} in + *-pc-windows-*) EXE_suffix=".exe" ;; + esac; + + # Setup paths + BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}" + BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}" + + # Let subsequent steps know where to find the binary + echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT + echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT + + - name: Set testing options + id: test-options + shell: bash + run: | + # test only library unit tests and binary for arm-type targets + unset CARGO_TEST_OPTIONS + unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${{ needs.crate_metadata.outputs.name }}" ;; esac; + echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT + + - name: Run tests + shell: bash + run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + + - name: Create tarball + id: package + shell: bash + run: | + PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; + PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} + PKG_NAME=${PKG_BASENAME}${PKG_suffix} + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT + + PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" + ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" + mkdir -p "${ARCHIVE_DIR}" + mkdir -p "${ARCHIVE_DIR}/autocomplete" + mkdir -p "${ARCHIVE_DIR}/man" + + # Binary + cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR" + + # README, LICENSE and CHANGELOG files + cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR" + + # Man pages + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/*.1 "$ARCHIVE_DIR/man/" + + # Autocompletion files + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/_'${{ needs.crate_metadata.outputs.name }}' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}.ps1' "$ARCHIVE_DIR/autocomplete/" + + # base compressed package + pushd "${PKG_STAGING}/" >/dev/null + case ${{ matrix.job.target }} in + *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;; + *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;; + esac; + popd >/dev/null + + # Let subsequent steps know where to find the compressed package + echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT + + - name: Create Debian package + id: debian-package + shell: bash + if: startsWith(matrix.job.os, 'ubuntu') + run: | + COPYRIGHT_YEARS="2018 - "$(date "+%Y") + DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package" + DPKG_DIR="${DPKG_STAGING}/dpkg" + mkdir -p "${DPKG_DIR}" + + DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }} + DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl + case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; + DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} + + unset DPKG_ARCH + case ${{ matrix.job.target }} in + aarch64-*-linux-*) DPKG_ARCH=arm64 ;; + arm-*-linux-*hf) DPKG_ARCH=armhf ;; + i686-*-linux-*) DPKG_ARCH=i686 ;; + x86_64-*-linux-*) DPKG_ARCH=amd64 ;; + *) DPKG_ARCH=notset ;; + esac; + + DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" + echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT + + # Binary + install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" + + # Man pages + for manpage in 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/*.1; do + manpage_name=$(basename "$manpage") + install -Dm644 "$manpage" "${DPKG_DIR}/usr/share/man/man1/${manpage_name}" + gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${manpage_name}" + done + + # Autocompletion files + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}" + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/_'${{ needs.crate_metadata.outputs.name }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}" + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish" + + # README and LICENSE + install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md" + install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT" + install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE" + install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" + gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" + + cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" < "${DPKG_DIR}/DEBIAN/control" <> $GITHUB_OUTPUT + + # build dpkg + fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" + + - name: "Artifact upload: tarball" + uses: actions/upload-artifact@v6 + with: + name: ${{ steps.package.outputs.PKG_NAME }} + path: ${{ steps.package.outputs.PKG_PATH }} + + - name: "Artifact upload: Debian package" + uses: actions/upload-artifact@v6 + if: steps.debian-package.outputs.DPKG_NAME + with: + name: ${{ steps.debian-package.outputs.DPKG_NAME }} + path: ${{ steps.debian-package.outputs.DPKG_PATH }} + + - name: Check for release + id: is-release + shell: bash + run: | + unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT + + - name: Publish archives and packages + uses: softprops/action-gh-release@v2 + if: steps.is-release.outputs.IS_RELEASE + with: + files: | + ${{ steps.package.outputs.PKG_PATH }} + ${{ steps.debian-package.outputs.DPKG_PATH }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + winget: + name: Publish to Winget + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: vedantmgoyal2009/winget-releaser@v2 + with: + identifier: sharkdp.pastel + installers-regex: '-pc-windows-msvc\.zip$' + token: ${{ secrets.WINGET_TOKEN }} diff --git a/userland/upstream-src/pastel/.gitignore b/userland/upstream-src/pastel/.gitignore new file mode 100644 index 0000000000..53eaa21960 --- /dev/null +++ b/userland/upstream-src/pastel/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/userland/upstream-src/pastel/CHANGELOG.md b/userland/upstream-src/pastel/CHANGELOG.md new file mode 100644 index 0000000000..5f36dd2808 --- /dev/null +++ b/userland/upstream-src/pastel/CHANGELOG.md @@ -0,0 +1,168 @@ +# v0.12.0 + +## Features + +- Added support for parsing ANSI 8-bit color codes + +## Bugfixes + +- Fixed `ansi-8bit-value` not returning SGR code 255, see #289 + +# v0.10.0 + +## Features + +- Added alpha property to set command, see #218 (@kanielrkirby) +- Add support for HSV, see #169 (@jameshurst) +- Added support for parsing LCh colors, see #2 and #167 (@MForster) +- Added hyprpicker as --color-picker, see #186 (@mrusme) +- Implement Color::from_u32 for the rgba, see #202 (@irevoire) + +## Bugfixes + +- `pastel pick` does not display all colors in some terminals, see #121 and #168 (@Divoolej) +- Fix lines in kitty terminal with text_fg_override_threshold set, see #197 (@joveian) + +## Changes + +- Use PASTEL_COLOR_MODE in ansi::Brush::from_environment, see #168 (@Divoolej) +- Unhide colorcheck command, see #182 (@CheshireSwift) + +## Other + +- Optimization for eliminating redundant memory operations, see #165 (@yyzdtccjdtc) +- Add colour as an alias for the color command, see #173 (@BuyMyMojo) +- Suggest to use pastel pick --help instead of -h, see #181 (@sharkdp) + + +# v0.9.0 + +## Features + +- Added support for transparency / alpha values, see #131 and #162 (@superhawk610) +- Added support for `NO_COLOR` environment variable, see #143 (@djmattyg007) +- Added new color pickers: Gnome/Wayland via gdbus, zenity, yad, and wcolor (@sigmaSd, @pvonmoradi) + +## Packaging + +- Added shell completion files again, see #166 (@sharkdp) + + +# v0.8.1 + +## Features + +- Added `From` and `Display` traits for each color struct, see #133 (@bresilla) + +## Other + +- Updated `lexical-core` dependency to fix a compile error with newer Rust versions + +## Packaging + +- `pastel` is now available on snapstore, see #130 (@purveshpatel511) + + +# v0.8.0 + +## Features + +- Added CMYK output format, see #122 and #123 (@aeter) + +## Other + +- Completely new CI/CD system via GitHub Actions, see #120 (@rivy) + +# v0.7.1 + +## Bugfixes + +- Fixed a bug with the new `ansi-*-escapecode` formats, see #116 (@bbkane) + +# v0.7.0 + +## Changes + +- **Breaking:** the existing `ansi-8bit` and `ansi-24bit` formats have been changed to + print out an escaped ANSI sequence that a user can see in the terminal output. + The previously existing formats are now available as `ansi-8bit-escapecode` and + `ansi-24bit-escapecode`. See #113 and #111. + +## Features + +- All CSS color formats are now supported (see #12) +- Added support for multiple color stops for gradients (`pastel gradient red blue yellow`), see #49 (@felipe-fg) +- Added `-f`/`--force-color` flag as an alias for `--mode=24bit`, see #48 (@samueldple) +- Added `--color-picker ` to allow users to choose the colorpicker, see #96 (@d-dorazio) +- Added input support for CIELAB, see #3/#101 (@MusiKid) +- Added support for `rgb(255 0 119)`, `rgb(100%,0%,46.7%)`, `gray(20%)`, and many more new CSS syntaxes, see #103 (@MusiKid) +- Faster and more flexible color parser, adding even more CSS color formats, see #105 (@halfbro) + +## `pastel` library changes + +- `distinct_colors` is now available in the `pastel::distinct` module, see #95 (@rivy) + +## Bugfixes + +- Added support for non-color consoles (Windows 7), see #91 (@rivy) + +## Other + +- pastel is now available via Nix, see #100 (@davidtwco) + +# v0.6.1 + +## Other + +- Enabled builds for arm, aarch64, and i686 +- Fixed build on 32bit platforms + +# v0.6.0 + +## Features + +- Added colorblindness simulations via `pastel colorblind`, see #80 (@rozbb) +- Added support for pre-determined colors in `pastel distinct`, see #88 (@d-dorazio) +- Added a new `set` subcommand that can be used to set specific properties of a color (`pastel set lightness 0.4`, `pastel set red 0`, etc.), see #43 +- Show the color name in `pastel show` or `pastel color` if it is an exact match, for example: + `pastel color ff00ff` will show "fuchsia", see #81 (@d-dorazio) +- Add KColorChooser as a supported color picker, see #79 (@data-man) +- Add macOS built-in color picker, see #84 (@erydo) +- Added a new 'count' argument for `pastel pick []` + +## Changes + +- `pastel distinct` has seen massive speedups, see #83 (@d-dorazio) + +## Bugfixes + +- Mixing colors in HSL space with black or white will not rotate the hue towards red (hue 0°), see #76 + +## Other + +- Pastel is now available via Homebrew, see README and #70 (@liamdawson) + +# v0.5.3 + +- Added `rgb-float` as a new format (e.g. `pastel random | pastel format rgb-float`). +- `pastel pick` should now work in 24-bit on Windows, see #45 +- Fix crash for `pastel distinct N` with N < 2 (show an error message), see #69 + +# v0.5.2 + +* Truecolor support for Windows (@lzybkr) +* Re-arranging of colors in `pastel distinct` so as to maximize the minimal distance to the predecessors +* Fixed small numerical approximation problem in the 'similar colors' computation +* Backported to Rust 1.34 + +# v0.5.1 + +- Added shell completion files for bash, zsh, fish and PowerShell. + +# v0.5.0 + +- Added `pastel distinct N` command to generate a set of N visually distinct colors + +# v0.4.0 + +Initial public release diff --git a/userland/upstream-src/pastel/Cargo.lock b/userland/upstream-src/pastel/Cargo.lock new file mode 100644 index 0000000000..c508a919ea --- /dev/null +++ b/userland/upstream-src/pastel/Cargo.lock @@ -0,0 +1,1058 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "assert_cmd" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_complete" +version = "4.5.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clap_mangen" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "either" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets 0.52.6", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "pastel" +version = "0.12.0" +dependencies = [ + "approx", + "assert_cmd", + "clap", + "clap_complete", + "clap_mangen", + "criterion", + "nom", + "once_cell", + "output_vt100", + "rand", + "rand_xoshiro", + "regex", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "roff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix", + "windows-sys 0.60.2", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/userland/upstream-src/pastel/Cargo.toml b/userland/upstream-src/pastel/Cargo.toml new file mode 100644 index 0000000000..601a267073 --- /dev/null +++ b/userland/upstream-src/pastel/Cargo.toml @@ -0,0 +1,113 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.83.0" +name = "pastel" +version = "0.12.0" +authors = ["David Peter "] +build = "build.rs" +exclude = ["doc/pastel.gif"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A command-line tool to generate, analyze, convert and manipulate colors" +homepage = "https://github.com/sharkdp/pastel" +readme = "README.md" +categories = ["command-line-utilities"] +license = "MIT/Apache-2.0" +repository = "https://github.com/sharkdp/pastel" + +[lib] +name = "pastel" +path = "src/lib.rs" + +[[bin]] +name = "pastel" +path = "src/cli/main.rs" + +[[test]] +name = "integration_tests" +path = "tests/integration_tests.rs" + +[[bench]] +name = "parse_color" +path = "benches/parse_color.rs" +harness = false + +[dependencies.clap] +version = "4" +features = [ + "suggestions", + "color", + "wrap_help", + "cargo", +] + +[dependencies.nom] +version = "7.1.3" + +[dependencies.once_cell] +version = "1.21.3" + +[dependencies.output_vt100] +version = "0.1" + +[dependencies.rand] +version = "0.9" + +[dependencies.regex] +version = "1.12" + +[dev-dependencies.approx] +version = "0.5.0" + +[dev-dependencies.assert_cmd] +version = "2.1.2" + +[dev-dependencies.criterion] +version = "0.7" + +[dev-dependencies.rand_xoshiro] +version = "0.7.0" + +[build-dependencies.clap] +version = "4" +features = ["cargo"] + +[build-dependencies.clap_complete] +version = "4" + +[build-dependencies.clap_mangen] +version = "0.2" + +[build-dependencies.once_cell] +version = "1.21.3" + +[build-dependencies.output_vt100] +version = "0.1" + +[lints.rust.unexpected_cfgs] +level = "warn" +priority = 0 +check-cfg = ["cfg(pastel_normal_build)"] + +[profile.release] +lto = true +codegen-units = 1 +strip = true + +[patch.crates-io] +getrandom = { path = "../getrandom-0.2.17" } +atty = { path = "../atty-0.2.14" } diff --git a/userland/upstream-src/pastel/Cargo.toml.orig b/userland/upstream-src/pastel/Cargo.toml.orig new file mode 100644 index 0000000000..8b117d7e7b --- /dev/null +++ b/userland/upstream-src/pastel/Cargo.toml.orig @@ -0,0 +1,58 @@ +[package] +authors = ["David Peter "] +categories = ["command-line-utilities"] +description = "A command-line tool to generate, analyze, convert and manipulate colors" +homepage = "https://github.com/sharkdp/pastel" +license = "MIT/Apache-2.0" +name = "pastel" +readme = "README.md" +repository = "https://github.com/sharkdp/pastel" +version = "0.12.0" +edition = "2021" +build = "build.rs" +exclude = ["doc/pastel.gif"] +rust-version = "1.83.0" + +[dependencies] +# library dependencies +nom = "7.1.3" +once_cell = "1.21.3" +output_vt100 = "0.1" +rand = "0.9" + +# binary-only dependencies (see https://github.com/rust-lang/cargo/issues/1982) +regex = "1.12" + +[dependencies.clap] +version = "4" +features = ["suggestions", "color", "wrap_help", "cargo"] + +[build-dependencies] +clap = { version = "4", features = ["cargo"] } +clap_complete = "4" +clap_mangen = "0.2" +once_cell = "1.21.3" +output_vt100 = "0.1" + +[[bin]] +name = "pastel" +path = "src/cli/main.rs" + + +[dev-dependencies] +approx = "0.5.0" +assert_cmd = "2.1.2" +rand_xoshiro = "0.7.0" +criterion = "0.7" + +[[bench]] +name = "parse_color" +harness = false + +[profile.release] +lto = true +strip = true +codegen-units = 1 + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(pastel_normal_build)'] } diff --git a/userland/upstream-src/pastel/LICENSE-APACHE b/userland/upstream-src/pastel/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/userland/upstream-src/pastel/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/userland/upstream-src/pastel/LICENSE-MIT b/userland/upstream-src/pastel/LICENSE-MIT new file mode 100644 index 0000000000..969d061e8b --- /dev/null +++ b/userland/upstream-src/pastel/LICENSE-MIT @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/userland/upstream-src/pastel/README.md b/userland/upstream-src/pastel/README.md new file mode 100644 index 0000000000..92dba7c317 --- /dev/null +++ b/userland/upstream-src/pastel/README.md @@ -0,0 +1,245 @@ +# pastel + +[![Build Status](https://img.shields.io/github/actions/workflow/status/sharkdp/pastel/CICD.yml?style=flat-square)](https://github.com/sharkdp/pastel/actions) +[![](https://img.shields.io/github/v/release/sharkdp/pastel?colorB=d7a400&style=flat-square)](https://github.com/sharkdp/pastel/releases) +[![](https://img.shields.io/crates/l/pastel.svg?colorB=ff7155&style=flat-square)](https://crates.io/crates/pastel) +[![](https://img.shields.io/crates/v/pastel.svg?colorB=ff69b4&style=flat-square)](https://crates.io/crates/pastel) + + +`pastel` is a command-line tool to generate, analyze, convert and manipulate colors. It supports many different color formats and color spaces like RGB (sRGB), HSL, CIELAB, CIELCh as well as ANSI 8-bit and 24-bit representations. + +## In action + +![pastel in action](doc/pastel.gif) + +## Tutorial + +### Getting help + +`pastel` provides a number of commands like `saturate`, `mix` or `paint`. To see a complete list, you can simply run +``` bash +pastel +``` +To get more information about a specific subcommand (say `mix`), you can call `pastel mix -h` or `pastel help mix`. + +### Composition + +Many `pastel` commands can be composed by piping the output of one command to another, for example: +``` bash +pastel random | pastel mix red | pastel lighten 0.2 | pastel format hex +``` + +### Specifying colors + +Colors can be specified in many different formats: +``` +lightslategray +'#778899' +778899 +789 +'rgb(119, 136, 153)' +'119,136,153' +'hsl(210, 14.3%, 53.3%)' +``` + +Colors can be passed as positional arguments, for example: +``` +pastel lighten 0.2 orchid orange lawngreen +``` +They can also be read from standard input. So this is equivalent: +``` +printf "%s\n" orchid orange lawngreen | pastel lighten 0.2 +``` +You can also explicitly specify which colors you want to read from the input. For example, this mixes `red` (which is read from STDIN) with `blue` (which is passed on the command line): +``` +pastel color red | pastel mix - blue +``` + +### Use cases and demo + +#### Converting colors from one format to another + +``` bash +pastel format hsl ff8000 +``` + +#### Show and analyze colors on the terminal + +``` bash +pastel color "rgb(255,50,127)" + +pastel color 556270 4ecdc4 c7f484 ff6b6b c44d58 +``` + +#### Pick a color from somewhere on the screen + +``` bash +pastel pick +``` + +#### Generate a set of N visually distinct colors + +``` +pastel distinct 8 +``` + +#### Get a list of all X11 / CSS color names + +``` bash +pastel list +``` + +#### Name a given color + +``` bash +pastel format name 44cc11 +``` + +#### Print colorized text from a shell script + +``` bash +bg="hotpink" +fg="$(pastel textcolor "$bg")" + +pastel paint "$fg" --on "$bg" "well readable text" +``` + +``` bash +pastel paint -n black --on red --bold " ERROR! " +echo " A serious error" + +pastel paint -n black --on yellow --bold " WARNING! " +echo " A warning message" + +pastel paint -n black --on limegreen --bold " INFO " +echo -n " Informational message with a " +echo -n "highlighted" | pastel paint -n default --underline +echo " word" +``` + +## Installation + +### On Debian-based systems + +You can download the latest Debian package from the [release page](https://github.com/sharkdp/pastel/releases) and install it via `dpkg`: +``` bash +wget "https://github.com/sharkdp/pastel/releases/download/v0.12.0/pastel_0.12.0_amd64.deb" +sudo dpkg -i pastel_0.12.0_amd64.deb +``` + +Alternatively, `pastel` is available in the official Debian repositories (currently in testing and unstable): +```bash +sudo apt update +sudo apt install pastel +``` + +### On Arch Linux + +You can install `pastel` from the [Extra](https://archlinux.org/packages/extra/x86_64/pastel/) repositories: +``` +sudo pacman -S pastel +``` + +### On Nix + +You can install `pastel` from the [Nix package](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/pastel/default.nix): +``` +nix-env --install pastel +``` + +### On MacOS + +You can install `pastel` via [Homebrew](https://formulae.brew.sh/formula/pastel) +``` +brew install pastel +``` + +### On Windows + +You can install `pastel` via [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/pastel.json) +``` +scoop install pastel +``` + +#### With Winget + +You can install `pastel` via [Winget](https://learn.microsoft.com/en-us/windows/package-manager/): +```bash +winget install sharkdp.pastel +``` + +### Via snap package + +[Get it from the Snap Store](https://snapcraft.io/pastel): +``` +sudo snap install pastel +``` + +### On NetBSD +Using the package manager: +``` +pkgin install pastel +``` + +From source: +``` +cd /usr/pkgsrc/graphics/pastel +make install +``` + +### On other distributions + +Check out the [release page](https://github.com/sharkdp/pastel/releases) for binary builds. + +### Via cargo (source) + +If you do not have cargo, install using [rust's installation documentation](https://doc.rust-lang.org/book/ch01-01-installation.html). + +If you have Rust 1.83 or higher, you can install `pastel` from source via `cargo`: +``` +cargo install pastel +``` + +Alternatively, you can install `pastel` directly from this repository by using +``` +git clone https://github.com/sharkdp/pastel +cargo install --path ./pastel +``` + +## Resources + +Interesting Wikipedia pages: + +* [Color difference](https://en.wikipedia.org/wiki/Color_difference) +* [CIE 1931 color space](https://en.wikipedia.org/wiki/CIE_1931_color_space) +* [CIELAB color space](https://en.wikipedia.org/wiki/CIELAB_color_space) +* [Line of purples](https://en.wikipedia.org/wiki/Line_of_purples) +* [Impossible color](https://en.wikipedia.org/wiki/Impossible_color) +* [sRGB](https://en.wikipedia.org/wiki/SRGB) +* [Color theory](https://en.wikipedia.org/wiki/Color_theory) +* [Eigengrau](https://en.wikipedia.org/wiki/Eigengrau) + +Color names: + +* [XKCD Color Survey Results](https://blog.xkcd.com/2010/05/03/color-survey-results/) +* [Peachpuffs and Lemonchiffons - talk about named colors](https://www.youtube.com/watch?v=HmStJQzclHc) +* [List of CSS color keywords](https://www.w3.org/TR/SVG11/types.html#ColorKeywords) + +Maximally distinct colors: + +* [How to automatically generate N "distinct" colors?](https://stackoverflow.com/q/470690/704831) +* [Publication on two algorithms to generate (maximally) distinct colors](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.65.2790) + +Other articles and videos: + +* [Color Matching](https://www.youtube.com/watch?v=82ItpxqPP4I) +* [Introduction to color spaces](https://ciechanow.ski/color-spaces/) + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/userland/upstream-src/pastel/benches/parse_color.rs b/userland/upstream-src/pastel/benches/parse_color.rs new file mode 100644 index 0000000000..e3ec73ea77 --- /dev/null +++ b/userland/upstream-src/pastel/benches/parse_color.rs @@ -0,0 +1,24 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use pastel::parser::parse_color; + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("parse_hex", |b| { + b.iter(|| { + parse_color("#ff0077"); + }) + }); + c.bench_function("parse_hex_short", |b| { + b.iter(|| { + parse_color("#f07"); + }) + }); + c.bench_function("parse_rgb", |b| { + b.iter(|| { + parse_color("rgb(255, 125, 0)"); + }) + }); + c.bench_function("parse_hsl", |b| b.iter(|| parse_color("hsl(280,20%,50%)"))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/userland/upstream-src/pastel/build.rs b/userland/upstream-src/pastel/build.rs new file mode 100644 index 0000000000..7ceb00708b --- /dev/null +++ b/userland/upstream-src/pastel/build.rs @@ -0,0 +1,47 @@ +use clap_complete::{generate_to, Shell}; +use clap_mangen::Man; +use std::fs; + +include!("src/cli/colorpicker_tools.rs"); +include!("src/cli/cli.rs"); + +fn main() { + let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or_else(|| std::env::var_os("OUT_DIR")); + let outdir = match var { + None => return, + Some(outdir) => outdir, + }; + fs::create_dir_all(&outdir).unwrap(); + + let mut cmd = build_cli(); + + for shell in [Shell::Bash, Shell::Zsh, Shell::Fish, Shell::PowerShell] { + generate_to(shell, &mut cmd, crate_name!(), &outdir).unwrap(); + } + + let man = Man::new(cmd.clone()); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer).expect("Man page generation failed"); + + let man_path = std::path::Path::new(&outdir).join("pastel.1"); + fs::write(man_path, buffer).expect("Failed to write main man page"); + + for subcommand in cmd.get_subcommands() { + let subcommand_name = subcommand.get_name(); + + // help command doesn't need its own man page + if subcommand_name == "help" { + continue; + } + + let man = Man::new(subcommand.clone()); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer) + .expect("Subcommand man page generation failed"); + + let man_path = std::path::Path::new(&outdir).join(format!("pastel-{}.1", subcommand_name)); + fs::write(man_path, buffer).expect("Failed to write subcommand man page"); + } + + println!("cargo:rustc-cfg=pastel_normal_build"); +} diff --git a/userland/upstream-src/pastel/doc/colorcheck.md b/userland/upstream-src/pastel/doc/colorcheck.md new file mode 100644 index 0000000000..0dfaa1801a --- /dev/null +++ b/userland/upstream-src/pastel/doc/colorcheck.md @@ -0,0 +1,21 @@ +# colorcheck + +The `colorcheck` command can be used to test whether or not your terminal emulator +properly supports 24-bit true color mode (16,777,216 colors). If this is not the case, `pastel` +can only show 8-bit approximations (256 colors): + +``` bash +pastel colorcheck +``` + +If everything works correctly, the output should look likes this (the background color and font +colors may be different, but the color panels should look the same): + +![](colorcheck.png) + +If you find it hard to compare the colors visually, you can also use a colorpicker (`pastel pick`) +to make sure that the three colors in the 24-bit panels are (from left to right): + + * `#492732`, `rgb(73, 39, 50)` + * `#10331e`, `rgb(16, 51, 30)` + * `#1d365a`, `rgb(29, 54, 90)` diff --git a/userland/upstream-src/pastel/doc/colorcheck.png b/userland/upstream-src/pastel/doc/colorcheck.png new file mode 100644 index 0000000000000000000000000000000000000000..24cc6d7f3ee7ef8aee2fac7d2222209c39ea779e GIT binary patch literal 4152 zcmbVPXIPV4wvHkM6_6tWhoTTHU??gAp+|%Cs$f8>NRc8vKf zwr_UF=C`SO7v$Ke&qxgo&O0f`x>YjWE&d&Z(kfpe5f zd}N{L?H4nCE8V^M(;9S?Ta))*bFr<{t&?99-NyKtieF_ir?EtGU~Wz-7j%f!xrj&# zt#uYfYjv^ULz+=s9S)tLPt;@;xo_)*Zv;QRY5;DiaX2z zb(+so?>35q?Ny0gW}-^3e`h0do)Rw2FT+ zO8!)JS}(%tk1?Zk`>y5uipYK_ucpaG$G{+k`z1F9m(Hy+mF;Y=CRqm{H8Wik4d9go#h})=fR^(DJB*hVH}059_%%u$O5KO+)ou9v2!h zoIl2>0~KJqKw^HJC~Vo^$$)WWzCPR8@Ib7RaudP!H{M&k*J`ca2lCOG#gCrU&EsMz zWnpAK20g>vb36S^?Dxh&Kh%d2i%=im>ccwi))z}9+s;%Uf^G&JBTay;X}e6Efn0z_ zaYLTds4zj=j$Gi!8C1Ah-Mm@f5PWj?THv(xhlsN>pP+FCj;LTu_@UKMSY$?p-DFJG3IKj?X>Fi>D(!D;?Pzal=KEl!Q~+_?fB0hEG5 zYfH;(sqR=cD`Vr4wite=(Ml^ZP8JpxVyn|wby7@>+uB5Z4*uI%jZq~{UUv4!wzk3I zhus*{kHf<Xg{UX2|cOZ7oeRaI32 zfuN|Uh@vkZG&V4J@3A~IGGZKzijIov`SJyAXb8u>_go$Og9bYNP+d!FWpNP>hja4q z6c!eqVq_HmodMxE5(PcO&JOUjKT_e=3G4KARZkFMJbrw4rE2vF9zT$Guh^<1eq>~1 zcGenp%h%UeL!+~){^G@puC5jq79Le5^w&g1`}+H*=H_ZQavz$c=(AMhEvM84UZER7VZ<-WrpEUFX`#&oiUhHY_rTl4Dsy`i$0D^(*8su zi3{`vv*l>kg7V|3r==Ai7d$mGdV`ok@f0iV2NqPA!LUKuV zZw)p!Hm0Ef7>Obj{*@W9Yhx1=C>>p<851Mp;3Elb?e049(b18SeJT~;ILj?otOC5f zpe~0$dp1%RfV_HDXOE~x?6VPDUS38$Jvcb%?d=7y=|~fb5s9Rsp~2(vtB0*Y&{LO~ z=$V+tip+z`%MqaD)$Ln5I|wl`A)~TK0OR@j`2a)3)?IJ1vlC|r_&GQ@^b1YH!^7P@ zJmdoos>$!Ova&!GlrnxpWs?>sChq5whDJw)mzU?~iv}M77H}-L7vn4*9O>y<`@Fi= znRGox!eQvWOUB?M(;Dy6j0_7aD`Ls?moHzGl$F)X9opL3SR&z9u8f!3>w9>3xVx9) z@yeiD{{H?(Mk%tqa&mrS)n4$pH<_6@Tik*hd2Nl-aT7ob9tY>+ zT;hqrkXJ^l0CY%8TYZS{8)pYtXlZGK@%S`(Kf`>qva)gq7yA`cU&@-fu5Jv;E<7|e zG(7w)2ghY0q1Z!g<0A?C(jmkcb}CNaRLYy2ZCdDLV-vNpvbKw`g8MtYRUiie+F{l&+&f-^-HjUhh8TLTCMjW z1~dPXgI_*@{AC0%9J8Q)fslZJ!Fy*I5*9VW@VB=X$}TSJ0UYUi@X^sx6JujjoLgr9 zf>x?z^z-KpnEGP3KyizUi^`GbG`)RSMkJUz?BCV(gcCSO5}`)+!g85Is>WoCj&08Y=!d6Ui{i z2PqPX^raL5{blAm|HCYtot**H1H7~olasx_HUu{{H6)UPW=jeN zyO~QWD=$xoi!-V7r*=p3LGdLeVl}2nX=(47_Bd5-E>_l3fZf1ANLce7Q&UILq)RKB zKyD)=k(l|OQu~2Kb8NCt#mb7KR~O)l-%t#UjBP$Fr%&ghfo?31dz%yQaImv`ZcMj< zlyQW&G&S8?SWiJX%}!4Pa&%sVf%xNBAcAnXtE=nwx54WK_KodrCI$wvBn_pE-tqCe zvNDh6P*%HA!$n~^xtfZKitKC~EEYTWiExrlC@MltQ&XHX{8G#UsCXFrl^0~y=%ggC zot4q*>T19;%fsb(98QCqnS*0tYKn!8t%cm6JJ!)AD<^lJj}K%bADt`vGC|nL11t_W zQyWRe*w_dl5c)&n0SCVMXqojjAm3rld=7wEsNZ+Mv$C?XsHiAAJNwNWI1ER!bFj19 z*xDlOEp~Nv^}z>7B+}>4V$^XDTU%Qlov8ZzQ3$Y?bF$c|*x1j5gZK3GF5s@FbOi?o z+Yc0gzuXl#bNu*m2L}ftk%)Tgv$Jwm()ose+4S^uK|ui!_4W1jy}iAj9-W|i=cyJ( zuNwf@8#5i<1esQH!}W`E^YfH?D37fFzCrTS(?RtU6BBFHopJSn)ILsukjTi$kdPz_ z!!N>TDB z0820Mh}1NmY}12IU-%I;4# zhs7KS&?cf?PXzM;p}lZn8Z@bQb4frHQBTXt%0LT@KgCnKJDalcv7w=%rKN@6e{;R5 zP#g4>1*SEeTwF`vzWE;Pd+mDk4h-mL^r{?zLZL?a=-uJohzO>XF6*nmfAgqP%D9%& z#mLC0O`Yvdl`6LFe;v++NbZ^?}Z;D_1S+0`EG*)K%b*VlvQqokz7bIdENuCA`U zeCOWt+=7C5M`is2> = Lazy::new(|| { + (16..=255) + .map(|code| (code, Color::from_ansi_8bit(code).to_lab())) + .collect() +}); + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Mode { + Ansi8Bit, + TrueColor, +} + +#[derive(Debug)] +pub struct UnknownColorModeError(pub String); + +impl Mode { + pub fn from_mode_str(mode_str: &str) -> Result, UnknownColorModeError> { + match mode_str { + "24bit" | "truecolor" => Ok(Some(Mode::TrueColor)), + "8bit" => Ok(Some(Mode::Ansi8Bit)), + "off" => Ok(None), + value => Err(UnknownColorModeError(value.into())), + } + } +} + +fn cube_to_8bit(code: u8) -> u8 { + assert!(code < 6); + match code { + 0 => 0, + _ => 55 + 40 * code, + } +} + +pub trait AnsiColor { + fn from_ansi_8bit(code: u8) -> Self; + fn to_ansi_8bit(&self) -> u8; + + fn to_ansi_sequence(&self, mode: Mode) -> String; +} + +impl AnsiColor for Color { + /// Create a color from an 8-bit ANSI escape code + /// + /// See: + fn from_ansi_8bit(code: u8) -> Color { + match code { + 0 => Color::black(), + 1 => Color::maroon(), + 2 => Color::green(), + 3 => Color::olive(), + 4 => Color::navy(), + 5 => Color::purple(), + 6 => Color::teal(), + 7 => Color::silver(), + 8 => Color::gray(), + 9 => Color::red(), + 10 => Color::lime(), + 11 => Color::yellow(), + 12 => Color::blue(), + 13 => Color::fuchsia(), + 14 => Color::aqua(), + 15 => Color::white(), + 16..=231 => { + // 6 x 6 x 6 cube of 216 colors. We need to decode from + // + // code = 16 + 36 × r + 6 × g + b + + let code_rgb = code - 16; + let blue = code_rgb % 6; + + let code_rg = (code_rgb - blue) / 6; + let green = code_rg % 6; + + let red = (code_rg - green) / 6; + + Color::from_rgb(cube_to_8bit(red), cube_to_8bit(green), cube_to_8bit(blue)) + } + 232..=255 => { + // grayscale from (almost) black to (almost) white in 24 steps + + let gray_value = 10 * (code - 232) + 8; + Color::from_rgb(gray_value, gray_value, gray_value) + } + } + } + + /// Approximate a color by its closest 8-bit ANSI color (as measured by the perceived + /// color distance). + /// + /// See: + fn to_ansi_8bit(&self) -> u8 { + let self_lab = self.to_lab(); + ANSI_LAB_REPRESENTATIONS + .iter() + .min_by_key(|(_, lab)| ciede2000(&self_lab, lab) as i32) + .expect("list of codes can not be empty") + .0 + } + + /// Return an ANSI escape sequence in 8-bit or 24-bit representation: + /// * 8-bit: `ESC[38;5;CODEm`, where CODE represents the color. + /// * 24-bit: `ESC[38;2;R;G;Bm`, where R, G, B represent 8-bit RGB values + fn to_ansi_sequence(&self, mode: Mode) -> String { + match mode { + Mode::Ansi8Bit => format!("\x1b[38;5;{}m", self.to_ansi_8bit()), + Mode::TrueColor => { + let rgba = self.to_rgba(); + format!("\x1b[38;2;{r};{g};{b}m", r = rgba.r, g = rgba.g, b = rgba.b) + } + } + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Style { + foreground: Option, + background: Option, + bold: bool, + italic: bool, + underline: bool, +} + +impl Style { + pub fn foreground(&mut self, color: &Color) -> &mut Self { + self.foreground = Some(color.clone()); + self + } + + pub fn on>(&mut self, color: C) -> &mut Self { + self.background = Some(color.borrow().clone()); + self + } + + pub fn bold(&mut self, on: bool) -> &mut Self { + self.bold = on; + self + } + + pub fn italic(&mut self, on: bool) -> &mut Self { + self.italic = on; + self + } + + pub fn underline(&mut self, on: bool) -> &mut Self { + self.underline = on; + self + } + + pub fn escape_sequence(&self, mode: Mode) -> String { + let mut codes: Vec = vec![]; + + if let Some(ref fg) = self.foreground { + match mode { + Mode::Ansi8Bit => codes.extend_from_slice(&[38, 5, fg.to_ansi_8bit()]), + Mode::TrueColor => { + let rgb = fg.to_rgba(); + codes.extend_from_slice(&[38, 2, rgb.r, rgb.g, rgb.b]); + } + } + } + if let Some(ref bg) = self.background { + match mode { + Mode::Ansi8Bit => codes.extend_from_slice(&[48, 5, bg.to_ansi_8bit()]), + Mode::TrueColor => { + let rgb = bg.to_rgba(); + codes.extend_from_slice(&[48, 2, rgb.r, rgb.g, rgb.b]); + } + } + } + + if self.bold { + codes.push(1); + } + + if self.italic { + codes.push(3); + } + + if self.underline { + codes.push(4); + } + + if codes.is_empty() { + codes.push(0); + } + + format!( + "\x1b[{codes}m", + codes = codes + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(";") + ) + } +} + +impl From for Style { + fn from(color: Color) -> Style { + Style { + foreground: Some(color), + background: None, + bold: false, + italic: false, + underline: false, + } + } +} + +impl From<&Color> for Style { + fn from(color: &Color) -> Style { + color.clone().into() + } +} + +impl From<&Style> for Style { + fn from(style: &Style) -> Style { + style.clone() + } +} + +impl From<&mut Style> for Style { + fn from(style: &mut Style) -> Style { + style.clone() + } +} + +pub trait ToAnsiStyle { + fn ansi_style(&self) -> Style; +} + +impl ToAnsiStyle for Color { + fn ansi_style(&self) -> Style { + self.clone().into() + } +} + +#[cfg(not(windows))] +pub fn get_colormode() -> Option { + use std::env; + let env_nocolor = env::var_os("NO_COLOR"); + if env_nocolor.is_some() { + return None; + } + + let env_colorterm = env::var("COLORTERM").ok(); + match env_colorterm.as_deref() { + Some("truecolor") | Some("24bit") => Some(Mode::TrueColor), + _ => Some(Mode::Ansi8Bit), + } +} + +#[cfg(windows)] +pub fn get_colormode() -> Option { + use std::env; + let env_nocolor = env::var_os("NO_COLOR"); + match env_nocolor { + Some(_) => None, + // Assume 24bit support on Windows + None => Some(Mode::TrueColor), + } +} + +#[derive(Default, Debug, Clone, Copy)] +pub struct Brush { + mode: Option, +} + +impl Brush { + pub fn from_mode(mode: Option) -> Self { + Brush { mode } + } + + pub fn from_environment(stream: &T) -> Result { + let mode = if stream.is_terminal() { + let env_color_mode = std::env::var("PASTEL_COLOR_MODE").ok(); + match env_color_mode.as_deref() { + Some(mode_str) => Mode::from_mode_str(mode_str)?, + None => get_colormode(), + } + } else { + None + }; + Ok(Brush { mode }) + } + + pub fn paint(self, text: S, style: impl Into"#).unwrap()); + +pub static START_TEMPLATE: Lazy = + Lazy::new(|| Regex::new(r#""#).unwrap()); +pub static END_TEMPLATE: Lazy = Lazy::new(|| Regex::new(r#""#).unwrap()); + +pub static STARTING_MARKDOWN_REGEX: Lazy = Lazy::new(|| Regex::new(r#"```\S+\s"#).unwrap()); +pub static ENDING_MARKDOWN_REGEX: Lazy = Lazy::new(|| Regex::new(r#"```\s?"#).unwrap()); + +pub static STARTING_LF_BLOCK_REGEX: Lazy = Lazy::new(|| Regex::new(r#"\{="#).unwrap()); +pub static ENDING_LF_BLOCK_REGEX: Lazy = Lazy::new(|| Regex::new(r#"=}"#).unwrap()); + +/// A memory of a regex matched. +/// The values provided by `Self::start` and `Self::end` are in the same space as the +/// start value supplied to `RegexCache::build` +pub struct Capture<'a> { + start: usize, + text: &'a [u8], +} + +impl Capture<'_> { + #[inline(always)] + fn start(&self) -> usize { + self.start + } + #[inline(always)] + pub fn end(&self) -> usize { + self.start + self.text.len() + } + #[inline(always)] + pub fn as_bytes(&self) -> &[u8] { + self.text + } +} + +impl<'a> std::fmt::Debug for Capture<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Capture") + .field("start", &self.start) + .field("end", &self.end()) + .field("text", &String::from_utf8_lossy(self.text)) + .finish() + } +} + +pub(crate) struct RegexCache<'a> { + inner: Option>, +} + +/// Embedding regexes are similar between different sets of languages. +/// `RegexFamily` records both which family the language belongs to, +/// as well as the actual matches +pub(crate) enum RegexFamily<'a> { + HtmlLike(HtmlLike<'a>), + LinguaFranca(SimpleCapture<'a>), + Markdown(SimpleCapture<'a>), + Rust, +} + +pub(crate) struct HtmlLike<'a> { + start_script: Option]>>, + start_style: Option]>>, + start_template: Option]>>, +} + +pub(crate) struct SimpleCapture<'a> { + starts: Option]>>, +} + +impl<'a> HtmlLike<'a> { + pub fn start_script_in_range<'this>( + &'this self, + start: usize, + end: usize, + ) -> Option>> { + filter_range(self.start_script.as_ref()?, start, end) + } + + pub fn start_style_in_range<'this>( + &'this self, + start: usize, + end: usize, + ) -> Option>> { + filter_range(self.start_style.as_ref()?, start, end) + } + + pub fn start_template_in_range<'this>( + &'this self, + start: usize, + end: usize, + ) -> Option>> { + filter_range(self.start_template.as_ref()?, start, end) + } +} + +impl<'a> SimpleCapture<'a> { + pub fn starts_in_range<'this>( + &'this self, + start: usize, + end: usize, + ) -> Option<&'this Capture<'a>> { + filter_range(self.starts.as_ref()?, start, end).and_then(|mut it| it.next()) + } + + fn make_capture( + regex: &Regex, + lines: &'a [u8], + start: usize, + end: usize, + ) -> Option> { + let capture = SimpleCapture { + starts: save_captures(regex, lines, start, end), + }; + + if capture.starts.is_some() { + Some(capture) + } else { + None + } + } +} + +fn filter_range<'dataset, 'cap>( + dataset: &'dataset [Capture<'cap>], + start: usize, + end: usize, +) -> Option>> { + let pos = dataset + .binary_search_by_key(&start, |cap| cap.start()) + .ok()?; + + if pos >= dataset.len() || dataset[pos].end() > end { + None + } else { + Some( + dataset[pos..] + .iter() + .take_while(move |cap| cap.end() <= end), + ) + } +} + +impl<'a> RegexCache<'a> { + /// Returns the language family for which regexes were matched, if any + pub(crate) fn family(&self) -> Option<&RegexFamily> { + self.inner.as_ref() + } + + /// Tries to memoize any matches of embedding regexes that occur within lines[start..end] + /// for the given language. Any `Capture` values eventually recovered will use the same + /// zero for their start as the given `start` argument. + pub(crate) fn build(lang: LanguageType, lines: &'a [u8], start: usize, end: usize) -> Self { + let inner = match lang { + LanguageType::Markdown | LanguageType::UnrealDeveloperMarkdown => { + SimpleCapture::make_capture(&STARTING_MARKDOWN_REGEX, lines, start, end) + .map(RegexFamily::Markdown) + } + LanguageType::Rust => Some(RegexFamily::Rust), + LanguageType::LinguaFranca => { + SimpleCapture::make_capture(&STARTING_LF_BLOCK_REGEX, lines, start, end) + .map(RegexFamily::LinguaFranca) + } + LanguageType::Html + | LanguageType::RubyHtml + | LanguageType::Svelte + | LanguageType::Vue + | LanguageType::GlimmerJs + | LanguageType::GlimmerTs => { + let html = HtmlLike { + start_script: save_captures(&START_SCRIPT, lines, start, end), + start_style: save_captures(&START_STYLE, lines, start, end), + start_template: save_captures(&START_TEMPLATE, lines, start, end), + }; + + if html.start_script.is_some() + || html.start_style.is_some() + || html.start_template.is_some() + { + Some(RegexFamily::HtmlLike(html)) + } else { + None + } + } + _ => None, + }; + Self { inner } + } +} + +fn save_captures<'a>( + regex: &Regex, + lines: &'a [u8], + start: usize, + end: usize, +) -> Option]>> { + let v: Vec<_> = regex + .captures(&lines[start..end])? + .iter() + .flatten() + .map(|cap| Capture { + start: start + cap.start(), + text: cap.as_bytes(), + }) + .collect(); + + if v.is_empty() { + None + } else { + Some(v.into()) + } +} diff --git a/userland/upstream-src/tokei/src/language/language_type.rs b/userland/upstream-src/tokei/src/language/language_type.rs new file mode 100644 index 0000000000..bffdf7a87a --- /dev/null +++ b/userland/upstream-src/tokei/src/language/language_type.rs @@ -0,0 +1,355 @@ +use std::{ + borrow::Cow, + fmt, + fs::File, + io::{self, Read}, + path::{Path, PathBuf}, + str::FromStr, +}; + +use crate::{ + config::Config, + language::syntax::{FileContext, LanguageContext, SyntaxCounter}, + stats::{CodeStats, Report}, + utils::{ext::SliceExt, fs as fsutils}, +}; + +use encoding_rs_io::DecodeReaderBytesBuilder; +use grep_searcher::{LineIter, LineStep}; +use once_cell::sync::Lazy; +use rayon::prelude::*; +use serde::Serialize; + +use self::LanguageType::*; + +include!(concat!(env!("OUT_DIR"), "/language_type.rs")); + +impl Serialize for LanguageType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.name()) + } +} + +impl LanguageType { + /// Parses a given [`Path`] using the [`LanguageType`]. Returning [`Report`] + /// on success and giving back ownership of [`PathBuf`] on error. + pub fn parse(self, path: PathBuf, config: &Config) -> Result { + let text = { + let f = match File::open(&path) { + Ok(f) => f, + Err(e) => return Err((e, path)), + }; + let mut s = Vec::new(); + let mut reader = DecodeReaderBytesBuilder::new().build(f); + + if let Err(e) = reader.read_to_end(&mut s) { + return Err((e, path)); + } + s + }; + + let mut stats = Report::new(path); + + stats += self.parse_from_slice(text, config); + + Ok(stats) + } + + /// Parses the text provided as the given [`LanguageType`]. + pub fn parse_from_str>(self, text: A, config: &Config) -> CodeStats { + self.parse_from_slice(text.as_ref().as_bytes(), config) + } + + /// Parses the bytes provided as the given [`LanguageType`]. + pub fn parse_from_slice>(self, text: A, config: &Config) -> CodeStats { + let text = text.as_ref(); + + if self == Jupyter { + return self + .parse_jupyter(text.as_ref(), config) + .unwrap_or_default(); + } + + let syntax = { + let mut syntax_mut = SyntaxCounter::new(self); + if self == LinguaFranca { + syntax_mut.lf_embedded_language = self.find_lf_target_language(text); + } + syntax_mut + }; + + if let Some(end) = syntax.shared.important_syntax.find(text).and_then(|m| { + // Get the position of the last line before the important + // syntax. + text[..=m.start()] + .iter() + .rev() + .position(|&c| c == b'\n') + .filter(|&p| p != 0) + .map(|p| m.start() - p) + }) { + let (skippable_text, rest) = text.split_at(end + 1); + let is_fortran = syntax.shared.is_fortran; + let is_literate = syntax.shared.is_literate; + let comments = syntax.shared.line_comments; + trace!( + "Using Simple Parse on {:?}", + String::from_utf8_lossy(skippable_text) + ); + let parse_lines = move || self.parse_lines(config, rest, CodeStats::new(), syntax); + let simple_parse = move || { + LineIter::new(b'\n', skippable_text) + .par_bridge() + .map(|line| { + // FORTRAN has a rule where it only counts as a comment if it's the + // first character in the column, so removing starting whitespace + // could cause a miscount. + let line = if is_fortran { line } else { line.trim() }; + if line.trim().is_empty() { + (1, 0, 0) + } else if is_literate + || comments.iter().any(|c| line.starts_with(c.as_bytes())) + { + (0, 0, 1) + } else { + (0, 1, 0) + } + }) + .reduce(|| (0, 0, 0), |a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2)) + }; + + let (mut stats, (blanks, code, comments)) = rayon::join(parse_lines, simple_parse); + + stats.blanks += blanks; + stats.code += code; + stats.comments += comments; + stats + } else { + self.parse_lines(config, text, CodeStats::new(), syntax) + } + } + + #[inline] + fn parse_lines( + self, + config: &Config, + lines: &[u8], + mut stats: CodeStats, + mut syntax: SyntaxCounter, + ) -> CodeStats { + let mut stepper = LineStep::new(b'\n', 0, lines.len()); + + while let Some((start, end)) = stepper.next(lines) { + let line = &lines[start..end]; + // FORTRAN has a rule where it only counts as a comment if it's the + // first character in the column, so removing starting whitespace + // could cause a miscount. + let line = if syntax.shared.is_fortran { + line + } else { + line.trim() + }; + trace!("{}", String::from_utf8_lossy(line)); + + if syntax.try_perform_single_line_analysis(line, &mut stats) { + continue; + } + + let started_in_comments = !syntax.stack.is_empty() + || (config.treat_doc_strings_as_comments == Some(true) + && syntax.quote.is_some() + && syntax.quote_is_doc_quote); + let ended_with_comments = + match syntax.perform_multi_line_analysis(lines, start, end, config) { + crate::language::syntax::AnalysisReport::Normal(end) => end, + crate::language::syntax::AnalysisReport::ChildLanguage(FileContext { + language, + end, + stats: blob, + }) => { + match language { + LanguageContext::Markdown { balanced, language } => { + // Add the lines for the code fences. + stats.comments += if balanced { 2 } else { 1 }; + // Add the code inside the fence to the stats. + *stats.blobs.entry(language).or_default() += blob; + } + LanguageContext::Rust => { + // Add all the markdown blobs. + *stats.blobs.entry(LanguageType::Markdown).or_default() += blob; + } + LanguageContext::LinguaFranca => { + let child_lang = syntax.get_lf_target_language(); + *stats.blobs.entry(child_lang).or_default() += blob; + } + LanguageContext::Html { language } => { + stats.code += 1; + // Add all the markdown blobs. + *stats.blobs.entry(language).or_default() += blob; + } + } + + // Advance to after the language code and the delimiter.. + stepper = LineStep::new(b'\n', end, lines.len()); + continue; + } + }; + trace!("{}", String::from_utf8_lossy(line)); + + if syntax.shared.is_literate + || syntax.line_is_comment(line, config, ended_with_comments, started_in_comments) + { + stats.comments += 1; + trace!("Comment No.{}", stats.comments); + trace!("Was the Comment stack empty?: {}", !started_in_comments); + } else { + stats.code += 1; + trace!("Code No.{}", stats.code); + } + } + + stats + } + + fn parse_jupyter(&self, json: &[u8], config: &Config) -> Option { + #[derive(Deserialize)] + struct Jupyter { + cells: Vec, + metadata: JupyterMetadata, + } + + #[derive(Clone, Copy, Deserialize, PartialEq, Eq)] + #[serde(rename_all = "lowercase")] + enum CellType { + Markdown, + Code, + } + + #[derive(Deserialize)] + struct JupyterCell { + cell_type: CellType, + source: Vec, + } + + #[derive(Deserialize)] + struct JupyterMetadata { + kernelspec: serde_json::Value, + language_info: serde_json::Value, + } + + let jupyter: Jupyter = serde_json::from_slice(json).ok()?; + + let mut jupyter_stats = CodeStats::new(); + + let language = jupyter + .metadata + .kernelspec + .get("language") + .and_then(serde_json::Value::as_str) + .and_then(|v| LanguageType::from_str(v).ok()) + .or_else(|| { + jupyter + .metadata + .language_info + .get("file_extension") + .and_then(serde_json::Value::as_str) + .and_then(LanguageType::from_file_extension) + }) + .unwrap_or(LanguageType::Python); + + let iter = jupyter + .cells + .par_iter() + .map(|cell| match cell.cell_type { + CellType::Markdown => ( + LanguageType::Markdown, + LanguageType::Markdown.parse_from_str(cell.source.join(""), config), + ), + CellType::Code => ( + language, + language.parse_from_str(cell.source.join(""), config), + ), + }) + .collect::>(); + + for (language, stats) in iter { + *jupyter_stats.blobs.entry(language).or_default() += &stats; + jupyter_stats += &stats; + } + + Some(jupyter_stats) + } + + /// The embedded language in LF is declared in a construct that looks like this: `target C;`, `target Python`. + /// This is the first thing in the file (although there may be comments before). + fn find_lf_target_language(&self, bytes: &[u8]) -> Option { + use regex::bytes::Regex; + static LF_TARGET_REGEX: Lazy = + Lazy::new(|| Regex::new(r#"(?m)\btarget\s+(\w+)\s*($|;|\{)"#).unwrap()); + LF_TARGET_REGEX.captures(bytes).and_then(|captures| { + let name = captures.get(1).unwrap().as_bytes(); + if name == b"CCpp" { + // this is a special alias for the C target in LF + Some(C) + } else { + let name_str = &String::from_utf8_lossy(name); + let by_name = LanguageType::from_name(name_str); + if by_name.is_none() { + trace!("LF target not recognized: {}", name_str); + } + by_name + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::{fs, path::Path}; + + #[test] + fn rust_allows_nested() { + assert!(LanguageType::Rust.allows_nested()); + } + + fn assert_stats(stats: &CodeStats, blanks: usize, code: usize, comments: usize) { + assert_eq!(stats.blanks, blanks, "expected {} blank lines", blanks); + assert_eq!(stats.code, code, "expected {} code lines", code); + assert_eq!( + stats.comments, comments, + "expected {} comment lines", + comments + ); + } + + #[test] + fn jupyter_notebook_has_correct_totals() { + let sample_notebook = + fs::read_to_string(Path::new("tests").join("data").join("jupyter.ipynb")).unwrap(); + + let stats = LanguageType::Jupyter + .parse_jupyter(sample_notebook.as_bytes(), &Config::default()) + .unwrap(); + + assert_stats(&stats, 115, 528, 333); + } + + #[test] + fn lf_embedded_language_is_counted() { + let file_text = + fs::read_to_string(Path::new("tests").join("data").join("linguafranca.lf")).unwrap(); + + let stats = LinguaFranca.parse_from_str(file_text, &Config::default()); + + assert_stats(&stats, 9, 11, 8); + + assert_eq!(stats.blobs.len(), 1, "num embedded languages"); + let rust_stats = stats.blobs.get(&Rust).expect("should have a Rust entry"); + assert_stats(rust_stats, 2, 5, 1); + } +} diff --git a/userland/upstream-src/tokei/src/language/language_type.tera.rs b/userland/upstream-src/tokei/src/language/language_type.tera.rs new file mode 100644 index 0000000000..4dd1be3af4 --- /dev/null +++ b/userland/upstream-src/tokei/src/language/language_type.tera.rs @@ -0,0 +1,470 @@ +use arbitrary::Arbitrary; + +/// Represents a individual programming language. Can be used to provide +/// information about the language, such as multi line comments, single line +/// comments, string literal syntax, whether a given language allows nesting +/// comments. +#[derive(Deserialize)] +#[derive(Arbitrary, Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[non_exhaustive] +#[allow(clippy::upper_case_acronyms)] +pub enum LanguageType { + {% for key, value in languages -%} + #[allow(missing_docs)] {% if value.name is defined %} #[serde(alias = "{{value.name}}")] {% else %} #[serde(alias = "{{key}}")] {% endif %} {{key}}, + {% endfor %} +} + +impl LanguageType { + + /// Returns the display name of a language. + /// + /// ``` + /// # use tokei::*; + /// let bash = LanguageType::Bash; + /// + /// assert_eq!(bash.name(), "BASH"); + /// ``` + pub fn name(self) -> &'static str { + match self { + {% for key, value in languages -%} + {{key}} => {% if value.name %}"{{value.name}}"{% else %}"{{key}}"{% endif %}, + {% endfor %} + } + } + + pub(crate) fn _is_blank(self) -> bool { + match self { + {% for key, v in languages -%} + {{key}} => {{ v.blank | default(value=false) }}, + {% endfor %} + } + } + + pub(crate) fn is_fortran(self) -> bool { + self == LanguageType::FortranModern || + self == LanguageType::FortranLegacy + } + + /// Returns whether the language is "literate", meaning that it considered + /// to primarily be documentation and is counted primarily as comments + /// rather than procedural code. + pub fn is_literate(self) -> bool { + match self { + {% for key, v in languages -%} + {{key}} => {{ v.literate | default(value=false) }}, + {% endfor %} + } + } + + /// Provides every variant in a Vec + pub fn list() -> &'static [(Self, &'static [&'static str])] { + &[{% for key, val in languages -%} + ({{key}}, + {% if val.extensions %} &[{% for extension in val.extensions %}"{{extension}}", {% endfor %}], + {% else %} &[], + {% endif %}), + {% endfor %}] + } + + /// Returns the single line comments of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::Rust; + /// assert_eq!(lang.line_comments(), &["//"]); + /// ``` + pub fn line_comments(self) -> &'static [&'static str] { + match self { + {% for key, value in languages -%} + {{key}} => &[{% for item in value.line_comment | default(value=[]) %}"{{item}}",{% endfor %}], + {% endfor %} + } + } + + /// Returns the single line comments of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::Rust; + /// assert_eq!(lang.multi_line_comments(), &[("/*", "*/")]); + /// ``` + pub fn multi_line_comments(self) -> &'static [(&'static str, &'static str)] + { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- for items in value.multi_line_comments | default(value=[]) -%} + ({% for item in items %}"{{item}}",{% endfor %}), + {%- endfor -%} + ], + {% endfor %} + } + } + + + /// Returns whether the language allows nested multi line comments. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::Rust; + /// assert!(lang.allows_nested()); + /// ``` + pub fn allows_nested(self) -> bool { + match self { + {% for key, v in languages -%} + {{key}} => {{ v.nested | default(value=false) }}, + {% endfor %} + } + } + + /// Returns what nested comments the language has. (Currently only D has + /// any of this type.) + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::D; + /// assert_eq!(lang.nested_comments(), &[("/+", "+/")]); + /// ``` + pub fn nested_comments(self) -> &'static [(&'static str, &'static str)] + { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- for items in value.nested_comments | default(value=[]) -%} + ({% for item in items %}"{{item}}",{% endfor %}), + {%- endfor -%} + ], + {% endfor %} + } + } + + /// Returns the quotes of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::C; + /// assert_eq!(lang.quotes(), &[("\"", "\"")]); + /// ``` + pub fn quotes(self) -> &'static [(&'static str, &'static str)] { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- for items in value.quotes | default(value=[]) -%} + ({% for item in items %}"{{item}}",{% endfor %}), + {%- endfor -%} + ], + {% endfor %} + } + } + + /// Returns the verbatim quotes of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::CSharp; + /// assert_eq!(lang.verbatim_quotes(), &[("@\"", "\"")]); + /// ``` + pub fn verbatim_quotes(self) -> &'static [(&'static str, &'static str)] { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- for items in value.verbatim_quotes | default(value=[]) -%} + ({% for item in items %}"{{item}}",{% endfor %}), + {%- endfor -%} + ], + {% endfor %} + } + } + + /// Returns the doc quotes of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::Python; + /// assert_eq!(lang.doc_quotes(), &[("\"\"\"", "\"\"\""), ("'''", "'''")]); + /// ``` + pub fn doc_quotes(self) -> &'static [(&'static str, &'static str)] { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {% for items in value.doc_quotes | default(value=[])-%} + ({% for item in items %}"{{item}}",{% endfor %}), + {%- endfor %} + ], + {%- endfor %} + } + } + + /// Returns the shebang of a language. + /// ``` + /// use tokei::LanguageType; + /// let lang = LanguageType::Bash; + /// assert_eq!(lang.shebangs(), &["#!/bin/bash"]); + /// ``` + pub fn shebangs(self) -> &'static [&'static str] { + match self { + {% for key, lang in languages -%} + {{key}} => &[{% for item in lang.shebangs | default(value=[]) %}"{{item}}",{% endfor %}], + {% endfor %} + } + } + + pub(crate) fn any_multi_line_comments(self) -> &'static [(&'static str, &'static str)] { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- set starting_multi_line_comments = value.multi_line_comments | default(value=[]) -%} + {%- set starting_nested_comments = value.nested_comments | default(value=[]) -%} + {%- for item in starting_multi_line_comments | concat(with=starting_nested_comments) -%} + ("{{item.0}}", "{{item.1}}"), + {%- endfor -%} + ], + {% endfor %} + } + } + + pub(crate) fn any_comments(self) -> &'static [&'static str] { + match self { + {% for key, value in languages -%} + {{key}} => &[ + {%- set starting_multi_line_comments = value.multi_line_comments | default(value=[]) -%} + {%- set starting_nested_comments = value.nested_comments | default(value=[]) -%} + + {%- for item in starting_multi_line_comments | concat(with=starting_nested_comments) -%} + "{{item.0}}", + "{{item.1}}", + {%- endfor -%} + {%- for item in value.line_comment | default(value=[]) -%} + "{{item}}", + {%- endfor -%} + ], + {% endfor %} + } + } + + /// Returns the parts of syntax that determines whether tokei can skip large + /// parts of analysis. + pub fn important_syntax(self) -> &'static [&'static str] { + match self { + {% for key, value in languages -%} + {%- set starting_quotes = value.quotes | default(value=[]) | map(attribute="0") -%} + {%- set starting_doc_quotes = value.doc_quotes | default(value=[]) | map(attribute="0") -%} + {%- set starting_multi_line_comments = value.multi_line_comments | default(value=[]) | map(attribute="0") -%} + {%- set starting_nested_comments = value.nested_comments | default(value=[]) | map(attribute="0") -%} + {%- set important_syntax = value.important_syntax | default(value=[]) -%} + + {{key}} => &[ + {%- for item in starting_quotes | + concat(with=starting_doc_quotes) | + concat(with=starting_multi_line_comments) | + concat(with=starting_nested_comments) | + concat(with=important_syntax) -%} + "{{item}}", + {%- endfor -%} + {%- for context in value.contexts | default(value=[]) -%} + {% if value.kind == "html" %} + "<{{context.tag}}", + {% endif %} + {%- endfor -%} + ], + {% endfor %} + } + } + + /// Get language from a file path. May open and read the file. + /// + /// ```no_run + /// use tokei::{Config, LanguageType}; + /// + /// let rust = LanguageType::from_path("./main.rs", &Config::default()); + /// + /// assert_eq!(rust, Some(LanguageType::Rust)); + /// ``` + pub fn from_path>(entry: P, _config: &Config) + -> Option + { + let entry = entry.as_ref(); + + if let Some(filename) = fsutils::get_filename(entry) { + match &*filename { + {% for key, value in languages -%} + {%- if value.filenames -%} + {%- for item in value.filenames -%} + | "{{item}}" + {%- endfor -%} + => return Some({{key}}), + {% endif -%} + {%- endfor %} + _ => () + } + } + + match fsutils::get_extension(entry) { + Some(extension) => LanguageType::from_file_extension(extension.as_str()), + None => LanguageType::from_shebang(entry), + } + } + + /// Get language from a file extension. + /// + /// ```no_run + /// use tokei::LanguageType; + /// + /// let rust = LanguageType::from_file_extension("rs"); + /// + /// assert_eq!(rust, Some(LanguageType::Rust)); + /// ``` + #[must_use] + pub fn from_file_extension(extension: &str) -> Option { + match extension { + {% for key, value in languages -%} + {%- if value.extensions -%} + {%- for item in value.extensions %}| "{{item}}" {% endfor %}=> Some({{key}}), + {% endif -%} + {%- endfor %} + extension => { + warn!("Unknown extension: {}", extension); + None + }, + } + } + + /// Get language from its name. + /// + /// ```no_run + /// use tokei::LanguageType; + /// + /// let rust = LanguageType::from_name("Rust"); + /// + /// assert_eq!(rust, Some(LanguageType::Rust)); + /// ``` + #[must_use] + pub fn from_name(name: &str) -> Option { + match name { + {% for key, value in languages -%} + {% if value.name and value.name != key -%} + | "{{value.name}}" + {% endif -%} + | "{{key}}" => Some({{key}}), + {% endfor %} + unknown => { + warn!("Unknown language name: {}", unknown); + None + }, + } + } + + /// Get language from its MIME type if available. + /// + /// ```no_run + /// use tokei::LanguageType; + /// + /// let lang = LanguageType::from_mime("application/javascript"); + /// + /// assert_eq!(lang, Some(LanguageType::JavaScript)); + /// ``` + #[must_use] + pub fn from_mime(mime: &str) -> Option { + match mime { + {% for key, value in languages -%} + {%- if value.mime -%} + {%- for item in value.mime %}| "{{item}}" {% endfor %}=> Some({{key}}), + {% endif -%} + {%- endfor %} + _ => { + warn!("Unknown MIME: {}", mime); + None + }, + } + } + + /// Get language from a shebang. May open and read the file. + /// + /// ```no_run + /// use tokei::LanguageType; + /// + /// let rust = LanguageType::from_shebang("./main.rs"); + /// + /// assert_eq!(rust, Some(LanguageType::Rust)); + /// ``` + pub fn from_shebang>(entry: P) -> Option { + // Read at max `READ_LIMIT` bytes from the given file. + // A typical shebang line has a length less than 32 characters; + // e.g. '#!/bin/bash' - 11B / `#!/usr/bin/env python3` - 22B + // It is *very* unlikely the file contains a valid shebang syntax + // if we don't find a newline character after searching the first 128B. + const READ_LIMIT: usize = 128; + + let mut file = File::open(entry).ok()?; + let mut buf = [0; READ_LIMIT]; + + let len = file.read(&mut buf).ok()?; + let buf = &buf[..len]; + + let first_line = buf.split(|b| *b == b'\n').next()?; + let first_line = std::str::from_utf8(first_line).ok()?; + + let mut words = first_line.split_whitespace(); + match words.next() { + {# First match against any shebang paths, and then check if the + language matches any found in the environment shebang path. #} + {% for key, value in languages -%} + {%- if value.shebangs %} + {%- for item in value.shebangs %}| Some("{{item}}") {% endfor %}=> Some({{key}}), + {% endif -%} + {%- endfor %} + + Some("#!/usr/bin/env") => { + if let Some(word) = words.next() { + match word { + {% for key, value in languages -%} + {%- if value.env -%} + {%- for item in value.env %} + {% if loop.index == 1 %} + _ if word.starts_with("{{item}}") + {% else %} + || word.starts_with("{{item}}") + {% endif %} + {% endfor %}=> Some({{key}}), + {% endif -%} + {%- endfor %} + env => { + warn!("Unknown environment: {:?}", env); + None + } + } + } else { + None + } + } + _ => None, + } + } +} + +impl FromStr for LanguageType { + type Err = &'static str; + + fn from_str(from: &str) -> Result { + match &*from.to_lowercase() { + {% for key, value in languages %} + {% if value.name %}"{{value.name | lower}}"{% else %}"{{key | lower}}"{% endif %} + => Ok({{key}}), + {% endfor %} + _ => Err("Language not found, please use `-l` to see all available\ + languages."), + } + } +} + +impl fmt::Display for LanguageType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + + +impl<'a> From for Cow<'a, LanguageType> { + fn from(from: LanguageType) -> Self { + Cow::Owned(from) + } +} + +impl<'a> From<&'a LanguageType> for Cow<'a, LanguageType> { + fn from(from: &'a LanguageType) -> Self { + Cow::Borrowed(from) + } +} diff --git a/userland/upstream-src/tokei/src/language/languages.rs b/userland/upstream-src/tokei/src/language/languages.rs new file mode 100644 index 0000000000..87a749ac7f --- /dev/null +++ b/userland/upstream-src/tokei/src/language/languages.rs @@ -0,0 +1,166 @@ +use std::{ + collections::{btree_map, BTreeMap}, + iter::IntoIterator, + ops::{AddAssign, Deref, DerefMut}, + path::Path, +}; + +use rayon::prelude::*; + +use crate::{ + config::Config, + language::{Language, LanguageType}, + utils, +}; + +/// A newtype representing a list of languages counted in the provided +/// directory. +/// ([_List of +/// Languages_](https://github.com/XAMPPRocky/tokei#supported-languages)) +#[derive(Debug, Default, PartialEq)] +pub struct Languages { + inner: BTreeMap, +} + +impl serde::Serialize for Languages { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Languages { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let map = <_>::deserialize(deserializer)?; + + Ok(Self::from_previous(map)) + } +} + +impl Languages { + fn from_previous(map: BTreeMap) -> Self { + use std::collections::btree_map::Entry; + let mut me = Self::new(); + + for (name, input_language) in map { + match me.entry(name) { + Entry::Occupied(mut entry) => { + *entry.get_mut() += input_language; + } + Entry::Vacant(entry) => { + entry.insert(input_language); + } + } + } + me + } + + /// Populates the `Languages` struct with statistics about languages + /// provided by [`Language`]. + /// + /// Takes a `&[&str]` of paths to recursively traverse, paths can be + /// relative, absolute or glob paths. A second `&[&str]` of paths to ignore, + /// these strings use the `.gitignore` syntax, such as `target` + /// or `**/*.bk`. + /// + /// ```no_run + /// use tokei::{Config, Languages}; + /// + /// let mut languages = Languages::new(); + /// languages.get_statistics(&["."], &[".git", "target"], &Config::default()); + /// ``` + /// + /// [`Language`]: struct.Language.html + pub fn get_statistics>( + &mut self, + paths: &[A], + ignored: &[&str], + config: &Config, + ) { + utils::fs::get_all_files(paths, ignored, &mut self.inner, config); + self.inner.par_iter_mut().for_each(|(_, l)| l.total()); + } + + /// Constructs a new, Languages struct. Languages is always empty and does + /// not allocate. + /// + /// ```rust + /// # use tokei::*; + /// let languages = Languages::new(); + /// ``` + #[must_use] + pub fn new() -> Self { + Languages::default() + } + + /// Summary of the Languages struct. + #[must_use] + pub fn total(self: &Languages) -> Language { + let mut total = Language::new(); + for (ty, l) in self { + let language = l.summarise(); + total.comments += language.comments; + total.blanks += language.blanks; + total.code += language.code; + total.inaccurate |= language.inaccurate; + total.children.insert(*ty, language.reports.clone()); + } + total + } +} + +impl IntoIterator for Languages { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl<'a> IntoIterator for &'a Languages { + type Item = (&'a LanguageType, &'a Language); + type IntoIter = btree_map::Iter<'a, LanguageType, Language>; + + fn into_iter(self) -> Self::IntoIter { + self.inner.iter() + } +} + +impl<'a> IntoIterator for &'a mut Languages { + type Item = (&'a LanguageType, &'a mut Language); + type IntoIter = btree_map::IterMut<'a, LanguageType, Language>; + + fn into_iter(self) -> Self::IntoIter { + self.inner.iter_mut() + } +} + +impl AddAssign> for Languages { + fn add_assign(&mut self, rhs: BTreeMap) { + for (name, language) in rhs { + if let Some(result) = self.inner.get_mut(&name) { + *result += language; + } + } + } +} + +impl Deref for Languages { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Languages { + fn deref_mut(&mut self) -> &mut BTreeMap { + &mut self.inner + } +} diff --git a/userland/upstream-src/tokei/src/language/mod.rs b/userland/upstream-src/tokei/src/language/mod.rs new file mode 100644 index 0000000000..f6ffa06cab --- /dev/null +++ b/userland/upstream-src/tokei/src/language/mod.rs @@ -0,0 +1,176 @@ +mod embedding; +pub mod language_type; +pub mod languages; +mod syntax; + +use std::{collections::BTreeMap, mem, ops::AddAssign}; + +pub use self::{language_type::*, languages::Languages}; + +use crate::{sort::Sort, stats::Report}; + +/// A struct representing statistics about a single Language. +#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize)] +pub struct Language { + /// The total number of blank lines. + pub blanks: usize, + /// The total number of lines of code. + pub code: usize, + /// The total number of comments(both single, and multi-line) + pub comments: usize, + /// A collection of statistics of individual files. + pub reports: Vec, + /// A map of any languages found in the reports. + pub children: BTreeMap>, + /// Whether this language had problems with file parsing + pub inaccurate: bool, +} + +impl Language { + /// Constructs a new empty Language with the comments provided. + /// + /// ``` + /// # use tokei::*; + /// let mut rust = Language::new(); + /// ``` + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Returns the total number of lines. + #[inline] + #[must_use] + pub fn lines(&self) -> usize { + self.blanks + self.code + self.comments + } + + /// Add a `Report` to the Language. This will not update the totals in the + /// Language struct. + pub fn add_report(&mut self, report: Report) { + for (lang, stats) in &report.stats.blobs { + let mut new_report = Report::new(report.name.clone()); + new_report.stats = stats.clone(); + + self.children.entry(*lang).or_default().push(new_report); + } + + self.reports.push(report); + } + + /// Marks this language as possibly not reflecting correct stats. + #[inline] + pub fn mark_inaccurate(&mut self) { + self.inaccurate = true; + } + + /// Creates a new `Language` from `self`, which is a summarised version + /// of the language that doesn't contain any children. It will count + /// non-blank lines in child languages as code unless the child language is + /// considered "literate" then it will be counted as comments. + #[must_use] + pub fn summarise(&self) -> Language { + let mut summary = self.clone(); + + for reports in self.children.values() { + for stats in reports.iter().map(|r| r.stats.summarise()) { + summary.comments += stats.comments; + summary.code += stats.code; + summary.blanks += stats.blanks; + } + } + + summary + } + + /// Totals up the statistics of the `Stat` structs currently contained in + /// the language. + /// + /// ```no_run + /// use std::{collections::BTreeMap, path::PathBuf}; + /// use tokei::Language; + /// + /// let mut language = Language::new(); + /// + /// // Add stats... + /// + /// assert_eq!(0, language.lines()); + /// + /// language.total(); + /// + /// assert_eq!(10, language.lines()); + /// ``` + pub fn total(&mut self) { + let mut blanks = 0; + let mut code = 0; + let mut comments = 0; + + for report in &self.reports { + blanks += report.stats.blanks; + code += report.stats.code; + comments += report.stats.comments; + } + + self.blanks = blanks; + self.code = code; + self.comments = comments; + } + + /// Checks if the language is empty. Empty meaning it doesn't have any + /// statistics. + /// + /// ``` + /// # use tokei::*; + /// let rust = Language::new(); + /// + /// assert!(rust.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + self.code == 0 && self.comments == 0 && self.blanks == 0 && self.children.is_empty() + } + + /// Sorts each of the `Report`s contained in the language based + /// on what category is provided. + /// + /// ```no_run + /// use std::{collections::BTreeMap, path::PathBuf}; + /// use tokei::{Language, Sort}; + /// + /// let mut language = Language::new(); + /// + /// // Add stats... + /// + /// language.sort_by(Sort::Lines); + /// assert_eq!(20, language.reports[0].stats.lines()); + /// + /// language.sort_by(Sort::Code); + /// assert_eq!(8, language.reports[0].stats.code); + /// ``` + pub fn sort_by(&mut self, category: Sort) { + match category { + Sort::Blanks => self + .reports + .sort_by(|a, b| b.stats.blanks.cmp(&a.stats.blanks)), + Sort::Comments => self + .reports + .sort_by(|a, b| b.stats.comments.cmp(&a.stats.comments)), + Sort::Code => self.reports.sort_by(|a, b| b.stats.code.cmp(&a.stats.code)), + Sort::Files => self.reports.sort_by(|a, b| a.name.cmp(&b.name)), + Sort::Lines => self + .reports + .sort_by(|a, b| b.stats.lines().cmp(&a.stats.lines())), + } + } +} + +impl AddAssign for Language { + fn add_assign(&mut self, mut rhs: Self) { + self.comments += rhs.comments; + self.blanks += rhs.blanks; + self.code += rhs.code; + self.reports.extend(mem::take(&mut rhs.reports)); + self.children.extend(mem::take(&mut rhs.children)); + self.inaccurate |= rhs.inaccurate; + } +} diff --git a/userland/upstream-src/tokei/src/language/syntax.rs b/userland/upstream-src/tokei/src/language/syntax.rs new file mode 100644 index 0000000000..95a3186ea2 --- /dev/null +++ b/userland/upstream-src/tokei/src/language/syntax.rs @@ -0,0 +1,683 @@ +use std::sync::Arc; + +use aho_corasick::AhoCorasick; +use dashmap::DashMap; +use grep_searcher::LineStep; +use log::Level::Trace; +use once_cell::sync::Lazy; + +use super::embedding::{ + RegexCache, RegexFamily, ENDING_LF_BLOCK_REGEX, ENDING_MARKDOWN_REGEX, END_SCRIPT, END_STYLE, + END_TEMPLATE, +}; +use crate::LanguageType::LinguaFranca; +use crate::{stats::CodeStats, utils::ext::SliceExt, Config, LanguageType}; + +/// Tracks the syntax of the language as well as the current state in the file. +/// Current has what could be consider three types of mode. +/// - `plain` mode: This is the normal state, blanks are counted as blanks, +/// string literals can trigger `string` mode, and comments can trigger +/// `comment` mode. +/// - `string` mode: This when the state machine is current inside a string +/// literal for a given language, comments cannot trigger `comment` mode while +/// in `string` mode. +/// - `comment` mode: This when the state machine is current inside a comment +/// for a given language, strings cannot trigger `string` mode while in +/// `comment` mode. +#[derive(Clone, Debug)] +pub(crate) struct SyntaxCounter { + pub(crate) shared: Arc, + pub(crate) quote: Option<&'static str>, + pub(crate) quote_is_doc_quote: bool, + pub(crate) stack: Vec<&'static str>, + pub(crate) quote_is_verbatim: bool, + pub(crate) lf_embedded_language: Option, +} + +#[derive(Clone, Debug)] +pub(crate) struct FileContext { + pub(crate) language: LanguageContext, + pub(crate) stats: CodeStats, + pub(crate) end: usize, +} + +impl FileContext { + pub fn new(language: LanguageContext, end: usize, stats: CodeStats) -> Self { + Self { + language, + stats, + end, + } + } +} + +#[derive(Clone, Debug)] +pub(crate) enum LanguageContext { + Html { + language: LanguageType, + }, + LinguaFranca, + Markdown { + balanced: bool, + language: LanguageType, + }, + Rust, +} + +#[derive(Clone, Debug)] +pub(crate) struct SharedMatchers { + pub language: LanguageType, + pub allows_nested: bool, + pub doc_quotes: &'static [(&'static str, &'static str)], + pub important_syntax: AhoCorasick, + #[allow(dead_code)] + pub any_comments: &'static [&'static str], + pub is_fortran: bool, + pub is_literate: bool, + pub line_comments: &'static [&'static str], + pub any_multi_line_comments: &'static [(&'static str, &'static str)], + pub multi_line_comments: &'static [(&'static str, &'static str)], + pub nested_comments: &'static [(&'static str, &'static str)], + pub string_literals: &'static [(&'static str, &'static str)], + pub verbatim_string_literals: &'static [(&'static str, &'static str)], +} + +impl SharedMatchers { + pub fn new(language: LanguageType) -> Arc { + static MATCHERS: Lazy>> = Lazy::new(DashMap::new); + + MATCHERS + .entry(language) + .or_insert_with(|| Arc::new(Self::init(language))) + .value() + .clone() + } + + pub fn init(language: LanguageType) -> Self { + fn init_corasick(pattern: &[&'static str]) -> AhoCorasick { + AhoCorasick::builder() + .match_kind(aho_corasick::MatchKind::LeftmostLongest) + .start_kind(aho_corasick::StartKind::Unanchored) + .prefilter(true) + .kind(Some(aho_corasick::AhoCorasickKind::DFA)) + .build(pattern) + .unwrap() + } + + Self { + language, + allows_nested: language.allows_nested(), + doc_quotes: language.doc_quotes(), + is_fortran: language.is_fortran(), + is_literate: language.is_literate(), + important_syntax: init_corasick(language.important_syntax()), + any_comments: language.any_comments(), + line_comments: language.line_comments(), + multi_line_comments: language.multi_line_comments(), + any_multi_line_comments: language.any_multi_line_comments(), + nested_comments: language.nested_comments(), + string_literals: language.quotes(), + verbatim_string_literals: language.verbatim_quotes(), + } + } +} + +#[derive(Debug)] +pub(crate) enum AnalysisReport { + /// No child languages were found, contains a boolean representing whether + /// the line ended with comments or not. + Normal(bool), + ChildLanguage(FileContext), +} + +impl SyntaxCounter { + pub(crate) fn new(language: LanguageType) -> Self { + Self { + shared: SharedMatchers::new(language), + quote_is_doc_quote: false, + quote_is_verbatim: false, + stack: Vec::with_capacity(1), + lf_embedded_language: None, + quote: None, + } + } + + /// Returns whether the syntax is currently in plain mode. + pub(crate) fn is_plain_mode(&self) -> bool { + self.quote.is_none() && self.stack.is_empty() + } + + /// Returns whether the syntax is currently in string mode. + pub(crate) fn _is_string_mode(&self) -> bool { + self.quote.is_some() + } + + /// Returns whether the syntax is currently in comment mode. + pub(crate) fn _is_comment_mode(&self) -> bool { + !self.stack.is_empty() + } + + pub(crate) fn get_lf_target_language(&self) -> LanguageType { + // in case the target declaration was not found, default it to that language + const DEFAULT_LANG: LanguageType = LinguaFranca; + self.lf_embedded_language.unwrap_or(DEFAULT_LANG) + } + + #[inline] + pub(crate) fn parse_line_comment(&self, window: &[u8]) -> bool { + if self.quote.is_some() || !self.stack.is_empty() { + false + } else if let Some(comment) = self + .shared + .line_comments + .iter() + .find(|c| window.starts_with(c.as_bytes())) + { + trace!("Start {:?}", comment); + true + } else { + false + } + } + + /// Try to see if we can determine what a line is from examining the whole + /// line at once. Returns `true` if successful. + pub(crate) fn try_perform_single_line_analysis( + &self, + line: &[u8], + stats: &mut crate::stats::CodeStats, + ) -> bool { + if !self.is_plain_mode() { + false + } else if line.trim().is_empty() { + stats.blanks += 1; + trace!("Blank No.{}", stats.blanks); + true + } else if self.shared.important_syntax.is_match(line) { + false + } else { + trace!("^ Skippable"); + + if self.shared.is_literate + || self + .shared + .line_comments + .iter() + .any(|c| line.starts_with(c.as_bytes())) + { + stats.comments += 1; + trace!("Comment No.{}", stats.comments); + } else { + stats.code += 1; + trace!("Code No.{}", stats.code); + } + + true + } + } + + pub(crate) fn perform_multi_line_analysis( + &mut self, + lines: &[u8], + start: usize, + end: usize, + config: &Config, + ) -> AnalysisReport { + let mut ended_with_comments = false; + let mut skip = 0; + macro_rules! skip { + ($skip:expr) => {{ + skip = $skip - 1; + }}; + } + + let regex_cache = RegexCache::build(self.shared.language, lines, start, end); + + for i in start..end { + if skip != 0 { + skip -= 1; + continue; + } + + let window = &lines[i..]; + + if window.trim().is_empty() { + break; + } + + ended_with_comments = false; + let is_end_of_quote_or_multi_line = self + .parse_end_of_quote(window) + .or_else(|| self.parse_end_of_multi_line(window)); + + if let Some(skip_amount) = is_end_of_quote_or_multi_line { + ended_with_comments = true; + skip!(skip_amount); + continue; + } else if self.quote.is_some() { + continue; + } + + if let Some(child) = self.parse_context(lines, i, end, config, ®ex_cache) { + return AnalysisReport::ChildLanguage(child); + } + + let is_quote_or_multi_line = self + .parse_quote(window) + .or_else(|| self.parse_multi_line_comment(window)); + + if let Some(skip_amount) = is_quote_or_multi_line { + skip!(skip_amount); + continue; + } + + if self.parse_line_comment(window) { + ended_with_comments = true; + break; + } + } + + AnalysisReport::Normal(ended_with_comments) + } + + /// Performs a set of heuristics to determine whether a line is a comment or + /// not. The procedure is as follows. + /// + /// - Yes/No: Counted as Comment + /// + /// 1. Check if we're in string mode + /// 1. Check if string literal is a doc string and whether tokei has + /// been configured to treat them as comments. + /// - Yes: When the line starts with the doc string or when we are + /// continuing from a previous line. + /// - No: The string is a normal string literal or tokei isn't + /// configured to count them as comments. + /// 2. If we're not in string mode, check if we left it this on this line. + /// - Yes: When we found a doc quote and we started in comments. + /// 3. Yes: When the whole line is a comment e.g. `/* hello */` + /// 4. Yes: When the previous line started a multi-line comment. + /// 5. Yes: When the line starts with a comment. + /// 6. No: Any other input. + pub(crate) fn line_is_comment( + &self, + line: &[u8], + config: &crate::Config, + _ended_with_comments: bool, + started_in_comments: bool, + ) -> bool { + let trimmed = line.trim(); + let whole_line_is_comment = || { + self.shared + .line_comments + .iter() + .any(|c| trimmed.starts_with(c.as_bytes())) + || self + .shared + .any_multi_line_comments + .iter() + .any(|(start, end)| { + trimmed.starts_with(start.as_bytes()) && trimmed.ends_with(end.as_bytes()) + }) + }; + let starts_with_comment = || { + let quote = match self.stack.last() { + Some(q) => q, + _ => return false, + }; + + self.shared + .any_multi_line_comments + .iter() + .any(|(start, end)| end == quote && trimmed.starts_with(start.as_bytes())) + }; + + // `Some(true)` in order to respect the current configuration. + #[allow(clippy::if_same_then_else)] + if self.quote.is_some() { + if self.quote_is_doc_quote && config.treat_doc_strings_as_comments == Some(true) { + self.quote.map_or(false, |q| line.starts_with(q.as_bytes())) + || (self.quote.is_some()) + } else { + false + } + } else if self + .shared + .doc_quotes + .iter() + .any(|(_, e)| line.contains_slice(e.as_bytes())) + && started_in_comments + { + true + } else if (whole_line_is_comment)() { + true + } else if started_in_comments { + true + } else { + (starts_with_comment)() + } + } + + #[inline] + pub(crate) fn parse_context( + &mut self, + lines: &[u8], + start: usize, + end: usize, + config: &Config, + regex_cache: &RegexCache, + ) -> Option { + use std::str::FromStr; + + // static TYPE_REGEX: Lazy = Lazy::new(|| Regex::new(r#"type="(.*)".*>"#).unwrap()); + if self.quote.is_some() || !self.stack.is_empty() { + return None; + } + + match regex_cache.family()? { + RegexFamily::Markdown(md) => { + if !lines[start..end].contains_slice(b"```") { + return None; + } + + let opening_fence = md.starts_in_range(start, end)?; + let start_of_code = opening_fence.end(); + let closing_fence = ENDING_MARKDOWN_REGEX.find(&lines[start_of_code..]); + if let Some(m) = &closing_fence { + trace!("{:?}", String::from_utf8_lossy(m.as_bytes())); + } + let end_of_code = closing_fence + .map_or_else(|| lines.len(), |fence| start_of_code + fence.start()); + let end_of_code_block = + closing_fence.map_or_else(|| lines.len(), |fence| start_of_code + fence.end()); + let balanced = closing_fence.is_some(); + let identifier = &opening_fence.as_bytes().trim()[3..]; + + let language = identifier + .split(|&b| b == b',') + .find_map(|s| LanguageType::from_str(&String::from_utf8_lossy(s)).ok())?; + trace!( + "{} BLOCK: {:?}", + language, + String::from_utf8_lossy(&lines[start_of_code..end_of_code]) + ); + let stats = + language.parse_from_slice(lines[start_of_code..end_of_code].trim(), config); + + Some(FileContext::new( + LanguageContext::Markdown { balanced, language }, + end_of_code_block, + stats, + )) + } + RegexFamily::Rust => { + let rest = &lines[start..]; + let comment_syntax = if rest.trim_start().starts_with(b"///") { + b"///" + } else if rest.trim_start().starts_with(b"//!") { + b"//!" + } else { + return None; + }; + + let mut stepper = LineStep::new(b'\n', start, lines.len()); + let mut markdown = Vec::new(); + let mut end_of_block = lines.len(); + + while let Some((start, end)) = stepper.next(lines) { + if lines[start..].trim().starts_with(comment_syntax) { + trace!("{}", String::from_utf8_lossy(&lines[start..end])); + let line = lines[start..end].trim_start(); + let stripped_line = &line[3.min(line.len())..]; + markdown.extend_from_slice(stripped_line); + end_of_block = end; + } else { + end_of_block = start; + break; + } + } + + trace!("Markdown found: {:?}", String::from_utf8_lossy(&markdown)); + let doc_block = LanguageType::Markdown.parse_from_slice(markdown.trim(), config); + + Some(FileContext::new( + LanguageContext::Rust, + end_of_block, + doc_block, + )) + } + RegexFamily::LinguaFranca(lf) => { + let opening_fence = lf.starts_in_range(start, end)?; + let start_of_code = opening_fence.end(); + let closing_fence = ENDING_LF_BLOCK_REGEX.find(&lines[start_of_code..]); + let end_of_code = closing_fence + .map_or_else(|| lines.len(), |fence| start_of_code + fence.start()); + + let block_contents = &lines[start_of_code..end_of_code]; + trace!("LF block: {:?}", String::from_utf8_lossy(block_contents)); + let stats = self.get_lf_target_language().parse_from_slice( + block_contents.trim_first_and_last_line_of_whitespace(), + config, + ); + trace!("-> stats: {:?}", stats); + + Some(FileContext::new( + LanguageContext::LinguaFranca, + end_of_code, + stats, + )) + } + RegexFamily::HtmlLike(html) => { + if let Some(mut captures) = html.start_script_in_range(start, end) { + let start_of_code = captures.next().unwrap().end(); + let closing_tag = END_SCRIPT.find(&lines[start_of_code..])?; + let end_of_code = start_of_code + closing_tag.start(); + let language = captures + .next() + .and_then(|m| { + LanguageType::from_mime(&String::from_utf8_lossy(m.as_bytes().trim())) + }) + .unwrap_or(LanguageType::JavaScript); + let script_contents = &lines[start_of_code..end_of_code]; + if script_contents.trim().is_empty() { + return None; + } + + let stats = language.parse_from_slice( + script_contents.trim_first_and_last_line_of_whitespace(), + config, + ); + Some(FileContext::new( + LanguageContext::Html { language }, + end_of_code, + stats, + )) + } else if let Some(mut captures) = html.start_style_in_range(start, end) { + let start_of_code = captures.next().unwrap().end(); + let closing_tag = END_STYLE.find(&lines[start_of_code..])?; + let end_of_code = start_of_code + closing_tag.start(); + let language = captures + .next() + .and_then(|m| { + LanguageType::from_str( + &String::from_utf8_lossy(m.as_bytes().trim()).to_lowercase(), + ) + .ok() + }) + .unwrap_or(LanguageType::Css); + let style_contents = &lines[start_of_code..end_of_code]; + if style_contents.trim().is_empty() { + return None; + } + + let stats = language.parse_from_slice( + style_contents.trim_first_and_last_line_of_whitespace(), + config, + ); + Some(FileContext::new( + LanguageContext::Html { language }, + end_of_code, + stats, + )) + } else if let Some(mut captures) = html.start_template_in_range(start, end) { + let start_of_code = captures.next().unwrap().end(); + let closing_tag = END_TEMPLATE.find(&lines[start_of_code..])?; + let end_of_code = start_of_code + closing_tag.start(); + let language = captures + .next() + .and_then(|m| { + LanguageType::from_str( + &String::from_utf8_lossy(m.as_bytes().trim()).to_lowercase(), + ) + .ok() + }) + .unwrap_or(LanguageType::Html); + + let template_contents = &lines[start_of_code..end_of_code]; + if template_contents.trim().is_empty() { + return None; + } + let stats = language.parse_from_slice( + template_contents.trim_first_and_last_line_of_whitespace(), + config, + ); + Some(FileContext::new( + LanguageContext::Html { language }, + end_of_code, + stats, + )) + } else { + None + } + } + } + } + + #[inline] + pub(crate) fn parse_quote(&mut self, window: &[u8]) -> Option { + if !self.stack.is_empty() { + return None; + } + + if let Some((start, end)) = self + .shared + .doc_quotes + .iter() + .find(|(s, _)| window.starts_with(s.as_bytes())) + { + trace!("Start Doc {:?}", start); + self.quote = Some(end); + self.quote_is_verbatim = false; + self.quote_is_doc_quote = true; + return Some(start.len()); + } + + if let Some((start, end)) = self + .shared + .verbatim_string_literals + .iter() + .find(|(s, _)| window.starts_with(s.as_bytes())) + { + trace!("Start verbatim {:?}", start); + self.quote = Some(end); + self.quote_is_verbatim = true; + self.quote_is_doc_quote = false; + return Some(start.len()); + } + + if let Some((start, end)) = self + .shared + .string_literals + .iter() + .find(|(s, _)| window.starts_with(s.as_bytes())) + { + trace!("Start {:?}", start); + self.quote = Some(end); + self.quote_is_verbatim = false; + self.quote_is_doc_quote = false; + return Some(start.len()); + } + + None + } + + #[inline] + pub(crate) fn parse_end_of_quote(&mut self, window: &[u8]) -> Option { + #[allow(clippy::if_same_then_else)] + if self._is_string_mode() && window.starts_with(self.quote?.as_bytes()) { + let quote = self.quote.take().unwrap(); + trace!("End {:?}", quote); + Some(quote.len()) + } else if !self.quote_is_verbatim && window.starts_with(br"\\") { + Some(2) + } else if !self.quote_is_verbatim + && window.starts_with(br"\") + && self + .shared + .string_literals + .iter() + .any(|(start, _)| window[1..].starts_with(start.as_bytes())) + { + // Tell the state machine to skip the next character because it + // has been escaped if the string isn't a verbatim string. + Some(2) + } else { + None + } + } + + #[inline] + pub(crate) fn parse_multi_line_comment(&mut self, window: &[u8]) -> Option { + if self.quote.is_some() { + return None; + } + + let iter = self + .shared + .multi_line_comments + .iter() + .chain(self.shared.nested_comments); + for &(start, end) in iter { + if window.starts_with(start.as_bytes()) { + if self.stack.is_empty() + || self.shared.allows_nested + || self.shared.nested_comments.contains(&(start, end)) + { + self.stack.push(end); + + if log_enabled!(Trace) && self.shared.allows_nested { + trace!("Start nested {:?}", start); + } else { + trace!("Start {:?}", start); + } + } + + return Some(start.len()); + } + } + + None + } + + #[inline] + pub(crate) fn parse_end_of_multi_line(&mut self, window: &[u8]) -> Option { + if self + .stack + .last() + .map_or(false, |l| window.starts_with(l.as_bytes())) + { + let last = self.stack.pop().unwrap(); + + if log_enabled!(Trace) { + if self.stack.is_empty() { + trace!("End {:?}", last); + } else { + trace!("End {:?}. Still in comments.", last); + } + } + + Some(last.len()) + } else { + None + } + } +} diff --git a/userland/upstream-src/tokei/src/lib.rs b/userland/upstream-src/tokei/src/lib.rs new file mode 100644 index 0000000000..a2b4d0e662 --- /dev/null +++ b/userland/upstream-src/tokei/src/lib.rs @@ -0,0 +1,64 @@ +//! # Tokei: Count your code quickly. +//! +//! A simple, efficient library for counting code in directories. This +//! functionality is also provided as a +//! [CLI utility](//github.com/XAMPPRocky/tokei). Tokei uses a small state +//! machine rather than regular expressions found in other code counters. Tokei +//! can accurately count a lot more edge cases such as nested comments, or +//! comment syntax inside string literals. +//! +//! # Examples +//! +//! Gets the total lines of code from all rust files in current directory, +//! and all subdirectories. +//! +//! ```no_run +//! use std::collections::BTreeMap; +//! use std::fs::File; +//! use std::io::Read; +//! +//! use tokei::{Config, Languages, LanguageType}; +//! +//! // The paths to search. Accepts absolute, relative, and glob paths. +//! let paths = &["src", "tests"]; +//! // Exclude any path that contains any of these strings. +//! let excluded = &["target"]; +//! // `Config` allows you to configure what is searched and counted. +//! let config = Config::default(); +//! +//! let mut languages = Languages::new(); +//! languages.get_statistics(paths, excluded, &config); +//! let rust = &languages[&LanguageType::Rust]; +//! +//! println!("Lines of code: {}", rust.code); +//! ``` + +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_variables, + unstable_features, + unused_import_braces, + missing_docs +)] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde; + +#[macro_use] +mod utils; +mod config; +mod consts; +mod language; +mod sort; +mod stats; + +pub use self::{ + config::Config, + consts::*, + language::{Language, LanguageType, Languages}, + sort::Sort, + stats::{find_char_boundary, CodeStats, Report}, +}; diff --git a/userland/upstream-src/tokei/src/main.rs b/userland/upstream-src/tokei/src/main.rs new file mode 100644 index 0000000000..62619dfe69 --- /dev/null +++ b/userland/upstream-src/tokei/src/main.rs @@ -0,0 +1,128 @@ +#[macro_use] +extern crate log; + +mod cli; +mod cli_utils; +mod consts; +mod input; + +use std::{error::Error, io, process}; + +use tokei::{Config, Languages, Sort}; + +use crate::{ + cli::Cli, + cli_utils::Printer, + consts::{ + BLANKS_COLUMN_WIDTH, CODE_COLUMN_WIDTH, COMMENTS_COLUMN_WIDTH, FALLBACK_ROW_LEN, + LANGUAGE_COLUMN_WIDTH, LINES_COLUMN_WIDTH, PATH_COLUMN_WIDTH, + }, + input::add_input, +}; + +fn main() -> Result<(), Box> { + let mut cli = Cli::from_args(); + + if cli.print_languages { + Cli::print_supported_languages()?; + process::exit(0); + } + let config = cli.override_config(Config::from_config_files()); + let mut languages = Languages::new(); + + if let Some(input) = cli.file_input() { + if !add_input(input, &mut languages) { + Cli::print_input_parse_failure(input); + process::exit(1); + } + } + + let input = cli.input(); + + for path in &input { + if ::std::fs::metadata(path).is_err() { + eprintln!("Error: '{}' not found.", path); + process::exit(1); + } + } + + let columns = cli + .columns + .or(config.columns) + .or_else(|| { + if cli.files { + term_size::dimensions().map(|(w, _)| w) + } else { + None + } + }) + .unwrap_or(FALLBACK_ROW_LEN) + .max(FALLBACK_ROW_LEN); + + if cli.streaming == Some(crate::cli::Streaming::Simple) { + println!( + "#{:^LANGUAGE_COLUMN_WIDTH$} {:^PATH_COLUMN_WIDTH$} {:^LINES_COLUMN_WIDTH$} {:^CODE_COLUMN_WIDTH$} {:^COMMENTS_COLUMN_WIDTH$} {:^BLANKS_COLUMN_WIDTH$}", + "language", "path", "lines", "code", "comments", "blanks" + ); + println!( + "{:>LANGUAGE_COLUMN_WIDTH$} {:LINES_COLUMN_WIDTH$} {:>CODE_COLUMN_WIDTH$} {:>COMMENTS_COLUMN_WIDTH$} {:>BLANKS_COLUMN_WIDTH$}", + (0..10).map(|_| "#").collect::(), + (0..80).map(|_| "#").collect::(), + (0..12).map(|_| "#").collect::(), + (0..12).map(|_| "#").collect::(), + (0..12).map(|_| "#").collect::(), + (0..12).map(|_| "#").collect::() + ); + } + + languages.get_statistics(&input, &cli.ignored_directories(), &config); + if config.for_each_fn.is_some() { + process::exit(0); + } + + if let Some(format) = cli.output { + print!("{}", format.print(&languages).unwrap()); + process::exit(0); + } + + let mut printer = Printer::new( + columns, + cli.files, + io::BufWriter::new(io::stdout()), + cli.number_format, + ); + + if languages.iter().any(|(_, lang)| lang.inaccurate) { + printer.print_inaccuracy_warning()?; + } + + printer.print_header()?; + + let mut is_sorted = false; + if let Some(sort_category) = cli.sort.or(config.sort) { + for (_, ref mut language) in &mut languages { + language.sort_by(sort_category); + } + + let mut languages: Vec<_> = languages.iter().collect(); + match sort_category { + Sort::Blanks => languages.sort_by(|a, b| b.1.blanks.cmp(&a.1.blanks)), + Sort::Comments => languages.sort_by(|a, b| b.1.comments.cmp(&a.1.comments)), + Sort::Code => languages.sort_by(|a, b| b.1.code.cmp(&a.1.code)), + Sort::Files => languages.sort_by(|a, b| b.1.reports.len().cmp(&a.1.reports.len())), + Sort::Lines => languages.sort_by(|a, b| b.1.lines().cmp(&a.1.lines())), + } + is_sorted = true; + if cli.sort_reverse { + printer.print_results(languages.into_iter().rev(), cli.compact, is_sorted)?; + } else { + printer.print_results(languages.into_iter(), cli.compact, is_sorted)?; + } + } else { + printer.print_results(languages.iter(), cli.compact, is_sorted)?; + } + + printer.print_total(&languages)?; + + Ok(()) +} diff --git a/userland/upstream-src/tokei/src/sort.rs b/userland/upstream-src/tokei/src/sort.rs new file mode 100644 index 0000000000..4673b01890 --- /dev/null +++ b/userland/upstream-src/tokei/src/sort.rs @@ -0,0 +1,61 @@ +use std::{borrow::Cow, str::FromStr}; + +use serde::de::{self, Deserialize, Deserializer}; + +/// Used for sorting languages. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Sort { + /// Sort by number blank lines. + Blanks, + /// Sort by number comments lines. + Comments, + /// Sort by number code lines. + Code, + /// Sort by number files lines. + Files, + /// Sort by number of lines. + Lines, +} + +impl FromStr for Sort { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(if s.eq_ignore_ascii_case("blanks") { + Sort::Blanks + } else if s.eq_ignore_ascii_case("comments") { + Sort::Comments + } else if s.eq_ignore_ascii_case("code") { + Sort::Code + } else if s.eq_ignore_ascii_case("files") { + Sort::Files + } else if s.eq_ignore_ascii_case("lines") { + Sort::Lines + } else { + return Err(format!("Unsupported sorting option: {}", s)); + }) + } +} + +impl<'de> Deserialize<'de> for Sort { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(de::Error::custom) + } +} + +impl<'a> From for Cow<'a, Sort> { + fn from(from: Sort) -> Self { + Cow::Owned(from) + } +} + +impl<'a> From<&'a Sort> for Cow<'a, Sort> { + fn from(from: &'a Sort) -> Self { + Cow::Borrowed(from) + } +} diff --git a/userland/upstream-src/tokei/src/stats.rs b/userland/upstream-src/tokei/src/stats.rs new file mode 100644 index 0000000000..5562e65e77 --- /dev/null +++ b/userland/upstream-src/tokei/src/stats.rs @@ -0,0 +1,143 @@ +use crate::consts::{ + BLANKS_COLUMN_WIDTH, CODE_COLUMN_WIDTH, COMMENTS_COLUMN_WIDTH, LINES_COLUMN_WIDTH, +}; +use crate::LanguageType; +use std::{collections::BTreeMap, fmt, ops, path::PathBuf}; + +/// A struct representing stats about a single blob of code. +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[non_exhaustive] +pub struct CodeStats { + /// The blank lines in the blob. + pub blanks: usize, + /// The lines of code in the blob. + pub code: usize, + /// The lines of comments in the blob. + pub comments: usize, + /// Language blobs that were contained inside this blob. + pub blobs: BTreeMap, +} + +impl CodeStats { + /// Creates a new blank `CodeStats`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Get the total lines in a blob of code. + #[must_use] + pub fn lines(&self) -> usize { + self.blanks + self.code + self.comments + } + + /// Creates a new `CodeStats` from an existing one with all of the child + /// blobs merged. + #[must_use] + pub fn summarise(&self) -> Self { + let mut summary = self.clone(); + + for (_, stats) in std::mem::take(&mut summary.blobs) { + let child_summary = stats.summarise(); + + summary.blanks += child_summary.blanks; + summary.comments += child_summary.comments; + summary.code += child_summary.code; + } + + summary + } +} + +impl ops::AddAssign for CodeStats { + fn add_assign(&mut self, rhs: Self) { + self.add_assign(&rhs); + } +} + +impl ops::AddAssign<&'_ CodeStats> for CodeStats { + fn add_assign(&mut self, rhs: &'_ CodeStats) { + self.blanks += rhs.blanks; + self.code += rhs.code; + self.comments += rhs.comments; + + for (language, stats) in &rhs.blobs { + *self.blobs.entry(*language).or_default() += stats; + } + } +} + +/// A struct representing the statistics of a file. +#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)] +#[non_exhaustive] +pub struct Report { + /// The code statistics found in the file. + pub stats: CodeStats, + /// File name. + pub name: PathBuf, +} + +impl Report { + /// Create a new `Report` from a [`PathBuf`]. + /// + /// [`PathBuf`]: //doc.rust-lang.org/std/path/struct.PathBuf.html + #[must_use] + pub fn new(name: PathBuf) -> Self { + Self { + name, + ..Self::default() + } + } +} + +impl ops::AddAssign for Report { + fn add_assign(&mut self, rhs: CodeStats) { + self.stats += rhs; + } +} + +#[doc(hidden)] +#[must_use] +pub fn find_char_boundary(s: &str, index: usize) -> usize { + for i in 0..4 { + if s.is_char_boundary(index + i) { + return index + i; + } + } + unreachable!(); +} + +macro_rules! display_stats { + ($f:expr, $this:expr, $name:expr, $max:expr) => { + write!( + $f, + " {: LINES_COLUMN_WIDTH$} {:>CODE_COLUMN_WIDTH$} {:>COMMENTS_COLUMN_WIDTH$} {:>BLANKS_COLUMN_WIDTH$}", + $name, + $this.stats.lines(), + $this.stats.code, + $this.stats.comments, + $this.stats.blanks, + max = $max + ) + }; +} + +impl fmt::Display for Report { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = self.name.to_string_lossy(); + let name_length = name.len(); + + // Added 2 to max length to cover wider Files column (see https://github.com/XAMPPRocky/tokei/issues/891). + let max_len = f.width().unwrap_or(27) + 2; + + if name_length <= max_len { + display_stats!(f, self, name, max_len) + } else { + let mut formatted = String::from("|"); + // Add 1 to the index to account for the '|' we add to the output string + let from = find_char_boundary(&name, name_length + 1 - max_len); + formatted.push_str(&name[from..]); + display_stats!(f, self, formatted, max_len) + } + } +} diff --git a/userland/upstream-src/tokei/src/utils/ext.rs b/userland/upstream-src/tokei/src/utils/ext.rs new file mode 100644 index 0000000000..bc526de7d2 --- /dev/null +++ b/userland/upstream-src/tokei/src/utils/ext.rs @@ -0,0 +1,158 @@ +//! Various extensions to Rust std types. + +pub(crate) trait AsciiExt { + fn is_whitespace(&self) -> bool; + fn is_line_ending_whitespace(&self) -> bool; +} + +impl AsciiExt for u8 { + fn is_whitespace(&self) -> bool { + *self == b' ' || (b'\x09'..=b'\x0d').contains(self) + } + + fn is_line_ending_whitespace(&self) -> bool { + *self == b'\n' + } +} + +pub(crate) trait SliceExt { + fn trim_first_and_last_line_of_whitespace(&self) -> &Self; + fn trim_start(&self) -> &Self; + fn trim(&self) -> &Self; + fn contains_slice(&self, needle: &Self) -> bool; +} + +impl SliceExt for [u8] { + fn trim_first_and_last_line_of_whitespace(&self) -> &Self { + let start = self + .iter() + .position(|c| c.is_line_ending_whitespace() || !c.is_whitespace()) + .map_or(0, |i| (i + 1).min(self.len().saturating_sub(1))); + + let end = self + .iter() + .rposition(|c| c.is_line_ending_whitespace() || !c.is_whitespace()) + .map_or_else( + || self.len().saturating_sub(1), + |i| { + // Remove the entire `\r\n` in the case that it was the line ending whitespace + if self[i.saturating_sub(1)] == b'\r' && self[i] == b'\n' { + i - 1 + } else { + i + } + }, + ); + + if self[start..].is_empty() { + return &[]; + } + + &self[start..=end] + } + + fn trim_start(&self) -> &Self { + let length = self.len(); + + if length == 0 { + return self; + } + + let start = match self.iter().position(|c| !c.is_whitespace()) { + Some(start) => start, + None => return &[], + }; + + &self[start..] + } + + fn trim(&self) -> &Self { + let length = self.len(); + + if length == 0 { + return self; + } + + let start = match self.iter().position(|c| !c.is_whitespace()) { + Some(start) => start, + None => return &[], + }; + + let end = match self.iter().rposition(|c| !c.is_whitespace()) { + Some(end) => end.max(start), + _ => length, + }; + + &self[start..=end] + } + + fn contains_slice(&self, needle: &Self) -> bool { + let self_length = self.len(); + let needle_length = needle.len(); + + if needle_length == 0 || needle_length > self_length { + return false; + } else if needle_length == self_length { + return self == needle; + } + + for window in self.windows(needle_length) { + if needle == window { + return true; + } + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use proptest::prelude::*; + + #[test] + fn is_whitespace() { + assert!(b' '.is_whitespace()); + assert!(b'\r'.is_whitespace()); + assert!(b'\n'.is_whitespace()); + } + + #[test] + fn trim() { + assert!([b' ', b' ', b' '].trim().is_empty()); + assert!([b' ', b'\r', b'\n'].trim().is_empty()); + assert!([b'\n'].trim().is_empty()); + assert!([].trim().is_empty()); + + assert_eq!([b'a', b'b'], [b'a', b'b'].trim()); + assert_eq!([b'h', b'i'], [b' ', b'h', b'i'].trim()); + assert_eq!([b'h', b'i'], [b'h', b'i', b' '].trim()); + assert_eq!([b'h', b'i'], [b' ', b'h', b'i', b' '].trim()); + } + + #[test] + fn contains() { + assert!([1, 2, 3, 4, 5].contains_slice(&[1, 2, 3, 4, 5])); + assert!([1, 2, 3, 4, 5].contains_slice(&[1, 2, 3])); + assert!([1, 2, 3, 4, 5].contains_slice(&[3, 4, 5])); + assert!([1, 2, 3, 4, 5].contains_slice(&[2, 3, 4])); + assert!(![1, 2, 3, 4, 5].contains_slice(&[])); + } + + #[test] + fn trim_first_and_last_line_of_whitespace_edge_cases() { + assert_eq!(b"", b"\ra ".trim_first_and_last_line_of_whitespace()); + assert_eq!(b"a", b"\r\na ".trim_first_and_last_line_of_whitespace()); + + assert_eq!(b" ", b" ".trim_first_and_last_line_of_whitespace()); + } + + proptest! { + #[test] + fn trim_first_and_last_line_of_whitespace_doesnt_panic(input: Vec) { + let _ = &input.trim_first_and_last_line_of_whitespace(); + } + } +} diff --git a/userland/upstream-src/tokei/src/utils/fs.rs b/userland/upstream-src/tokei/src/utils/fs.rs new file mode 100644 index 0000000000..966115f5f7 --- /dev/null +++ b/userland/upstream-src/tokei/src/utils/fs.rs @@ -0,0 +1,486 @@ +use std::{collections::BTreeMap, path::Path}; + +use ignore::{overrides::OverrideBuilder, DirEntry, WalkBuilder, WalkState::Continue}; + +use rayon::prelude::*; + +use crate::{ + config::Config, + language::{Language, LanguageType}, +}; + +const IGNORE_FILE: &str = ".tokeignore"; + +pub fn get_all_files>( + paths: &[A], + ignored_directories: &[&str], + languages: &mut BTreeMap, + config: &Config, +) { + let languages = parking_lot::Mutex::new(languages); + let (tx, rx) = crossbeam_channel::unbounded(); + + let mut paths = paths.iter(); + let mut walker = WalkBuilder::new(paths.next().unwrap()); + + for path in paths { + walker.add(path); + } + + if !ignored_directories.is_empty() { + let mut overrides = OverrideBuilder::new("."); + + for ignored in ignored_directories { + rs_error!(overrides.add(&format!("!{}", ignored))); + } + + walker.overrides(overrides.build().expect("Excludes provided were invalid")); + } + + let ignore = config.no_ignore.map(|b| !b).unwrap_or(true); + let ignore_dot = ignore && config.no_ignore_dot.map(|b| !b).unwrap_or(true); + let ignore_vcs = ignore && config.no_ignore_vcs.map(|b| !b).unwrap_or(true); + + // Custom ignore files always work even if the `ignore` option is false, + // so we only add if that option is not present. + if ignore_dot { + walker.add_custom_ignore_filename(IGNORE_FILE); + } + + walker + .git_exclude(ignore_vcs) + .git_global(ignore_vcs) + .git_ignore(ignore_vcs) + .hidden(config.hidden.map(|b| !b).unwrap_or(true)) + .ignore(ignore_dot) + .parents(ignore && config.no_ignore_parent.map(|b| !b).unwrap_or(true)); + + walker.build_parallel().run(move || { + let tx = tx.clone(); + Box::new(move |entry| { + let entry = match entry { + Ok(entry) => entry, + Err(error) => { + use ignore::Error; + if let Error::WithDepth { err: ref error, .. } = error { + if let Error::WithPath { + ref path, + err: ref error, + } = **error + { + error!("{} reading {}", error, path.display()); + return Continue; + } + } + error!("{}", error); + return Continue; + } + }; + + if entry.file_type().map_or(false, |ft| ft.is_file()) { + tx.send(entry).unwrap(); + } + + Continue + }) + }); + + let rx_iter = rx + .into_iter() + .par_bridge() + .filter_map(|e| LanguageType::from_path(e.path(), config).map(|l| (e, l))); + + let process = |(entry, language): (DirEntry, LanguageType)| { + let result = language.parse(entry.into_path(), config); + let mut lock = languages.lock(); + let entry = lock.entry(language).or_insert_with(Language::new); + match result { + Ok(stats) => { + let func = config.for_each_fn; + if let Some(f) = func { + f(language, stats.clone()) + }; + entry.add_report(stats) + } + Err((error, path)) => { + entry.mark_inaccurate(); + error!("Error reading {}:\n{}", path.display(), error); + } + } + }; + + if let Some(types) = config.types.as_deref() { + rx_iter.filter(|(_, l)| types.contains(l)).for_each(process) + } else { + rx_iter.for_each(process) + } +} + +pub(crate) fn get_extension(path: &Path) -> Option { + path.extension().map(|e| e.to_string_lossy().to_lowercase()) +} + +pub(crate) fn get_filename(path: &Path) -> Option { + path.file_name().map(|e| e.to_string_lossy().to_lowercase()) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::TempDir; + + use super::IGNORE_FILE; + use crate::{ + config::Config, + language::{languages::Languages, LanguageType}, + }; + + const FILE_CONTENTS: &[u8] = b"fn main() {}"; + const FILE_NAME: &str = "main.rs"; + const IGNORE_PATTERN: &str = "*.rs"; + const LANGUAGE: &LanguageType = &LanguageType::Rust; + + #[test] + fn ignore_directory_with_extension() { + let mut languages = Languages::new(); + let tmp_dir = TempDir::new().expect("Couldn't create temp dir"); + let path_name = tmp_dir.path().join("directory.rs"); + + fs::create_dir(path_name).expect("Couldn't create directory.rs within temp"); + + super::get_all_files( + &[tmp_dir.into_path().to_str().unwrap()], + &[], + &mut languages, + &Config::default(), + ); + + assert!(languages.get(LANGUAGE).is_none()); + } + + #[test] + fn hidden() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::write(dir.path().join(".hidden.rs"), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.hidden = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_implies_dot() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::write(dir.path().join(".ignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_implies_vcs_gitignore() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + git2::Repository::init(dir.path()).expect("Couldn't create git repo."); + + fs::write(dir.path().join(".gitignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_parent() { + let parent_dir = TempDir::new().expect("Couldn't create temp dir."); + let child_dir = parent_dir.path().join("child/"); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::create_dir_all(&child_dir) + .unwrap_or_else(|_| panic!("Couldn't create {:?}", child_dir)); + fs::write(parent_dir.path().join(".ignore"), IGNORE_PATTERN) + .expect("Couldn't create .gitignore."); + fs::write(child_dir.join(FILE_NAME), FILE_CONTENTS).expect("Couldn't create child.rs"); + + super::get_all_files( + &[child_dir.as_path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore_parent = Some(true); + + super::get_all_files( + &[child_dir.as_path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_dot() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::write(dir.path().join(".ignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore_dot = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_dot_still_vcs_gitignore() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + git2::Repository::init(dir.path()).expect("Couldn't create git repo."); + + fs::write(dir.path().join(".gitignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + config.no_ignore_dot = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + } + + #[test] + fn no_ignore_dot_includes_custom_ignore() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::write(dir.path().join(IGNORE_FILE), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore_dot = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_vcs_gitignore() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + git2::Repository::init(dir.path()).expect("Couldn't create git repo."); + + fs::write(dir.path().join(".gitignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore_vcs = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn no_ignore_vcs_gitignore_still_dot() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + fs::write(dir.path().join(".ignore"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + config.no_ignore_vcs = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + } + + #[test] + fn no_ignore_vcs_gitexclude() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let mut config = Config::default(); + let mut languages = Languages::new(); + + git2::Repository::init(dir.path()).expect("Couldn't create git repo."); + + fs::write(dir.path().join(".git/info/exclude"), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + config.no_ignore_vcs = Some(true); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } + + #[test] + fn custom_ignore() { + let dir = TempDir::new().expect("Couldn't create temp dir."); + let config = Config::default(); + let mut languages = Languages::new(); + + git2::Repository::init(dir.path()).expect("Couldn't create git repo."); + + fs::write(dir.path().join(IGNORE_FILE), IGNORE_PATTERN).unwrap(); + fs::write(dir.path().join(FILE_NAME), FILE_CONTENTS).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_none()); + + fs::remove_file(dir.path().join(IGNORE_FILE)).unwrap(); + + super::get_all_files( + &[dir.path().to_str().unwrap()], + &[], + &mut languages, + &config, + ); + + assert!(languages.get(LANGUAGE).is_some()); + } +} diff --git a/userland/upstream-src/tokei/src/utils/macros.rs b/userland/upstream-src/tokei/src/utils/macros.rs new file mode 100644 index 0000000000..cdf93edea2 --- /dev/null +++ b/userland/upstream-src/tokei/src/utils/macros.rs @@ -0,0 +1,102 @@ +#![allow(unused_macros)] + +macro_rules! opt_warn { + ($option:expr, $message:expr) => { + match $option { + Some(result) => result, + None => { + warn!($message); + continue; + } + } + }; +} + +macro_rules! rs_warn { + ($result:expr, $message: expr) => { + match $result { + Ok(result) => result, + Err(error) => { + warn!("{}", error); + continue; + } + } + }; +} + +macro_rules! opt_error { + ($option:expr, $message:expr) => { + match $option { + Some(result) => result, + None => { + error!($message); + continue; + } + } + }; +} + +macro_rules! rs_error { + ($result:expr) => { + match $result { + Ok(result) => result, + Err(error) => { + error!("{}", error); + continue; + } + } + }; +} + +macro_rules! opt_ret_warn { + ($option:expr, $message:expr) => { + match $option { + Some(result) => result, + None => { + warn!($message); + return None; + } + } + }; +} + +macro_rules! rs_ret_warn { + ($result:expr, $message: expr) => { + match $result { + Ok(result) => result, + Err(error) => { + warn!("{}", error); + return None; + } + } + }; +} + +macro_rules! opt_ret_error { + ($option:expr, $message:expr) => { + match $option { + Some(result) => result, + None => { + error!($message); + return None; + } + } + }; +} + +macro_rules! rs_ret_error { + ($result:expr) => { + match $result { + Ok(result) => result, + Err(error) => { + error!("{}", error); + return None; + } + } + }; +} + +macro_rules! debug { + ($fmt:expr) => (if cfg!(debug_assertions) {println!($fmt)}); + ($fmt:expr, $($arg:tt)*) => (if cfg!(debug_assertions) {println!($fmt, $($arg)*)}); +} diff --git a/userland/upstream-src/tokei/src/utils/mod.rs b/userland/upstream-src/tokei/src/utils/mod.rs new file mode 100644 index 0000000000..a3ce44911d --- /dev/null +++ b/userland/upstream-src/tokei/src/utils/mod.rs @@ -0,0 +1,4 @@ +#[macro_use] +mod macros; +pub(crate) mod ext; +pub mod fs; From 13cbd62530f12324eb6d332f3f8029edfc12dbdc Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 16:51:47 +0200 Subject: [PATCH 05/10] desktop: real line-art icons for the installed tools The eight tools shipped a hued letter-tile from the generator; give each a 48x48 glyph mask drawn in the same cyan line-art language as the desktop apps (a regex star for grex, a bar chart for tokei, a table for csview, braces for jsonxf, and so on). A tool with no shipped artwork still falls back to the generated tile. Also wire the tool_capsules module the generated registry needs: the macro and the spec were present but never declared, so the embedded-tool list did not compile. --- .../assets/app_icons/tools/choose.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/csview.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/dotenv_linter.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/grex.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/huniq.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/jsonxf.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/pastel.rgba | Bin 0 -> 9216 bytes .../assets/app_icons/tools/tokei.rgba | Bin 0 -> 9216 bytes .../capsule_desktop_shell/src/render/icons.rs | 6 +++ .../src/render/launchpad/mod.rs | 1 + .../src/render/launchpad/tile.rs | 7 +-- .../src/render/launchpad/tool_icons.rs | 42 ++++++++++++++++++ .../capsule_desktop_shell/src/render/mod.rs | 2 +- 13 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/choose.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/csview.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/dotenv_linter.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/grex.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/huniq.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/jsonxf.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/pastel.rgba create mode 100644 userland/capsule_desktop_shell/assets/app_icons/tools/tokei.rgba create mode 100644 userland/capsule_desktop_shell/src/render/launchpad/tool_icons.rs diff --git a/userland/capsule_desktop_shell/assets/app_icons/tools/choose.rgba b/userland/capsule_desktop_shell/assets/app_icons/tools/choose.rgba new file mode 100644 index 0000000000000000000000000000000000000000..d28f0c48dfb10cbb367a4795a693893aff777ae9 GIT binary patch literal 9216 zcmeI1L2AQ55Crp#{3EZ)|KH%VqB#@@Q5rfhwq;ns($Li&wRe{soNe2_wzbwCtUIvo zz-Qip@BQ!BPq-?3r*p18kA0<2W$*MIYTy6+)MxH;2FL!td#|f|RE*~x{p-x_+>iZ_ zz0Uco9OrP(<4osECbXMxEcNJS zdDw(@^Npn*-7F8A&~Co5)T5i_VH4WTH+7Jq1}9AsYf@+RZnXdUUfqY(l&F#!`=NmWNGfH{V$5(arL(3GIGg zvUgkFa&)siY)b1Lwa)XddY$uEIp&@7_|Mj3uRH&Kd$kY8{`>JnPkrWU-mCpO_fP)+ dMeSF8huYI*Rrb|;yxzC=eBFU{2cB{V{s8+yXdM6m literal 0 HcmV?d00001 diff --git a/userland/capsule_desktop_shell/assets/app_icons/tools/csview.rgba b/userland/capsule_desktop_shell/assets/app_icons/tools/csview.rgba new file mode 100644 index 0000000000000000000000000000000000000000..5b1cd7b4a6038fbc1dd550894ed95e8acdd3b76b GIT binary patch literal 9216 zcmeHLQ3}E^6dRs&U!G%k@&G&9Y@83v!>x%EIx&Susmp89G&*oumSJ((182Y)a0Z-# zR|YQSy)c%VIu|rRi+VTcJIjC;RsVDr@kIx;9ML~N_rN||RUSRY*7NwB#mxaMGkOQH z&=Z5{4{VEv#i{(6SM6tYu{f3A`d4SO4|#3>x$knFzgscw+NkjLTR3pWfR=oZt#z$G z_USeMyZ&F#4|9K!tyV5sT%BKrKl7^ntS%O(@>~DvZ1y3q?LYS}Q1i$7%HmZ1e9vk> ztBb{{{CoO?v)7EXWPPnQdd@PA>Sy}ntSw(yT5L^^N49rXf!N}Cy)X>;`@dx|>lSvE>#MD19HkA7YLnA?qG%^rGgtZE6 zZ0@#q_A&dIWg3d@okvwo@62#l6h&4fq#C3>;M)UhI43#adImM9<@3|DpS(i_wVL|# zjdw$thYq!Tewy}c|BF8jwuJ&YCCHft200}X|7v|xeO7Ct_uyRXvx!5o+Y+2BH5#Kn z)PDim2ItBelK=m2Dc@8-t@^~gfAspg=2m^W^2+?V^2fY?()}~;{mHWt^Zt>u9pV|& z{UHBEb9_JfuNag6OZ9_;Ysi3D{GTfCOk?tY2*01o|2^_`Acp$yz~?g{@q}a>p!cc! z-+;#lK;i?*0U+MT^j~(DXkW~)#W012O z_@?bu{AFu+SKi-{?^k%ImnwetcZH9?Yz>etzJcQ!`ab@?{gc0J4d*qybM6%U%NlOr zhv)XHruQlNr4Gh&D(C&o4B z+NCtT2t&p-V&>Git9GsWYn`8(-`_@a%-$E;S__A~f7br{Rr~jY)4$Q2@a?q_VtZ1n zb!Ygo7;@o&3xhirH{}}hQ}g$8YA;$E+8cgq-l4?Jq5oWcy6b`epI(&Kec3!i;PfWkX0>xJ)FM8Hdw!PBaL>o)jE~`-pXD>$^RYSOW4PyM`3(1b zY|i)??)h0h!#y9HTfQP61k9~tO|3{^u_#848gO49Yibeh^V!_;DR0&o>9h4Boay!W z=fIL}-0 zw}y6L$eHczt)3gvXM0DtgEe3_ch>k?biSzY8e?r%y^*e+=-pRG9ObOFbuOz1Ru8Nm I_@f^924?(e)c^nh literal 0 HcmV?d00001 diff --git a/userland/capsule_desktop_shell/assets/app_icons/tools/jsonxf.rgba b/userland/capsule_desktop_shell/assets/app_icons/tools/jsonxf.rgba new file mode 100644 index 0000000000000000000000000000000000000000..963ae4f2f7556fb2073905318fea21946453b50c GIT binary patch literal 9216 zcmeI0F>b>!3`L7>*)w&_+Jh9iMY{DW*>jDcy+%&Z)n~}q#pMG`fCyliili7sNI;Nc zlmCBFlpJUr$Coi%<{)bzYanaj(KYZYXYXNZjJ^E##(D8QejCyIBTbEYc;fKER}BoX zys<6(-h}U>0;V2KUTl1=fd!^FzQ*recm_)@VY{ommXX_FjK#OFJ}JN&DEpwzqLd%p4%g(&n+w* zrSV?>vmqK9@_?lVhgVOD-=`8x{k|a&Sk~Yw(YEBf6TGV-I5ozHA51m4;9Sz>egCb% z(*H`^G{ja7V1lg%r@pK0qw9}eKdfKm{j2pC`TS%K(Z65J!J4j1ZQlcPPE!xA9+;`$ z|NY7QubZp2@*na3WH8B!cz+fB{*ZH(yVi?Y2Zje}b7$`Db9}9V1?H_^&)@uCk9vQu gdEw5PT+$fp^QHdYxFy_t?_Yz=Rpu&d;K^&?AEo8Q?f?J) literal 0 HcmV?d00001 diff --git a/userland/capsule_desktop_shell/assets/app_icons/tools/pastel.rgba b/userland/capsule_desktop_shell/assets/app_icons/tools/pastel.rgba new file mode 100644 index 0000000000000000000000000000000000000000..658bfb88b151447deb85a9ffa3313904f1602421 GIT binary patch literal 9216 zcmeI0J4ysW5Qa6?Km!9Gw=oqnLGS{b2%f-5#MDSbFW@Ci#8A-0L=(XS2%;FQ|0h#W z6w_KfD>s5ww`pyt5rIk2YtWJ|IyIc$VFeT@CZ zHC}Dvyi@rT&|zNK{PtXy6~n3IUD6fUU<=1@%wto2pCu7*>bR0zN*)@aP9I|taKJU> z97gi@83MUhdsng`*_OOY4Akjk3>|bm;DU3AIpD|RMU5Sa`Cd`o$8NgxF=l$$VC&KM z9Qd96y5Fe1F0q&>_arGW=5?`!!vJ?*xx_y6`HtU3iI1mi{OH5s=YWqs_B3aOf!{r@ zyUimve@veeE}u*I^5e7CIjj8{N$yVKOzq!EHgFp7h0jU)UB9j z#-B_0%(2PQxf7Rx^DoC{8XdR|_?(6#+VEyEaQ|?IN1ak_xcwbh;=RPX%fKC4D%KPa zJ`DIy>E-tGb~K!;l501u9zG2Co|eb#{N6n(z-PRv5+8;Vd-%V{-zvnFdeq$c-{mgG&>imK0#TKIRWouWeTo!N6_e)oQLw&yxSI+lim-snv{I^%1 z-;Wm8NIpII-FGB5o?Bw$a|TYhbM0r*!w+la;r~BLasP$?Pb@Za$?pf73-=FqDDME? zVhDBm{N1GHH{j^&KL?97RkEi!mpJdb0vl}MNbwKV&;7uCj` z@_;-b4|osE_H9J?+FA04ap!^jEZpFnGq}5=c9#EecicHeP5j4bApMWgx#j1p>5sDs z(bD7S`WF31{7$g^(7YTYJO`Ga?HkoU;@|hq!=t}^%IeDYMf!(1FIX+24pJ(XFqq*4idYJuOcE9L8 z&(M=cbFu67F#Eaee$joNfz5Ar-YpiZi>Kw~K+X3ckL&{jSf-7Ed7})lT#dixpVf4D pxrc%rwOT~=Ew=t1|4cTo6|>z#wC?A6bYEUq*Z Option<&'static [u8]> { + match label { + b"grex" => Some(GREX), + b"dotenv-linter" => Some(DOTENV_LINTER), + b"pastel" => Some(PASTEL), + b"jsonxf" => Some(JSONXF), + b"choose" => Some(CHOOSE), + b"tokei" => Some(TOKEI), + b"huniq" => Some(HUNIQ), + b"csview" => Some(CSVIEW), + _ => None, + } +} + +pub(super) fn draw(ctx: &Context, x: u32, y: u32, size: u32, label: &[u8]) { + match glyph_for(label) { + Some(glyph) => draw_tool_icon(ctx, x, y, size, glyph), + None => gen_icon::draw(ctx, x, y, size, label), + } +} diff --git a/userland/capsule_desktop_shell/src/render/mod.rs b/userland/capsule_desktop_shell/src/render/mod.rs index 9b84ace7dc..b5f2d68587 100644 --- a/userland/capsule_desktop_shell/src/render/mod.rs +++ b/userland/capsule_desktop_shell/src/render/mod.rs @@ -28,6 +28,6 @@ pub mod topbar; pub use bottom_taskbar::paint_bottom_taskbar; pub use chrome::paint_chrome; -pub use icons::draw_app_icon; +pub use icons::{draw_app_icon, draw_tool_icon}; pub use layout::{menubar_rect, spotlight_rect}; pub use toasts::sync_toast_layer; From 1a10ce9fd12c238f5df6196cb389c824eb6d53f6 Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 17:11:16 +0200 Subject: [PATCH 06/10] tools: run the baked command-line tools from the terminal A command-line tool has nothing to do until it is invoked, so the eight tools no longer spawn at boot. A new MkToolRun syscall runs one baked, attested tool by name, parented to the caller so the caller feeds its stdin and drains its stdout through the existing MkProcInput and MkProcOutput path. Only the baked set can be named, and the gate is IPC, so an ordinary capsule cannot point it at an arbitrary binary. The shell recognizes each tool as a bare command (tokei, grex foo, ...) and streams its output through the same async drain job the store installs use. A Launchpad tool tile opens the terminal, where the tool runs by name. --- src/syscall/contract/cap_table/mk.rs | 5 ++ .../dispatch/router/microkernel_ops.rs | 1 + src/syscall/microkernel/dispatch/process.rs | 2 + src/syscall/microkernel/mod.rs | 1 + src/syscall/microkernel/numbers.rs | 4 ++ src/syscall/microkernel/tool_run.rs | 58 +++++++++++++++++ src/syscall/numbers/defs.rs | 1 + src/userspace/init/entry.rs | 10 --- src/userspace/tool_capsules/mod.rs | 5 +- src/userspace/tool_capsules/registry.rs | 20 +++--- src/userspace/tool_capsules/spec.rs | 20 +++++- .../src/server/handlers/launchpad.rs | 10 ++- .../src/command/builtin/mod.rs | 1 + .../src/command/builtin/tool.rs | 63 +++++++++++++++++++ .../capsule_terminal/src/jobs/classify.rs | 14 ++++- userland/libc/src/lib.rs | 2 + userland/libc/src/syscall/mod.rs | 1 + userland/libc/src/syscall/numbers/core.rs | 1 + userland/libc/src/tool_run.rs | 26 ++++++++ 19 files changed, 217 insertions(+), 28 deletions(-) create mode 100644 src/syscall/microkernel/tool_run.rs create mode 100644 userland/capsule_terminal/src/command/builtin/tool.rs create mode 100644 userland/libc/src/tool_run.rs diff --git a/src/syscall/contract/cap_table/mk.rs b/src/syscall/contract/cap_table/mk.rs index 6a5451e654..2d2ebea4e1 100644 --- a/src/syscall/contract/cap_table/mk.rs +++ b/src/syscall/contract/cap_table/mk.rs @@ -103,6 +103,11 @@ pub(super) fn check(caps: &CapabilityToken, number: SyscallNumber) -> Option caps.can_spawn_window(), + // Running a baked command-line tool needs only IPC: the tool is spawned + // parented to the caller so the caller can drive its stdio, and only the + // baked, attested set can be named. + SyscallNumber::MkToolRun => caps.can_ipc(), + _ => return None, }) } diff --git a/src/syscall/dispatch/router/microkernel_ops.rs b/src/syscall/dispatch/router/microkernel_ops.rs index d9abf226d9..81e68ce3c7 100644 --- a/src/syscall/dispatch/router/microkernel_ops.rs +++ b/src/syscall/dispatch/router/microkernel_ops.rs @@ -74,6 +74,7 @@ pub(super) fn matches(nr: SyscallNumber) -> bool { | MkPioRelease | MkDebug | MkSpawnInstance + | MkToolRun ) } diff --git a/src/syscall/microkernel/dispatch/process.rs b/src/syscall/microkernel/dispatch/process.rs index a14c2aebae..cc7d012335 100644 --- a/src/syscall/microkernel/dispatch/process.rs +++ b/src/syscall/microkernel/dispatch/process.rs @@ -30,6 +30,7 @@ use crate::syscall::microkernel::process::{ use crate::syscall::microkernel::futex::{sys_futex_wait, sys_futex_wake}; use crate::syscall::microkernel::procstat::sys_proc_stat; use crate::syscall::microkernel::spawn_instance::sys_spawn_instance; +use crate::syscall::microkernel::tool_run::sys_tool_run; use crate::syscall::microkernel::time::{ sys_time_adjust, sys_time_millis, sys_time_monotonic, sys_time_rtc, }; @@ -63,6 +64,7 @@ pub(super) fn handle(nr: u64, a: Args) -> Option { SYS_STDIN_READ => sys_stdin_read(a.a0, a.a1 as usize), SYS_ATTEST_STATUS => sys_attest_status(a.a0), SYS_SPAWN_INSTANCE => sys_spawn_instance(a.a0, a.a1), + SYS_TOOL_RUN => sys_tool_run(a.a0, a.a1, a.a2, a.a3), _ => return None, }) } diff --git a/src/syscall/microkernel/mod.rs b/src/syscall/microkernel/mod.rs index f7bc009d39..f8fe66b14b 100644 --- a/src/syscall/microkernel/mod.rs +++ b/src/syscall/microkernel/mod.rs @@ -42,6 +42,7 @@ pub mod process; pub mod procstat; pub mod spawn_instance; pub mod time; +pub mod tool_run; pub mod wait; pub use attest::sys_attest_status; diff --git a/src/syscall/microkernel/numbers.rs b/src/syscall/microkernel/numbers.rs index 630373b231..1b942249ba 100644 --- a/src/syscall/microkernel/numbers.rs +++ b/src/syscall/microkernel/numbers.rs @@ -77,3 +77,7 @@ pub const SYS_PCI_CONFIG_WRITE: u64 = tag4(b"MPCW"); // Spawn another window instance of an embedded, attested app capsule // (terminal or browser). Gated on the SpawnWindow capability. pub const SYS_SPAWN_INSTANCE: u64 = tag4(b"MSPI"); + +// Run a baked, attested command-line tool by name, parented to the caller so +// it can drive the tool's stdin and stdout. Gated on the IPC capability. +pub const SYS_TOOL_RUN: u64 = tag4(b"MTRN"); diff --git a/src/syscall/microkernel/tool_run.rs b/src/syscall/microkernel/tool_run.rs new file mode 100644 index 0000000000..0656eb12fd --- /dev/null +++ b/src/syscall/microkernel/tool_run.rs @@ -0,0 +1,58 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use super::errnos::{ERRNO_FAULT, ERRNO_INVAL, ERRNO_NOENT}; +use crate::usercopy::{read_user_bytes, validate_user_read}; + +const MAX_NAME: usize = 48; +const MAX_ARGV: usize = 4096; + +/// `MkToolRun(name_ptr, name_len, argv_ptr, argv_len)`. Run one of the baked, +/// attested command-line tools by its service name (for example `tool.tokei`), +/// parented to the caller so the caller feeds its stdin and drains its stdout +/// through the existing `MkProcInput`/`MkProcOutput` path. `argv` is the tool's +/// NUL-separated argument blob (`name\0arg1\0...`), empty for none. Returns the +/// new tool's pid, or an errno. Only the baked set can be named, so a caller can +/// never point this at an arbitrary binary. +pub fn sys_tool_run(name_ptr: u64, name_len: u64, argv_ptr: u64, argv_len: u64) -> i64 { + let nlen = name_len as usize; + if nlen == 0 || nlen > MAX_NAME || validate_user_read(name_ptr, nlen).is_err() { + return ERRNO_INVAL; + } + let name = match read_user_bytes(name_ptr, nlen) { + Ok(b) => b, + Err(_) => return ERRNO_FAULT, + }; + let alen = argv_len as usize; + if alen > MAX_ARGV { + return ERRNO_INVAL; + } + let argv = if alen == 0 { + alloc::vec::Vec::new() + } else { + if validate_user_read(argv_ptr, alen).is_err() { + return ERRNO_INVAL; + } + match read_user_bytes(argv_ptr, alen) { + Ok(b) => b, + Err(_) => return ERRNO_FAULT, + } + }; + match crate::userspace::tool_capsules::run_named(&name, &argv) { + Some(pid) => pid as i64, + None => ERRNO_NOENT, + } +} diff --git a/src/syscall/numbers/defs.rs b/src/syscall/numbers/defs.rs index 089072ebd9..74ccf38300 100644 --- a/src/syscall/numbers/defs.rs +++ b/src/syscall/numbers/defs.rs @@ -74,6 +74,7 @@ pub enum SyscallNumber { MkProcInput = tag4(b"MPIN"), MkStdinRead = tag4(b"MSRD"), MkAttestStatus = tag4(b"MAST"), + MkToolRun = tag4(b"MTRN"), MkCapGrant = tag4(b"MCGT"), MkCapRevoke = tag4(b"MCRV"), MkCapCheck = tag4(b"MCCK"), diff --git a/src/userspace/init/entry.rs b/src/userspace/init/entry.rs index 4f6c6feaff..e1dc87d52f 100644 --- a/src/userspace/init/entry.rs +++ b/src/userspace/init/entry.rs @@ -32,10 +32,6 @@ pub fn run_init() -> ! { spawn_plan::spawn_desktop(); spawn_plan::spawn_market(); spawn_plan::spawn_apps(); - // Installed command-line tools have nothing to do until launched, so they - // spawn after the display and desktop are up; ahead of the GPU they starved - // the display bring-up. - run_tools(); run_tokio_smoke(); boot_log::ok("INIT", "Capsules spawned"); lower_init_priority(); @@ -85,12 +81,6 @@ fn run_sd() { #[cfg(not(feature = "nonos-capsule-sd"))] fn run_sd() {} -// Spawn every embedded tool capsule from the generated registry. Adding a tool -// is a registry line, not a new function here. -fn run_tools() { - crate::userspace::tool_capsules::spawn_all(); -} - #[cfg(feature = "nonos-capsule-tokio-smoke")] fn run_tokio_smoke() { match crate::userspace::capsule_tokio_smoke::spawn_tokio_smoke_capsule() { diff --git a/src/userspace/tool_capsules/mod.rs b/src/userspace/tool_capsules/mod.rs index e9bc5a2d82..7771d35d84 100644 --- a/src/userspace/tool_capsules/mod.rs +++ b/src/userspace/tool_capsules/mod.rs @@ -5,11 +5,12 @@ //! Tool capsules are signed, STARK-attested crates.io utilities baked into the //! kernel image from `userland/apps.list`. Each carries its ELF, NONOS-ID cert, //! manifest, and STARK trailer; the spawner verifies all four under the baked -//! trust anchor before it runs. `spawn_all` brings them up after the desktop. +//! trust anchor before it runs. Tools run on demand through `run_named`, not at +//! boot: a command-line tool has nothing to do until it is invoked. #[macro_use] mod embed_macro; mod registry; mod spec; -pub use registry::spawn_all; +pub use registry::run_named; diff --git a/src/userspace/tool_capsules/registry.rs b/src/userspace/tool_capsules/registry.rs index 22edcf6ec2..a19d5af55e 100644 --- a/src/userspace/tool_capsules/registry.rs +++ b/src/userspace/tool_capsules/registry.rs @@ -38,13 +38,19 @@ fn embedded_tools() -> Vec { ] } -/// Spawn every embedded tool capsule, verified under the baked trust anchor. One -/// tool failing to spawn is logged and skipped so it never blocks the others. -pub fn spawn_all() { - for tool in embedded_tools() { - match tool.spawn() { - Ok(()) => boot_log::ok("TOOL", tool.name), - Err(_) => boot_log::error("tool capsule spawn failed"), +/// Run the embedded tool whose service name matches `name` (for example +/// `tool.tokei`), verified under the baked trust anchor and parented to the +/// caller so it can drive the tool's stdin and stdout. `argv` is the NUL +/// separated argument blob. Returns the tool's pid, or `None` when no tool +/// matches or the spawn is rejected. A command-line tool has nothing to do +/// until invoked, so tools are launched here on demand, not at boot. +pub fn run_named(name: &[u8], argv: &[u8]) -> Option { + let tool = embedded_tools().into_iter().find(|t| t.name.as_bytes() == name)?; + match tool.spawn_with_args(argv) { + Ok(pid) => Some(pid), + Err(_) => { + boot_log::error("tool capsule spawn failed"); + None } } } diff --git a/src/userspace/tool_capsules/spec.rs b/src/userspace/tool_capsules/spec.rs index 9edc7407db..96c7266a21 100644 --- a/src/userspace/tool_capsules/spec.rs +++ b/src/userspace/tool_capsules/spec.rs @@ -6,6 +6,8 @@ //! attested artifacts are data; the capability set and trust anchor are the same //! for every tool, so a tool is a value, not a hand-written module. +extern crate alloc; + use crate::capabilities::Capability; use crate::kernel_core::process_spawn::capsule_spawn::{self, CapsuleSpecVerified, SpawnError}; use crate::security::nonos_id_cert::IdCertVerifyError; @@ -27,7 +29,11 @@ pub struct ToolCapsule { impl ToolCapsule { /// Verify the artifacts under the baked trust anchor and spawn the tool with /// the sandboxed capability set (execute, IPC, memory) every tool shares. - pub fn spawn(&self) -> Result<(), SpawnError> { + /// The process is parented to the caller in the syscall context, so that + /// caller feeds its stdin and drains its stdout, and `argv` (a NUL-separated + /// `name\0arg1\0...` blob) becomes the tool's argument vector. Returns the + /// tool's pid. + pub fn spawn_with_args(&self, argv: &[u8]) -> Result { let trust_anchor = decode_trust_anchor(BAKED_TRUST_ANCHOR_POLICY) .map_err(|_| SpawnError::NonosIdCertRejected(IdCertVerifyError::TrustAnchorPolicy))?; let spec = CapsuleSpecVerified { @@ -45,7 +51,15 @@ impl ToolCapsule { | Capability::Memory.bit(), debug_tag: b"", }; - capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; - Ok(()) + let pid = capsule_spawn::spawn_verified(&spec, &trust_anchor, None)?; + if !argv.is_empty() { + let vec: alloc::vec::Vec = argv + .split(|&b| b == 0) + .filter(|s| !s.is_empty()) + .map(|s| alloc::string::String::from_utf8_lossy(s).into_owned()) + .collect(); + crate::process::with_process(pid, |pcb| *pcb.argv.lock() = vec); + } + Ok(pid) } } diff --git a/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs b/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs index 340e2a86b5..b147195e97 100644 --- a/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs +++ b/userland/capsule_desktop_shell/src/server/handlers/launchpad.rs @@ -8,7 +8,7 @@ use crate::render::launchpad::{hit, target, Target}; use crate::server::handlers::launcher_request; use crate::server::repaint::repaint; -use crate::state::{Context, LAUNCHER_APPS, TOOL_APPS}; +use crate::state::{Context, LAUNCHER_APPS}; pub fn open(ctx: &mut Context) { ctx.launchpad = true; @@ -21,8 +21,12 @@ pub fn click(ctx: &mut Context, px: u32, py: u32) { Target::App(a) => { let _ = launcher_request::request(&LAUNCHER_APPS[a]); } - Target::Tool(t) => { - let _ = launcher_request::request_service(TOOL_APPS[t].service); + Target::Tool(_) => { + // A tool is a command-line program: it runs in the terminal, + // where the kernel spawns it parented to the shell so its output + // streams into the scrollback. Opening the terminal is the + // launch; the user runs the tool there by name. + let _ = launcher_request::request_service(b"app.terminal"); } } } diff --git a/userland/capsule_terminal/src/command/builtin/mod.rs b/userland/capsule_terminal/src/command/builtin/mod.rs index e342d60c33..c60a53cbc2 100644 --- a/userland/capsule_terminal/src/command/builtin/mod.rs +++ b/userland/capsule_terminal/src/command/builtin/mod.rs @@ -30,5 +30,6 @@ pub mod nox; pub mod ping; pub mod service; pub mod theme; +pub mod tool; pub mod version; pub mod whoami; diff --git a/userland/capsule_terminal/src/command/builtin/tool.rs b/userland/capsule_terminal/src/command/builtin/tool.rs new file mode 100644 index 0000000000..cce351adbf --- /dev/null +++ b/userland/capsule_terminal/src/command/builtin/tool.rs @@ -0,0 +1,63 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Run one of the baked, attested command-line tools (grex, tokei, choose, ...) +//! from the shell. The kernel spawns the tool parented to this terminal, so the +//! same async drain job that streams a store install's output streams the +//! tool's stdout here. Adding a tool is one line in `TOOLS`. + +use alloc::vec::Vec; +use nonos_libc::mk_tool_run; + +use crate::command::builtin::nox::install::InstallJob; +use crate::term::state::State; + +/// The installed command-line tools, by the bare name typed at the prompt. Each +/// maps to its baked `tool.` service. Kept in step with userland/apps.list. +pub const TOOLS: &[&[u8]] = &[ + b"grex", + b"dotenv-linter", + b"pastel", + b"jsonxf", + b"choose", + b"tokei", + b"huniq", + b"csview", +]; + +pub fn is_tool(name: &[u8]) -> bool { + TOOLS.contains(&name) +} + +/// Spawn the baked tool named by `args[0]` with the rest as its argv, and return +/// a drain job that streams its stdout to the terminal. `None` on a spawn error, +/// with the reason already pushed to the scrollback. +pub fn prepare(state: &mut State, args: &[&[u8]]) -> Option { + let name = args[0]; + let mut service = Vec::with_capacity(5 + name.len()); + service.extend_from_slice(b"tool."); + service.extend_from_slice(name); + + let argv = argv_blob(args); + let rc = mk_tool_run(&service, &argv); + if rc < 0 { + state.scrollback.push_error(b"tool: launch failed"); + state.last_status = 1; + return None; + } + Some(InstallJob::new(rc as u32)) +} + +// argv as the tool sees it: argv[0] is the command name, then each argument, +// NUL-separated, which is what the kernel splits into the process argv. +fn argv_blob(args: &[&[u8]]) -> Vec { + let mut blob = Vec::new(); + for (i, a) in args.iter().enumerate() { + if i > 0 { + blob.push(0); + } + blob.extend_from_slice(a); + } + blob +} diff --git a/userland/capsule_terminal/src/jobs/classify.rs b/userland/capsule_terminal/src/jobs/classify.rs index 130b53bd2b..89f9b82d6c 100644 --- a/userland/capsule_terminal/src/jobs/classify.rs +++ b/userland/capsule_terminal/src/jobs/classify.rs @@ -18,6 +18,7 @@ use alloc::vec::Vec; use crate::command::builtin::nox::install; use crate::command::builtin::ping; +use crate::command::builtin::tool; use crate::command::dispatch::split_stages; use crate::command::output::Output; use crate::term::state::State; @@ -68,24 +69,31 @@ pub fn is_job_command(state: &mut State, args: &[&[u8]]) -> Verdict { Verdict::Handled } }, - // Bare-name run of a bundled store tool (`rg foo`, `sd a b`, ...): route + // Bare-name run of a store tool staged in the vfs (`sd a b`, ...): route // it through the same async installer path as `install `, so the // tool loads and streams its output without blocking the event loop. // The tool name is args[0], which prepare reads as the capsule stem. - name if TOOLS.contains(&name) => match install::prepare(state, args) { + name if STORE_TOOLS.contains(&name) => match install::prepare(state, args) { Some(job) => Verdict::Job(JobWork::InstallDrain(job)), None => { state.last_status = 1; Verdict::Handled } }, + // Bare-name run of a baked, attested tool (`tokei`, `grex foo`, ...): the + // kernel spawns it parented to this terminal and it streams its stdout + // through the same drain job. + name if tool::is_tool(name) => match tool::prepare(state, args) { + Some(job) => Verdict::Job(JobWork::InstallDrain(job)), + None => Verdict::Handled, + }, _ => Verdict::Instant, } } // The CLI tools staged in the vfs store. A bare invocation of one of these runs // it from the store instead of falling through to "unknown verb". -const TOOLS: &[&[u8]] = &[b"sd", b"tokio-smoke", b"std_proof"]; +const STORE_TOOLS: &[&[u8]] = &[b"sd", b"tokio-smoke", b"std_proof"]; fn is_plain(args: &[&[u8]]) -> bool { !args.iter().any(|a| matches!(*a, b"|" | b">" | b">>" | b"<")) diff --git a/userland/libc/src/lib.rs b/userland/libc/src/lib.rs index 89ef676d4e..8fe7f6468a 100644 --- a/userland/libc/src/lib.rs +++ b/userland/libc/src/lib.rs @@ -34,6 +34,7 @@ pub mod proc_output; pub mod process; pub mod procstat; pub mod spawn_instance; +pub mod tool_run; pub mod surface_registry; mod syscall; pub mod time; @@ -54,6 +55,7 @@ pub use broker::{ }; pub use capsule_load::{mk_capsule_load, CapsuleLoadRequest}; pub use spawn_instance::mk_spawn_instance; +pub use tool_run::mk_tool_run; pub use crypto::{ crypto_decrypt, crypto_decrypt_aad, crypto_ed25519_verify, crypto_encrypt, crypto_encrypt_aad, crypto_hash, crypto_hkdf_sha256, crypto_hmac_sha256, crypto_keccak256, diff --git a/userland/libc/src/syscall/mod.rs b/userland/libc/src/syscall/mod.rs index 5835c3a596..0fe643e3e6 100644 --- a/userland/libc/src/syscall/mod.rs +++ b/userland/libc/src/syscall/mod.rs @@ -37,6 +37,7 @@ pub(crate) use numbers::{ N_MK_PCI_CONFIG_WRITE, N_MK_PID_ALIVE, N_MK_PIO_GRANT, N_MK_PIO_READ, N_MK_PIO_RELEASE, N_MK_PIO_WRITE, N_MK_PROC_INPUT, N_MK_PROC_OUTPUT, N_MK_PROC_STAT, N_MK_SERVICE_LOOKUP, N_MK_SERVICE_REGISTER, N_MK_SPAWN_INSTANCE, N_MK_STDIN_READ, N_MK_SURFACE_ATTACH, + N_MK_TOOL_RUN, N_MK_SURFACE_PRESENT, N_MK_SURFACE_REGISTER, N_MK_SURFACE_RELEASE, N_MK_SURFACE_SHARE, N_MK_TIME_ADJUST, N_MK_TIME_MILLIS, N_MK_TIME_RTC, N_MK_UPTIME_MS, N_MK_WAIT, N_MK_YIELD, }; diff --git a/userland/libc/src/syscall/numbers/core.rs b/userland/libc/src/syscall/numbers/core.rs index 50b4336d92..bdd15b055e 100644 --- a/userland/libc/src/syscall/numbers/core.rs +++ b/userland/libc/src/syscall/numbers/core.rs @@ -31,6 +31,7 @@ pub(crate) const N_MK_PROC_STAT: i64 = tag4(b"MPST"); pub(crate) const N_MK_PROC_OUTPUT: i64 = tag4(b"MOUT"); pub(crate) const N_MK_ATTEST_STATUS: i64 = tag4(b"MAST"); pub(crate) const N_MK_SPAWN_INSTANCE: i64 = tag4(b"MSPI"); +pub(crate) const N_MK_TOOL_RUN: i64 = tag4(b"MTRN"); pub(crate) const N_MK_WAIT: i64 = tag4(b"MWAT"); pub(crate) const N_MK_KILL: i64 = tag4(b"MKIL"); pub(crate) const N_MK_PROC_INPUT: i64 = tag4(b"MPIN"); diff --git a/userland/libc/src/tool_run.rs b/userland/libc/src/tool_run.rs new file mode 100644 index 0000000000..e77b64e1b9 --- /dev/null +++ b/userland/libc/src/tool_run.rs @@ -0,0 +1,26 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +//! Run a baked, attested command-line tool by name. The tool is spawned +//! parented to the caller, so the caller drains its stdout with `mk_proc_output` +//! and feeds its stdin with `mk_proc_input`, and waits for exit with `mk_wait`. + +use crate::syscall::{call_raw, N_MK_TOOL_RUN}; + +/// Run the baked tool named `name` (for example `b"tool.tokei"`) with `argv`, a +/// NUL-separated `name\0arg1\0...` blob (empty for none). Returns the new tool's +/// pid, or a negative errno. +pub fn mk_tool_run(name: &[u8], argv: &[u8]) -> i64 { + call_raw( + N_MK_TOOL_RUN, + [ + name.as_ptr() as u64, + name.len() as u64, + argv.as_ptr() as u64, + argv.len() as u64, + 0, + 0, + ], + ) +} From a14b880c05aa71f1af73212defcdf9a58c9ebb8f Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Mon, 27 Jul 2026 17:30:17 +0200 Subject: [PATCH 07/10] tools: vendor the four remaining tool sources and build them from source The nonos-app pipeline left grex, dotenv-linter, jsonxf and csview as prebuilt binaries with no rule to rebuild them, so a clean checkout (and CI) could not reproduce them. Vendor the byte-identical crates.io source for the four under upstream-src, like sd and choose, and add one templated rule that cross-compiles every installed tool to target/upstream-/bin/. grex and tokei gate their binary behind a cli feature, so those two re-enable it; the rest strip default features (unix-only pagers, update checks) the same way the shimmed tools already did. --- .gitignore | 3 + mk/20-build.mk | 32 + userland/upstream-src/csview/.cargo-ok | 1 + .../upstream-src/csview/.cargo_vcs_info.json | 6 + userland/upstream-src/csview/.gitattributes | 1 + .../csview/.github/dependabot.yml | 15 + .../csview/.github/issue_template.md | 22 + .../csview/.github/pull_request_template.md | 29 + .../csview/.github/workflows/CICD.yml | 342 ++ .../csview/.github/workflows/release.yml | 39 + userland/upstream-src/csview/.gitignore | 4 + .../csview/.pre-commit-config.yaml | 43 + userland/upstream-src/csview/CHANGELOG.md | 357 ++ userland/upstream-src/csview/Cargo.lock | 476 ++ userland/upstream-src/csview/Cargo.toml | 86 + userland/upstream-src/csview/Cargo.toml.orig | 37 + userland/upstream-src/csview/FUNDING.yml | 1 + userland/upstream-src/csview/LICENSE-APACHE | 201 + userland/upstream-src/csview/LICENSE-MIT | 19 + userland/upstream-src/csview/README.md | 190 + userland/upstream-src/csview/build.rs | 20 + userland/upstream-src/csview/cliff.toml | 107 + userland/upstream-src/csview/release.toml | 4 + .../upstream-src/csview/rust-toolchain.toml | 3 + userland/upstream-src/csview/rustfmt.toml | 13 + userland/upstream-src/csview/src/cli.rs | 92 + userland/upstream-src/csview/src/main.rs | 102 + userland/upstream-src/csview/src/table/mod.rs | 6 + .../upstream-src/csview/src/table/printer.rs | 260 ++ userland/upstream-src/csview/src/table/row.rs | 96 + .../upstream-src/csview/src/table/style.rs | 277 ++ userland/upstream-src/csview/src/util.rs | 74 + userland/upstream-src/dotenv-linter/.cargo-ok | 1 + .../dotenv-linter/.cargo_vcs_info.json | 6 + .../upstream-src/dotenv-linter/Cargo.lock | 2668 +++++++++++ .../upstream-src/dotenv-linter/Cargo.toml | 117 + .../dotenv-linter/Cargo.toml.orig | 60 + userland/upstream-src/dotenv-linter/README.md | 248 + .../dotenv-linter/benches/check.rs | 34 + .../dotenv-linter/benches/diff.rs | 34 + .../upstream-src/dotenv-linter/benches/fix.rs | 79 + .../upstream-src/dotenv-linter/src/cli.rs | 218 + .../upstream-src/dotenv-linter/src/diff.rs | 40 + .../dotenv-linter/src/fs_utils.rs | 103 + .../upstream-src/dotenv-linter/src/lib.rs | 218 + .../upstream-src/dotenv-linter/src/main.rs | 6 + .../dotenv-linter/src/output/check.rs | 85 + .../dotenv-linter/src/output/diff.rs | 51 + .../dotenv-linter/src/output/fix.rs | 113 + .../dotenv-linter/src/output/mod.rs | 3 + userland/upstream-src/grex/.cargo-ok | 1 + .../upstream-src/grex/.cargo_vcs_info.json | 6 + userland/upstream-src/grex/.editorconfig | 28 + .../upstream-src/grex/.github/dependabot.yml | 11 + .../grex/.github/workflows/python-build.yml | 83 + .../grex/.github/workflows/release.yml | 221 + .../grex/.github/workflows/rust-build.yml | 154 + userland/upstream-src/grex/.gitignore | 55 + userland/upstream-src/grex/Cargo.lock | 1492 ++++++ userland/upstream-src/grex/Cargo.toml | 143 + userland/upstream-src/grex/Cargo.toml.orig | 74 + userland/upstream-src/grex/LICENSE | 201 + userland/upstream-src/grex/README.md | 595 +++ userland/upstream-src/grex/README_PYPI.md | 270 ++ userland/upstream-src/grex/RELEASE_NOTES.md | 190 + .../upstream-src/grex/benches/benchmark.rs | 148 + .../upstream-src/grex/benches/testcases.txt | 50 + userland/upstream-src/grex/demo.gif | Bin 0 -> 388643 bytes userland/upstream-src/grex/demo.tape | 50 + userland/upstream-src/grex/grex.pyi | 152 + userland/upstream-src/grex/logo.png | Bin 0 -> 5711 bytes userland/upstream-src/grex/pyproject.toml | 38 + userland/upstream-src/grex/requirements.txt | 2 + userland/upstream-src/grex/src/builder.rs | 264 ++ userland/upstream-src/grex/src/char_range.rs | 117 + userland/upstream-src/grex/src/cluster.rs | 347 ++ userland/upstream-src/grex/src/component.rs | 347 ++ userland/upstream-src/grex/src/config.rs | 71 + userland/upstream-src/grex/src/dfa.rs | 441 ++ userland/upstream-src/grex/src/expression.rs | 659 +++ userland/upstream-src/grex/src/format.rs | 329 ++ userland/upstream-src/grex/src/grapheme.rs | 256 ++ userland/upstream-src/grex/src/lib.rs | 290 ++ userland/upstream-src/grex/src/macros.rs | 23 + userland/upstream-src/grex/src/main.rs | 447 ++ userland/upstream-src/grex/src/python.rs | 277 ++ userland/upstream-src/grex/src/quantifier.rs | 36 + userland/upstream-src/grex/src/regexp.rs | 280 ++ userland/upstream-src/grex/src/substring.rs | 20 + .../grex/src/unicode_tables/decimal.rs | 97 + .../grex/src/unicode_tables/mod.rs | 23 + .../grex/src/unicode_tables/space.rs | 36 + .../grex/src/unicode_tables/word.rs | 822 ++++ userland/upstream-src/grex/src/wasm.rs | 214 + .../grex/tests/cli_integration_tests.rs | 4050 +++++++++++++++++ .../grex/tests/lib_integration_tests.rs | 2232 +++++++++ .../upstream-src/grex/tests/property_tests.rs | 635 +++ .../grex/tests/python/test_grex.py | 340 ++ .../grex/tests/wasm_browser_tests.rs | 239 + .../grex/tests/wasm_node_tests.rs | 237 + userland/upstream-src/grex/website.jpg | Bin 0 -> 300372 bytes userland/upstream-src/jsonxf/.cargo-ok | 1 + userland/upstream-src/jsonxf/.gitignore | 3 + userland/upstream-src/jsonxf/CHANGELOG.md | 51 + userland/upstream-src/jsonxf/Cargo.lock | 30 + userland/upstream-src/jsonxf/Cargo.toml | 36 + userland/upstream-src/jsonxf/Cargo.toml.orig | 24 + userland/upstream-src/jsonxf/MIT-LICENSE.txt | 8 + userland/upstream-src/jsonxf/README.md | 102 + .../jsonxf/benchmark/benchmark.rb | 61 + userland/upstream-src/jsonxf/src/jsonxf.rs | 460 ++ userland/upstream-src/jsonxf/src/main.rs | 191 + .../jsonxf/tests/formatter_test.rs | 53 + .../jsonxf/tests/minimize_test.rs | 38 + .../jsonxf/tests/pretty_print_test.rs | 72 + .../jsonxf/tests/test_cases_test.rs | 66 + 116 files changed, 25331 insertions(+) create mode 100644 userland/upstream-src/csview/.cargo-ok create mode 100644 userland/upstream-src/csview/.cargo_vcs_info.json create mode 100644 userland/upstream-src/csview/.gitattributes create mode 100644 userland/upstream-src/csview/.github/dependabot.yml create mode 100644 userland/upstream-src/csview/.github/issue_template.md create mode 100644 userland/upstream-src/csview/.github/pull_request_template.md create mode 100644 userland/upstream-src/csview/.github/workflows/CICD.yml create mode 100644 userland/upstream-src/csview/.github/workflows/release.yml create mode 100644 userland/upstream-src/csview/.gitignore create mode 100644 userland/upstream-src/csview/.pre-commit-config.yaml create mode 100644 userland/upstream-src/csview/CHANGELOG.md create mode 100644 userland/upstream-src/csview/Cargo.lock create mode 100644 userland/upstream-src/csview/Cargo.toml create mode 100644 userland/upstream-src/csview/Cargo.toml.orig create mode 100644 userland/upstream-src/csview/FUNDING.yml create mode 100644 userland/upstream-src/csview/LICENSE-APACHE create mode 100644 userland/upstream-src/csview/LICENSE-MIT create mode 100644 userland/upstream-src/csview/README.md create mode 100644 userland/upstream-src/csview/build.rs create mode 100644 userland/upstream-src/csview/cliff.toml create mode 100644 userland/upstream-src/csview/release.toml create mode 100644 userland/upstream-src/csview/rust-toolchain.toml create mode 100644 userland/upstream-src/csview/rustfmt.toml create mode 100644 userland/upstream-src/csview/src/cli.rs create mode 100644 userland/upstream-src/csview/src/main.rs create mode 100644 userland/upstream-src/csview/src/table/mod.rs create mode 100644 userland/upstream-src/csview/src/table/printer.rs create mode 100644 userland/upstream-src/csview/src/table/row.rs create mode 100644 userland/upstream-src/csview/src/table/style.rs create mode 100644 userland/upstream-src/csview/src/util.rs create mode 100644 userland/upstream-src/dotenv-linter/.cargo-ok create mode 100644 userland/upstream-src/dotenv-linter/.cargo_vcs_info.json create mode 100644 userland/upstream-src/dotenv-linter/Cargo.lock create mode 100644 userland/upstream-src/dotenv-linter/Cargo.toml create mode 100644 userland/upstream-src/dotenv-linter/Cargo.toml.orig create mode 100644 userland/upstream-src/dotenv-linter/README.md create mode 100644 userland/upstream-src/dotenv-linter/benches/check.rs create mode 100644 userland/upstream-src/dotenv-linter/benches/diff.rs create mode 100644 userland/upstream-src/dotenv-linter/benches/fix.rs create mode 100644 userland/upstream-src/dotenv-linter/src/cli.rs create mode 100644 userland/upstream-src/dotenv-linter/src/diff.rs create mode 100644 userland/upstream-src/dotenv-linter/src/fs_utils.rs create mode 100644 userland/upstream-src/dotenv-linter/src/lib.rs create mode 100644 userland/upstream-src/dotenv-linter/src/main.rs create mode 100644 userland/upstream-src/dotenv-linter/src/output/check.rs create mode 100644 userland/upstream-src/dotenv-linter/src/output/diff.rs create mode 100644 userland/upstream-src/dotenv-linter/src/output/fix.rs create mode 100644 userland/upstream-src/dotenv-linter/src/output/mod.rs create mode 100644 userland/upstream-src/grex/.cargo-ok create mode 100644 userland/upstream-src/grex/.cargo_vcs_info.json create mode 100644 userland/upstream-src/grex/.editorconfig create mode 100644 userland/upstream-src/grex/.github/dependabot.yml create mode 100644 userland/upstream-src/grex/.github/workflows/python-build.yml create mode 100644 userland/upstream-src/grex/.github/workflows/release.yml create mode 100644 userland/upstream-src/grex/.github/workflows/rust-build.yml create mode 100644 userland/upstream-src/grex/.gitignore create mode 100644 userland/upstream-src/grex/Cargo.lock create mode 100644 userland/upstream-src/grex/Cargo.toml create mode 100644 userland/upstream-src/grex/Cargo.toml.orig create mode 100644 userland/upstream-src/grex/LICENSE create mode 100644 userland/upstream-src/grex/README.md create mode 100644 userland/upstream-src/grex/README_PYPI.md create mode 100644 userland/upstream-src/grex/RELEASE_NOTES.md create mode 100644 userland/upstream-src/grex/benches/benchmark.rs create mode 100644 userland/upstream-src/grex/benches/testcases.txt create mode 100644 userland/upstream-src/grex/demo.gif create mode 100644 userland/upstream-src/grex/demo.tape create mode 100644 userland/upstream-src/grex/grex.pyi create mode 100644 userland/upstream-src/grex/logo.png create mode 100644 userland/upstream-src/grex/pyproject.toml create mode 100644 userland/upstream-src/grex/requirements.txt create mode 100644 userland/upstream-src/grex/src/builder.rs create mode 100644 userland/upstream-src/grex/src/char_range.rs create mode 100644 userland/upstream-src/grex/src/cluster.rs create mode 100644 userland/upstream-src/grex/src/component.rs create mode 100644 userland/upstream-src/grex/src/config.rs create mode 100644 userland/upstream-src/grex/src/dfa.rs create mode 100644 userland/upstream-src/grex/src/expression.rs create mode 100644 userland/upstream-src/grex/src/format.rs create mode 100644 userland/upstream-src/grex/src/grapheme.rs create mode 100644 userland/upstream-src/grex/src/lib.rs create mode 100644 userland/upstream-src/grex/src/macros.rs create mode 100644 userland/upstream-src/grex/src/main.rs create mode 100644 userland/upstream-src/grex/src/python.rs create mode 100644 userland/upstream-src/grex/src/quantifier.rs create mode 100644 userland/upstream-src/grex/src/regexp.rs create mode 100644 userland/upstream-src/grex/src/substring.rs create mode 100644 userland/upstream-src/grex/src/unicode_tables/decimal.rs create mode 100644 userland/upstream-src/grex/src/unicode_tables/mod.rs create mode 100644 userland/upstream-src/grex/src/unicode_tables/space.rs create mode 100644 userland/upstream-src/grex/src/unicode_tables/word.rs create mode 100644 userland/upstream-src/grex/src/wasm.rs create mode 100644 userland/upstream-src/grex/tests/cli_integration_tests.rs create mode 100644 userland/upstream-src/grex/tests/lib_integration_tests.rs create mode 100644 userland/upstream-src/grex/tests/property_tests.rs create mode 100644 userland/upstream-src/grex/tests/python/test_grex.py create mode 100644 userland/upstream-src/grex/tests/wasm_browser_tests.rs create mode 100644 userland/upstream-src/grex/tests/wasm_node_tests.rs create mode 100644 userland/upstream-src/grex/website.jpg create mode 100644 userland/upstream-src/jsonxf/.cargo-ok create mode 100644 userland/upstream-src/jsonxf/.gitignore create mode 100644 userland/upstream-src/jsonxf/CHANGELOG.md create mode 100644 userland/upstream-src/jsonxf/Cargo.lock create mode 100644 userland/upstream-src/jsonxf/Cargo.toml create mode 100644 userland/upstream-src/jsonxf/Cargo.toml.orig create mode 100644 userland/upstream-src/jsonxf/MIT-LICENSE.txt create mode 100644 userland/upstream-src/jsonxf/README.md create mode 100755 userland/upstream-src/jsonxf/benchmark/benchmark.rb create mode 100644 userland/upstream-src/jsonxf/src/jsonxf.rs create mode 100644 userland/upstream-src/jsonxf/src/main.rs create mode 100644 userland/upstream-src/jsonxf/tests/formatter_test.rs create mode 100644 userland/upstream-src/jsonxf/tests/minimize_test.rs create mode 100644 userland/upstream-src/jsonxf/tests/pretty_print_test.rs create mode 100644 userland/upstream-src/jsonxf/tests/test_cases_test.rs diff --git a/.gitignore b/.gitignore index 131b2b894e..ee57734872 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,6 @@ nonos-bootloader/keys # Per-user kernel build configuration written by tools/nonos-config .nonos-config + +# Build output from vendored crates.io tool sources +userland/upstream-src/*/target/ diff --git a/mk/20-build.mk b/mk/20-build.mk index c7a464f647..cd0e830ea0 100644 --- a/mk/20-build.mk +++ b/mk/20-build.mk @@ -293,6 +293,38 @@ $(UPSTREAM_TOKIO_SMOKE_BIN): $(NONOS_RT_OBJ) $(NONOS_STD_PAL_STAMP) \ .PHONY: nonos-mk-upstream-tokio-smoke nonos-mk-upstream-tokio-smoke: $(UPSTREAM_TOKIO_SMOKE_BIN) +# Installed crates.io command-line tools, cross-compiled unmodified for +# x86_64-nonos from the byte-identical source vendored under upstream-src (cargo +# only honors [patch] for path builds, so shimmed crates must build with --path). +# Each tool's Capsule.mk consumes target/upstream-/bin/; this template +# is the one rule that builds it, so adding a tool is a name in NONOS_TOOL_BINS +# plus its `nonos-app add`, not a new recipe. getrandom-backed tools take the +# RDRAND backend, which nonos x86_64 always has. +NONOS_TOOL_BINS := grex dotenv-linter pastel jsonxf choose tokei huniq csview + +# Default is to strip default features (they pull unix-only or update-check +# extras nonos does not want). grex and tokei gate their binary behind a `cli` +# feature, so those two re-enable it. Per-tool overrides live in _CARGO_FEATURES. +NONOS_TOOL_FEATURES_DEFAULT := --no-default-features +grex_CARGO_FEATURES := --no-default-features --features cli +tokei_CARGO_FEATURES := --no-default-features --features cli + +define nonos_upstream_tool_rule +$(TARGET_DIR)/upstream-$(1)/bin/$(1): $(NONOS_RT_OBJ) $(NONOS_STD_PAL_STAMP) \ + userland/x86_64-nonos-user.json | $(TARGET_DIR)/.nonos-toolchain.stamp + @echo "Building upstream $(1) for NONOS (unmodified crates.io source)..." + @cd userland/upstream-src/$(1) && RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \ + RUSTFLAGS="-Clink-arg=$(abspath $(NONOS_RT_OBJ)) --cfg getrandom_backend=\"rdrand\"" \ + $(CARGO) install --path . $(or $($(1)_CARGO_FEATURES),$(NONOS_TOOL_FEATURES_DEFAULT)) \ + --target $(abspath userland/x86_64-nonos-user.json) \ + -Zbuild-std=std,panic_abort -Zbuild-std-features=compiler-builtins-mem \ + --root $(abspath $(TARGET_DIR)/upstream-$(1)) --no-track --force --bin $(1) +endef +$(foreach t,$(NONOS_TOOL_BINS),$(eval $(call nonos_upstream_tool_rule,$(t)))) + +.PHONY: nonos-mk-upstream-tools +nonos-mk-upstream-tools: $(foreach t,$(NONOS_TOOL_BINS),$(TARGET_DIR)/upstream-$(t)/bin/$(t)) + $(CAPSULE_SIGN_BIN): @echo "Building capsule-sign host tool..." @cd nonos-sign && cargo build --release --bin capsule-sign diff --git a/userland/upstream-src/csview/.cargo-ok b/userland/upstream-src/csview/.cargo-ok new file mode 100644 index 0000000000..5f8b795830 --- /dev/null +++ b/userland/upstream-src/csview/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/userland/upstream-src/csview/.cargo_vcs_info.json b/userland/upstream-src/csview/.cargo_vcs_info.json new file mode 100644 index 0000000000..fbcdbf02f2 --- /dev/null +++ b/userland/upstream-src/csview/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "4d36d2d736558517e2c0c6920dc5b1ff639b1f42" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/userland/upstream-src/csview/.gitattributes b/userland/upstream-src/csview/.gitattributes new file mode 100644 index 0000000000..febe51d98a --- /dev/null +++ b/userland/upstream-src/csview/.gitattributes @@ -0,0 +1 @@ +/completions/**/* linguist-generated diff --git a/userland/upstream-src/csview/.github/dependabot.yml b/userland/upstream-src/csview/.github/dependabot.yml new file mode 100644 index 0000000000..93d9249324 --- /dev/null +++ b/userland/upstream-src/csview/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + reviewers: + - wfxr + labels: + - dependencies + commit-message: + prefix: "chore" + prefix-development: "chore" + include: "scope" diff --git a/userland/upstream-src/csview/.github/issue_template.md b/userland/upstream-src/csview/.github/issue_template.md new file mode 100644 index 0000000000..bed735ff10 --- /dev/null +++ b/userland/upstream-src/csview/.github/issue_template.md @@ -0,0 +1,22 @@ + + + + +## Check list + +- [ ] I have read through the [README](https://github.com/wfxr/csview/blob/master/README.md) +- [ ] I have searched through the existing issues + +## Environment info + +- OS + - [ ] Linux + - [ ] Mac OS X + - [ ] Windows + - [ ] Others: + +## Version + + + +## Problem / Steps to reproduce diff --git a/userland/upstream-src/csview/.github/pull_request_template.md b/userland/upstream-src/csview/.github/pull_request_template.md new file mode 100644 index 0000000000..55b00908fe --- /dev/null +++ b/userland/upstream-src/csview/.github/pull_request_template.md @@ -0,0 +1,29 @@ + + +## Check list + +- [ ] I have read through the [README](https://github.com/wfxr/csview/blob/master/README.md) (especially F.A.Q section) +- [ ] I have searched through the existing issues or pull requests +- [ ] I have performed a self-review of my code and commented hard-to-understand areas +- [ ] I have made corresponding changes to the documentation (when necessary) + +## Description + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor +- [ ] Breaking change +- [ ] Documentation change +- [ ] CICD related improvement + +## Test environment + +- OS + - [ ] Linux + - [ ] Mac OS X + - [ ] Windows + - [ ] Others: diff --git a/userland/upstream-src/csview/.github/workflows/CICD.yml b/userland/upstream-src/csview/.github/workflows/CICD.yml new file mode 100644 index 0000000000..a2a5785faa --- /dev/null +++ b/userland/upstream-src/csview/.github/workflows/CICD.yml @@ -0,0 +1,342 @@ +name: CICD + +env: + CICD_INTERMEDIATES_DIR: "_cicd-intermediates" + MSRV_FEATURES: "--all-features" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + tags: + - "*" + +jobs: + crate_metadata: + name: Extract crate metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Extract crate information + id: crate_metadata + run: | + cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"description=" + .packages[0].description' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT + outputs: + name: ${{ steps.crate_metadata.outputs.name }} + version: ${{ steps.crate_metadata.outputs.version }} + maintainer: ${{ steps.crate_metadata.outputs.maintainer }} + homepage: ${{ steps.crate_metadata.outputs.homepage }} + description: ${{ steps.crate_metadata.outputs.description }} + msrv: ${{ steps.crate_metadata.outputs.msrv }} + + ensure_cargo_fmt: + name: Ensure 'cargo fmt' has been run + runs-on: ubuntu-latest + steps: + - uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - uses: actions/checkout@v4 + - run: cargo fmt -- --check + + # min_version: + # name: Minimum supported rust version + # runs-on: ubuntu-latest + # needs: crate_metadata + # steps: + # - name: Checkout source code + # uses: actions/checkout@v4 + # + # - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) + # uses: dtolnay/rust-toolchain@master + # with: + # toolchain: ${{ needs.crate_metadata.outputs.msrv }} + # components: clippy + # - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) + # run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} + # - name: Run tests + # run: cargo test --locked ${{ env.MSRV_FEATURES }} + + build: + name: ${{ matrix.job.target }} (${{ matrix.job.os }}) + runs-on: ${{ matrix.job.os }} + needs: crate_metadata + strategy: + fail-fast: false + matrix: + job: + - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: aarch64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } + - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } + - { target: i686-pc-windows-msvc , os: windows-2019 } + - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + - { target: x86_64-apple-darwin , os: macos-12 } + - { target: aarch64-apple-darwin , os: macos-14 } + # - { target: x86_64-pc-windows-gnu , os: windows-2019 } + - { target: x86_64-pc-windows-msvc , os: windows-2019 } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + env: + BUILD_CMD: cargo + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Install prerequisites + shell: bash + run: | + case ${{ matrix.job.target }} in + arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + targets: ${{ matrix.job.target }} + + - name: Install cross + if: matrix.job.use-cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Overwrite build command env variable + if: matrix.job.use-cross + shell: bash + run: echo "BUILD_CMD=cross" >> $GITHUB_ENV + + - name: Show version information (Rust, cargo, GCC) + shell: bash + run: | + gcc --version || true + rustup -V + rustup toolchain list + rustup default + cargo -V + rustc -V + + - name: Build + shell: bash + run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} + + - name: Set binary name & path + id: bin + shell: bash + run: | + # Figure out suffix of binary + EXE_suffix="" + case ${{ matrix.job.target }} in + *-pc-windows-*) EXE_suffix=".exe" ;; + esac; + + # Setup paths + BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}" + BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}" + + # Let subsequent steps know where to find the binary + echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT + echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT + + - name: Set testing options + id: test-options + shell: bash + run: | + # test only library unit tests and binary for arm-type targets + unset CARGO_TEST_OPTIONS + unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${{ needs.crate_metadata.outputs.name }}" ;; esac; + echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT + + - name: Run tests + shell: bash + run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + + - name: Test run + shell: bash + run: echo -e 'a,b,c\n1,2,3\n4,5,6\n7,8,9' | $BUILD_CMD run --locked --target=${{ matrix.job.target }} + + - name: Strip binary + shell: bash + run: | + if hash strip &>/dev/null; then + # strip binary if possible + strip "${{ steps.bin.outputs.BIN_PATH }}" || true + fi + + - name: Create tarball + id: package + shell: bash + run: | + PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; + PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} + PKG_NAME=${PKG_BASENAME}${PKG_suffix} + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT + + PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" + ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" + mkdir -p "${ARCHIVE_DIR}" + + # Binary + cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR" + + # README and LICENSE files + cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "$ARCHIVE_DIR" + + # Autocompletion files + cp -r completions "${ARCHIVE_DIR}" + + # base compressed package + pushd "${PKG_STAGING}/" >/dev/null + case ${{ matrix.job.target }} in + *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;; + *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;; + esac; + popd >/dev/null + + # Let subsequent steps know where to find the compressed package + echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT + + - name: Create Debian package + id: debian-package + shell: bash + if: startsWith(matrix.job.os, 'ubuntu') + run: | + COPYRIGHT_YEARS="2020 - "$(date "+%Y") + DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package" + DPKG_DIR="${DPKG_STAGING}/dpkg" + mkdir -p "${DPKG_DIR}" + + DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }} + DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl + case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; + DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} + + unset DPKG_ARCH + case ${{ matrix.job.target }} in + aarch64-*-linux-*) DPKG_ARCH=arm64 ;; + arm-*-linux-*hf) DPKG_ARCH=armhf ;; + i686-*-linux-*) DPKG_ARCH=i686 ;; + x86_64-*-linux-*) DPKG_ARCH=amd64 ;; + *) DPKG_ARCH=notset ;; + esac; + + DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" + echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT + + # Binary + install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" + + # Man page + # install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" + # gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" + + # Autocompletion files + install -Dm644 'completions/bash/${{ needs.crate_metadata.outputs.name }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}" + install -Dm644 'completions/fish/${{ needs.crate_metadata.outputs.name }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish" + install -Dm644 'completions/zsh/_${{ needs.crate_metadata.outputs.name }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}" + + # README and LICENSE + install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md" + install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT" + install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE" + + cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" < "${DPKG_DIR}/DEBIAN/control" <> $GITHUB_OUTPUT + + # build dpkg + fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" + + - name: "Artifact upload: tarball" + uses: actions/upload-artifact@master + with: + name: ${{ steps.package.outputs.PKG_NAME }} + path: ${{ steps.package.outputs.PKG_PATH }} + + - name: "Artifact upload: Debian package" + uses: actions/upload-artifact@master + if: steps.debian-package.outputs.DPKG_NAME + with: + name: ${{ steps.debian-package.outputs.DPKG_NAME }} + path: ${{ steps.debian-package.outputs.DPKG_PATH }} + + - name: Check for release + id: is-release + shell: bash + run: | + unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT + + - name: Publish archives and packages + uses: softprops/action-gh-release@v1 + if: steps.is-release.outputs.IS_RELEASE + with: + files: | + ${{ steps.package.outputs.PKG_PATH }} + ${{ steps.debian-package.outputs.DPKG_PATH }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/userland/upstream-src/csview/.github/workflows/release.yml b/userland/upstream-src/csview/.github/workflows/release.yml new file mode 100644 index 0000000000..118b953bec --- /dev/null +++ b/userland/upstream-src/csview/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +jobs: + changelog: + name: Generate and publish changelog + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate a changelog + uses: orhun/git-cliff-action@v3 + id: git-cliff + with: + config: cliff.toml + args: -v --latest --strip header + env: + OUTPUT: CHANGES.md + GITHUB_REPO: ${{ github.repository }} + + - name: Polish changelog + shell: bash + run: sed -i '1,2d' CHANGES.md + + - name: Upload the changelog + uses: ncipollo/release-action@v1 + with: + # draft: true + allowUpdates: true + bodyFile: CHANGES.md diff --git a/userland/upstream-src/csview/.gitignore b/userland/upstream-src/csview/.gitignore new file mode 100644 index 0000000000..2c882fbd77 --- /dev/null +++ b/userland/upstream-src/csview/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/flamegraph.svg +/perf.data diff --git a/userland/upstream-src/csview/.pre-commit-config.yaml b/userland/upstream-src/csview/.pre-commit-config.yaml new file mode 100644 index 0000000000..885e8446bb --- /dev/null +++ b/userland/upstream-src/csview/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: check-added-large-files + - id: mixed-line-ending + - id: check-toml +- repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + pass_filenames: false + always_run: true + language: system + entry: cargo fmt + - id: cargo-check + name: cargo check + pass_filenames: false + always_run: true + language: system + entry: cargo check + - id: cargo-clippy + name: cargo clippy + pass_filenames: false + language: system + always_run: true + entry: cargo clippy + args: ["--", "-D", "warnings"] + - id: update-completions + name: update shell completions + pass_filenames: false + language: system + always_run: true + entry: > + sh -c ' + touch build.rs && + SHELL_COMPLETIONS_DIR=completions cargo build && + git add completions + ' diff --git a/userland/upstream-src/csview/CHANGELOG.md b/userland/upstream-src/csview/CHANGELOG.md new file mode 100644 index 0000000000..5d54c1f4ec --- /dev/null +++ b/userland/upstream-src/csview/CHANGELOG.md @@ -0,0 +1,357 @@ + +## [1.3.4](https://github.com/wfxr/csview/compare/v1.3.3..1.3.4) (2024-12-28) + +### 🚀 Features + +- Colored help message - ([d08efb0](https://github.com/wfxr/csview/commit/d08efb0b044bce4df81960ac1e877e2ca209c993)) + +### ⚙️ Miscellaneous Tasks + +- Ignore release commit message for cliff - ([70d956e](https://github.com/wfxr/csview/commit/70d956ea6de85909b7d935ca8febca9897d0d844)) + +## [1.3.3](https://github.com/wfxr/csview/compare/v1.3.2..v1.3.3) (2024-07-08) + +### 🐛 Bug Fixes + +- Workaround error caused by pager env - ([988add5](https://github.com/wfxr/csview/commit/988add57b9de99c61140c68fd868ee8bec79f5a2)) + +## [1.3.2](https://github.com/wfxr/csview/compare/v1.3.1..v1.3.2) (2024-04-30) + +### ⚙️ Miscellaneous Tasks + +- Add aarch64-apple-darwin target - ([4481363](https://github.com/wfxr/csview/commit/448136378a3edab2e1669efab63cd81cde20a703)) +- Update completions for new option - ([3565cfe](https://github.com/wfxr/csview/commit/3565cfee2dd62c4a22f5752ec261aa0b628e0a4a)) + +## [1.3.1](https://github.com/wfxr/csview/compare/v1.3.0..v1.3.1) (2024-04-29) + +### 🚀 Features + +- Only enable pager when stdout is a tty - ([420fd03](https://github.com/wfxr/csview/commit/420fd0310db32d42315fc5dcb2b745a4c4cc6909)) +- Allow disabling pager - ([80ae054](https://github.com/wfxr/csview/commit/80ae054283d857814408d186643105b57f7c559d)) + +### 📚 Documentation + +- Remove outdated faq - ([d814a38](https://github.com/wfxr/csview/commit/d814a389eec4a50ee6c0effc19ca601a37ff4395)) + +### ⚙️ Miscellaneous Tasks + +- Remove stale.yml - ([d3ac13b](https://github.com/wfxr/csview/commit/d3ac13b72a6af52cc81ab4482c1db44670fdb307)) + +## [1.3.0](https://github.com/wfxr/csview/compare/v1.2.4..v1.3.0) (2024-04-15) + +### 🚀 Features + +- Add pager support ([#198](https://github.com/wfxr/csview/issues/198)) - ([47d3c0a](https://github.com/wfxr/csview/commit/47d3c0a61b6e605ff2ebb0cd4d8d9a60adf758e2)) +- Show error when no input file specified - ([1214c96](https://github.com/wfxr/csview/commit/1214c962a6ff2d20eb1bb67fff3c49053406ca5f)) + +### 🚜 Refactor + +- Replace unmaintained `atty` ([#200](https://github.com/wfxr/csview/issues/200)) - ([d60612b](https://github.com/wfxr/csview/commit/d60612b669c41a2f4515f3a3cb067f3be185274e)) + +### ⚙️ Miscellaneous Tasks + +- Update dependencies - ([b9aed24](https://github.com/wfxr/csview/commit/b9aed24bc46e725a6de1c761e3eee2c508ed5338)) +- Fix release.toml - ([8b28b89](https://github.com/wfxr/csview/commit/8b28b89824b54fd110f9b73a24d8d839e8db627b)) +- Add rust-toolchain.toml & update rustfmt.toml ([#201](https://github.com/wfxr/csview/issues/201)) - ([6fc99d4](https://github.com/wfxr/csview/commit/6fc99d432a9a0670430beef1f74e8da5c126e547)) +- Add changelog ([#199](https://github.com/wfxr/csview/issues/199)) - ([a15325d](https://github.com/wfxr/csview/commit/a15325d1ac8d9d90172247b76bb8e3cc734da6d8)) +- Add FUNDING.yml - ([0e6045f](https://github.com/wfxr/csview/commit/0e6045fb85d2ca748c7dfd7f4e075fce0641bfe1)) + +## [1.2.4](https://github.com/wfxr/csview/compare/v1.2.3..v1.2.4) (2024-03-04) + +### ⚙️ Miscellaneous Tasks + +- Update cicd config ([#191](https://github.com/wfxr/csview/issues/191)) - ([3ea5126](https://github.com/wfxr/csview/commit/3ea512652476364ab2afcbae37d9432ce7695192)) + +## [1.2.3](https://github.com/wfxr/csview/compare/v1.2.2..v1.2.3) (2024-02-22) + +### ⚙️ Miscellaneous Tasks + +- Cargo update - ([d903e78](https://github.com/wfxr/csview/commit/d903e78363b850686d2a878ab101fe50f0a6e875)) +- Update stale config - ([e39d5a9](https://github.com/wfxr/csview/commit/e39d5a9972d0e742490c068dcc49781ab761e0af)) +- Bump up dependencies - ([4e8034f](https://github.com/wfxr/csview/commit/4e8034fda7a2f212898196bf444ff7cf1229ca87)) +- Avoid unnecessary ci jobs - ([0cec5ca](https://github.com/wfxr/csview/commit/0cec5cab52cb3e21fc1a3c70830b205302833660)) + +## [1.2.2](https://github.com/wfxr/csview/compare/v1.2.1..v1.2.2) (2022-10-09) + +### 🐛 Bug Fixes + +- Fix tests - ([9383041](https://github.com/wfxr/csview/commit/93830413d754f52b1ede1b7b0826f9e489720732)) + +### 🚜 Refactor + +- Adjust error code - ([cf101e9](https://github.com/wfxr/csview/commit/cf101e951a59f482b8271debf7bcef52c6273105)) +- Simplify sniff logic - ([9df90a5](https://github.com/wfxr/csview/commit/9df90a547969bcd9b1695ed5c019dffb84d9c916)) +- Simplify seq logic - ([14c1c88](https://github.com/wfxr/csview/commit/14c1c88aac405bcd961d846a8949502499a65b12)) + +### ⚙️ Miscellaneous Tasks + +- Upgrade clap to v4 - ([c0380e3](https://github.com/wfxr/csview/commit/c0380e306e272fa2cfd66d6394e2e643e1ac6f18)) +- Update stale config - ([4dc8ff1](https://github.com/wfxr/csview/commit/4dc8ff1b0692ba280b8dc8c9cd49f7c3712dfc59)) +- Refactor project layout - ([7b0c620](https://github.com/wfxr/csview/commit/7b0c62088e1179b0d160818c24a4baac5172d364)) + +## [1.2.1](https://github.com/wfxr/csview/compare/v1.2.0..v1.2.1) (2022-09-23) + +### 🚜 Refactor + +- Serialize enum in lowercase - ([1715587](https://github.com/wfxr/csview/commit/1715587a57fb27bf119fe959c5254ee5da21ab22)) + +## [1.2.0](https://github.com/wfxr/csview/compare/v1.1.0..v1.2.0) (2022-09-23) + +### 🚀 Features + +- Add alignment cli options - ([429eb5d](https://github.com/wfxr/csview/commit/429eb5d61cf23d5eee266dd06ee2559af280df6c)) +- Add alignment support - ([a01d227](https://github.com/wfxr/csview/commit/a01d2279f1b7b5ffccf04ee0da9a1398a6d30af8)) +- Add minimal ascii border style - ([0c5ff8e](https://github.com/wfxr/csview/commit/0c5ff8ecb85b50d3182af99891eac04b5cc55209)) + +### 📚 Documentation + +- Update example - ([af43fc9](https://github.com/wfxr/csview/commit/af43fc9dc9ffee47425e75643aee4df8742927b0)) + +### ⚙️ Miscellaneous Tasks + +- Bump up dependencies - ([d7ddc08](https://github.com/wfxr/csview/commit/d7ddc080fe2cdb5353b8372649e40dce30e5878e)) + +## [1.1.0](https://github.com/wfxr/csview/compare/v1.0.1..v1.1.0) (2022-07-02) + +### 🚀 Features + +- Support printing line numbers - ([95b31a5](https://github.com/wfxr/csview/commit/95b31a5700f6a4f460950f0f7baa89559d0c989b)) + +### 🚜 Refactor + +- Fix cargo clippy warnings - ([5a1e6d9](https://github.com/wfxr/csview/commit/5a1e6d9e353313a1ebd86cd01e712458e4c94321)) + +### 📚 Documentation + +- `csview` now in homebrew core repo - ([4fec934](https://github.com/wfxr/csview/commit/4fec9346aba10fc1b2ebfc797d83b8ce0f25a1ca)) + +### ⚙️ Miscellaneous Tasks + +- Bump up dependencies - ([ba43992](https://github.com/wfxr/csview/commit/ba43992ed2e1d8087b2f9b0f9d7a5f315b04a4da)) + +## [1.0.1](https://github.com/wfxr/csview/compare/v1.0.0..v1.0.1) (2022-02-16) + +### 📚 Documentation + +- Update readme - ([84a8392](https://github.com/wfxr/csview/commit/84a839265227182467fafc9ef285fb289d0b0eea)) + +### ⚙️ Miscellaneous Tasks + +- Remove unstable features - ([aca9261](https://github.com/wfxr/csview/commit/aca9261dd6d4e1f4245087537750b4969b82308f)) + +## [1.0.0](https://github.com/wfxr/csview/compare/v0.3.12..v1.0.0) (2022-02-16) + +### 🚀 Features + +- Change default style - ([2389b4a](https://github.com/wfxr/csview/commit/2389b4a75ce6b91c4188a375ba7bd6df98c3afaa)) +- Remove completion sub command - ([d29caba](https://github.com/wfxr/csview/commit/d29cababf42f9b930ef08a3af7b46eb47d78a4e6)) +- Add sniff limit option - ([56b4858](https://github.com/wfxr/csview/commit/56b485830165f8a093646b602cde55a37585e0cb)) +- Add padding and indent option - ([40d7085](https://github.com/wfxr/csview/commit/40d7085479eefe166b56858f19f62df83f0c34e6)) +- Skip title sep when there is no data - ([d53caa5](https://github.com/wfxr/csview/commit/d53caa5675094c33a673208f9ad26193058af781)) +- Replace `prettytable-rs` - ([081d965](https://github.com/wfxr/csview/commit/081d9658a038efea94ffc34f85d6903dac1f4ae4)) +- Implement table writer - ([9fdb7a6](https://github.com/wfxr/csview/commit/9fdb7a6903f0c6c85caea8990ad618893c8ca170)) + +### 🚜 Refactor + +- Rename & clean derive traits - ([d77a5b0](https://github.com/wfxr/csview/commit/d77a5b0fe5b10ebd9900c392585b314b0f1d09b2)) +- Remove cell - ([46bd2c4](https://github.com/wfxr/csview/commit/46bd2c414778b44155c20e778cba88c5784f978d)) +- Lint - ([cf5f340](https://github.com/wfxr/csview/commit/cf5f3406175fefc8a255af2b3fee6f658d146e1b)) +- Rename - ([573e297](https://github.com/wfxr/csview/commit/573e297c52695d12a61a9faec2e4bac946cadceb)) +- Remove redundant enums - ([863c97e](https://github.com/wfxr/csview/commit/863c97e74cd4f1f1b08711fe77e63b39b2d0a37c)) +- Rename - ([d6b336a](https://github.com/wfxr/csview/commit/d6b336a4c910e26563c4877fe809110c47cbc226)) +- Tweak cli options - ([b574e79](https://github.com/wfxr/csview/commit/b574e79b7b7d055ccd515a0ffa19ce4515a8e80c)) + +### 📚 Documentation + +- Update default style - ([bab68ce](https://github.com/wfxr/csview/commit/bab68ce17c106ffea523b522116a100132dad33a)) +- Fix time unit - ([b21642c](https://github.com/wfxr/csview/commit/b21642c5c550af87311bfc2e7ff78049d979f933)) +- Update - ([4d4461e](https://github.com/wfxr/csview/commit/4d4461e3ffec65dd2be0d6f1c87f6bd1b2c43fad)) +- Update - ([a83ad55](https://github.com/wfxr/csview/commit/a83ad555539bbe6a0e0768c01f9d0600f3bdc0cb)) + +### ⚡ Performance + +- Add buffer on writer - ([86c6101](https://github.com/wfxr/csview/commit/86c610119d506b0a09453c5f3b3a3a1223ae5cca)) +- Remove unnecessary buffer - ([ffc59ca](https://github.com/wfxr/csview/commit/ffc59ca90e2aa2c1fdcdc1c20e9114e693dc5c71)) + +### 🧪 Testing + +- Add no padding test - ([ca15128](https://github.com/wfxr/csview/commit/ca15128b1b2a0ec60e3f1e043946f87cbd96bb36)) + +### ⚙️ Miscellaneous Tasks + +- Bump up version - ([8cd76c7](https://github.com/wfxr/csview/commit/8cd76c73a75d0db1db7687d131d54d398f5b2967)) +- NextLineHelp - ([cb795c9](https://github.com/wfxr/csview/commit/cb795c95a8ba941e32127798ab515059b2f6d33b)) +- Switched to nightly rust - ([abb2300](https://github.com/wfxr/csview/commit/abb23008218c103cb6afba38c1381181b2d79908)) + +## [0.3.11](https://github.com/wfxr/csview/compare/v0.3.10..v0.3.11) (2022-01-01) + +### 🚜 Refactor + +- Misc - ([35bca57](https://github.com/wfxr/csview/commit/35bca57f605fa825d56d965eff1c7ea200d5c570)) + +### ⚙️ Miscellaneous Tasks + +- Update dependabot config - ([2d2cc6d](https://github.com/wfxr/csview/commit/2d2cc6d1ab09ac5349de0e426a6fc60ee267826c)) +- Clap v3 - ([569552b](https://github.com/wfxr/csview/commit/569552b57c5c5d922885aeeec515c67156b6c5ad)) + +## [0.3.10](https://github.com/wfxr/csview/compare/v0.3.9..v0.3.10) (2021-12-26) + +### 🚀 Features + +- Upgrade cli - ([2b40db4](https://github.com/wfxr/csview/commit/2b40db482fbe9b86fff74618590154941dd21939)) + +### 📚 Documentation + +- Update - ([7934cf7](https://github.com/wfxr/csview/commit/7934cf758b268c7dde0caa1b91d8b503414b18ff)) + +### ⚙️ Miscellaneous Tasks + +- Add rustfmt.toml - ([a0b9025](https://github.com/wfxr/csview/commit/a0b9025e930dbd947363e0149c0ffaa3332280e4)) + +### Typo + +- Misc - ([ce696ba](https://github.com/wfxr/csview/commit/ce696ba7ff2b89680b7bc4b9bb5bb145a86872f5)) + +## [0.3.9](https://github.com/wfxr/csview/compare/v0.3.8..v0.3.9) (2021-11-23) + +### 🚜 Refactor + +- Clippy lint - ([f36edb1](https://github.com/wfxr/csview/commit/f36edb1bdd49c6916d76d3f3687e4cde876c58e6)) + +### 📚 Documentation + +- Add pkgsrc installation. - ([9888062](https://github.com/wfxr/csview/commit/9888062d1b58de4f5a11ed05970c809dcab012a7)) + +### ⚙️ Miscellaneous Tasks + +- Update license - ([a1de726](https://github.com/wfxr/csview/commit/a1de726f18fd22de1c4d4111a56aee2328262b75)) +- Update ci config - ([33b2b94](https://github.com/wfxr/csview/commit/33b2b946833952eeda82fa04d6d92888f4434843)) +- Update linguist - ([a82edc9](https://github.com/wfxr/csview/commit/a82edc94c48ceab598bbf510481544763e0990e1)) +- Upgrade pre-commit version - ([26fe3d8](https://github.com/wfxr/csview/commit/26fe3d86b9bdf4e1497f3deb28d21677402cae03)) +- Upgrade to 2021 edition - ([0035c2e](https://github.com/wfxr/csview/commit/0035c2e6c636fc574e589dc5b00bfb6b0e5db3e0)) + +## [0.3.8](https://github.com/wfxr/csview/compare/v0.3.7..v0.3.8) (2021-04-05) + +### 🐛 Bug Fixes + +- Fix output when table is empty ([#8](https://github.com/wfxr/csview/issues/8)) - ([2fbe3fd](https://github.com/wfxr/csview/commit/2fbe3fd051babe2531bae92f6cca6a495841676e)) + +## [0.3.7](https://github.com/wfxr/csview/compare/v0.3.6..v0.3.7) (2021-03-19) + +### 🚜 Refactor + +- Improve error handling - ([67f7b44](https://github.com/wfxr/csview/commit/67f7b44dfa139ba56e24cf0b48001487fddfb0a9)) +- Error handling - ([b6c38ec](https://github.com/wfxr/csview/commit/b6c38ecd7f2060f5b906282324725f400ec8e26d)) + +### ⚙️ Miscellaneous Tasks + +- Add dependabot - ([f5e5920](https://github.com/wfxr/csview/commit/f5e5920677a5f4fc8317f0fc77645de681c68ea1)) + +## [0.3.6](https://github.com/wfxr/csview/compare/v0.3.5..v0.3.6) (2021-02-07) + +### 🚜 Refactor + +- Minor refactor & bump up dependencies - ([6edd84e](https://github.com/wfxr/csview/commit/6edd84e90056949916797b25f5aae56ddac8ed37)) + +### 📚 Documentation + +- Add install description for windows - ([06b3006](https://github.com/wfxr/csview/commit/06b3006971066b77a516b8c2e90ef4c306f6a781)) + +### ⚙️ Miscellaneous Tasks + +- Add .gitattributes - ([ca098b0](https://github.com/wfxr/csview/commit/ca098b0e5f9de1dd65993916220e683e9f131a13)) +- Update clippy hook - ([2aafe79](https://github.com/wfxr/csview/commit/2aafe7941bdc50e33a0d5d5d1427f1828e58b77b)) + +## [0.3.5](https://github.com/wfxr/csview/compare/v0.3.4..v0.3.5) (2020-09-23) + +### 🚀 Features + +- Add grid style - ([3b758a9](https://github.com/wfxr/csview/commit/3b758a9a406bede7e921dcb7c716ff3e2a5ac2b2)) + +### 📚 Documentation + +- Add macOS installation - ([985d643](https://github.com/wfxr/csview/commit/985d6439c9d79ac648e9930ee87895193d0550b7)) +- Fix table indent in README - ([5356a4d](https://github.com/wfxr/csview/commit/5356a4d462c4ffb85f3bb162bef192c5a0787d40)) +- Add arch linux installation - ([e0c178b](https://github.com/wfxr/csview/commit/e0c178b8f39b46b9a8dfdd9ba35e3f64206ac4ae)) + +## [0.3.4](https://github.com/wfxr/csview/compare/v0.3.3..v0.3.4) (2020-09-19) + +### 🚜 Refactor + +- Use exit code defined in sysexits.h - ([c52153e](https://github.com/wfxr/csview/commit/c52153e9f1c187953c663ee0e90c12bcc0d1c38c)) + +### 📚 Documentation + +- Misc - ([177f555](https://github.com/wfxr/csview/commit/177f555f17aff140319388b3ba57308a2ee47499)) +- Add installation section - ([320fd43](https://github.com/wfxr/csview/commit/320fd435f97206dffafe2238af99689e1e1b16df)) + +### ⚙️ Miscellaneous Tasks + +- Remove duplicated hooks - ([a0e3ca3](https://github.com/wfxr/csview/commit/a0e3ca3e85c0cab427eba54fcac7eae5a068cfc4)) +- Words - ([5691214](https://github.com/wfxr/csview/commit/5691214a66abf188e6c77f6a568dcddcc4947477)) +- Add probot-stale config - ([2f036c2](https://github.com/wfxr/csview/commit/2f036c25082e1e2eab1fc3577b3730e858302210)) +- Add issue/pr templates - ([4b1ba13](https://github.com/wfxr/csview/commit/4b1ba136ad9d5a537987d0cdec88eee64c9a8a6b)) + +## [0.3.3](https://github.com/wfxr/csview/compare/v0.3.2..v0.3.3) (2020-09-19) + +### 🚜 Refactor + +- Improve error handling ([#2](https://github.com/wfxr/csview/issues/2)) - ([511ac52](https://github.com/wfxr/csview/commit/511ac52313df9aa409a9f48142fb108d72ebb26a)) + +### 📚 Documentation + +- Add credits section - ([a037d45](https://github.com/wfxr/csview/commit/a037d455327d41d25fd15e3a27fc989d1560f024)) +- Add features section - ([7cf4aa6](https://github.com/wfxr/csview/commit/7cf4aa6ae126bbc117babd04f358beddd21fb4fc)) +- Add faq section - ([841bd55](https://github.com/wfxr/csview/commit/841bd55c63a5dc7e826ba4408192afb4f89c3710)) + +### ⚙️ Miscellaneous Tasks + +- Add about message - ([a2b2f9d](https://github.com/wfxr/csview/commit/a2b2f9dc50705c9fa19862e2b9501d6a2578240f)) + +### Lint + +- Cargo clippy - ([b1cb104](https://github.com/wfxr/csview/commit/b1cb104788062bc0e88c105eee5cb9275bbc8362)) + +## [0.3.2](https://github.com/wfxr/csview/compare/v0.3.1..v0.3.2) (2020-09-18) + +### 🚀 Features + +- Add markdown style - ([9fb038e](https://github.com/wfxr/csview/commit/9fb038e09e5501a7d5e51a704b99467f532dc18e)) + +### 📚 Documentation + +- Fix crates badge - ([a24e2be](https://github.com/wfxr/csview/commit/a24e2be7391029a1947c1e06565bd2f46341526b)) + +### ⚙️ Miscellaneous Tasks + +- Fix a typo ([#1](https://github.com/wfxr/csview/issues/1)) - ([a5e2e39](https://github.com/wfxr/csview/commit/a5e2e3998472f70b523bbc4886f932fea960b7a9)) + +## [0.3.1](https://github.com/wfxr/csview/compare/v0.3.0..v0.3.1) (2020-09-17) + +### 📚 Documentation + +- Add more descriptions - ([113609f](https://github.com/wfxr/csview/commit/113609f0590190dffcad799fa9862a1b0b9012e0)) +- Update readme - ([74c07f4](https://github.com/wfxr/csview/commit/74c07f419435ac7dbc5f87422695db787cd42c7d)) +- Update readme - ([906c747](https://github.com/wfxr/csview/commit/906c7474df7bd0860d4d72f35a1714a2fb76c46e)) +- Update help messages - ([1d48b23](https://github.com/wfxr/csview/commit/1d48b23eb18d52d2efe5bfb3236682a859a7ad1c)) + +## [0.3.0] - 2020-09-17 + +### 🚀 Features + +- Show possible values for shells - ([c2bbcbc](https://github.com/wfxr/csview/commit/c2bbcbca944d3fc27bd76314ae7ffed72c24d504)) +- Support border style - ([7be10ae](https://github.com/wfxr/csview/commit/7be10ae0148170e7ac6c3eca0797f9ae2bf83906)) +- Support tsv - ([d53fb02](https://github.com/wfxr/csview/commit/d53fb02bffab217b5cd1c55ff14defe694c73ded)) +- Add delimeter support - ([cc9a1b0](https://github.com/wfxr/csview/commit/cc9a1b075e5942e1c36f5b62077595263705c5fb)) +- Use prettytable to print - ([751980c](https://github.com/wfxr/csview/commit/751980cab7be9d9e4c4079835bb5b9a1b7ca30e7)) + +### 🚜 Refactor + +- Remove update command - ([3002149](https://github.com/wfxr/csview/commit/300214906649498b9713ebaa69cea7128ad43220)) + +### ⚙️ Miscellaneous Tasks + +- Add pre-commit hooks - ([cbc60d4](https://github.com/wfxr/csview/commit/cbc60d42287aef1e225961c33ff9a97f3a1c0a6d)) +- Ci & cd - ([985bd1e](https://github.com/wfxr/csview/commit/985bd1e2b9255e9a58709a376b7ce1b99f4f1fca)) + + diff --git a/userland/upstream-src/csview/Cargo.lock b/userland/upstream-src/csview/Cargo.lock new file mode 100644 index 0000000000..c35a177cf7 --- /dev/null +++ b/userland/upstream-src/csview/Cargo.lock @@ -0,0 +1,476 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cc" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" + +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_complete" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "csview" +version = "1.3.4" +dependencies = [ + "anyhow", + "clap", + "clap_complete", + "csv", + "exitcode", + "itertools", + "pager", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "pager" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2599211a5c97fbbb1061d3dc751fa15f404927e4846e07c643287d6d1f462880" +dependencies = [ + "errno 0.2.8", + "libc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags", + "errno 0.3.8", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-truncate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/userland/upstream-src/csview/Cargo.toml b/userland/upstream-src/csview/Cargo.toml new file mode 100644 index 0000000000..aa9a51cf29 --- /dev/null +++ b/userland/upstream-src/csview/Cargo.toml @@ -0,0 +1,86 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "csview" +version = "1.3.4" +authors = ["Wenxuan Zhang "] +build = "build.rs" +exclude = ["/completions"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "A high performance csv viewer with cjk/emoji support." +homepage = "https://github.com/wfxr/csview" +readme = "README.md" +keywords = [ + "csv", + "pager", + "viewer", + "tool", +] +categories = ["command-line-utilities"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/wfxr/csview" + +[profile.release] +lto = true +codegen-units = 1 + +[[bin]] +name = "csview" +path = "src/main.rs" + +[dependencies.anyhow] +version = "1.0" + +[dependencies.clap] +version = "4" +features = [ + "wrap_help", + "derive", +] + +[dependencies.csv] +version = "1.3" + +[dependencies.exitcode] +version = "1.1" + +[dependencies.itertools] +version = "0.13" + +[dependencies.unicode-truncate] +version = "2.0" + +[dependencies.unicode-width] +version = "0" + +[build-dependencies.clap] +version = "4" +features = [ + "wrap_help", + "derive", +] + +[build-dependencies.clap_complete] +version = "4" + +[features] +default = ["pager"] +pager = ["dep:pager"] + +[target.'cfg(target_family = "unix")'.dependencies.pager] +version = "0.16" +optional = true diff --git a/userland/upstream-src/csview/Cargo.toml.orig b/userland/upstream-src/csview/Cargo.toml.orig new file mode 100644 index 0000000000..420ccb5b19 --- /dev/null +++ b/userland/upstream-src/csview/Cargo.toml.orig @@ -0,0 +1,37 @@ +[package] +name = "csview" +version = "1.3.4" +authors = ["Wenxuan Zhang "] +description = "A high performance csv viewer with cjk/emoji support." +categories = ["command-line-utilities"] +homepage = "https://github.com/wfxr/csview" +keywords = ["csv", "pager", "viewer", "tool"] +readme = "README.md" +license = "MIT OR Apache-2.0" +exclude = ["/completions"] +repository = "https://github.com/wfxr/csview" +edition = "2021" +build = "build.rs" + +[features] +default = ["pager"] +pager = ["dep:pager"] + +[dependencies] +csv = "1.3" +clap = { version = "4", features = ["wrap_help", "derive"] } +exitcode = "1.1" +anyhow = "1.0" +unicode-width = "0" +unicode-truncate = "2.0" +itertools = "0.13" +[target.'cfg(target_family = "unix")'.dependencies] +pager = { version = "0.16", optional = true } + +[build-dependencies] +clap = { version = "4", features = ["wrap_help", "derive"] } +clap_complete = "4" + +[profile.release] +lto = true +codegen-units = 1 diff --git a/userland/upstream-src/csview/FUNDING.yml b/userland/upstream-src/csview/FUNDING.yml new file mode 100644 index 0000000000..63b4a7e420 --- /dev/null +++ b/userland/upstream-src/csview/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: wfxr diff --git a/userland/upstream-src/csview/LICENSE-APACHE b/userland/upstream-src/csview/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/userland/upstream-src/csview/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/userland/upstream-src/csview/LICENSE-MIT b/userland/upstream-src/csview/LICENSE-MIT new file mode 100644 index 0000000000..6eac644a2d --- /dev/null +++ b/userland/upstream-src/csview/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2020 Wenxuan Zhang (https://wfxr.mit-license.org/2020). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/userland/upstream-src/csview/README.md b/userland/upstream-src/csview/README.md new file mode 100644 index 0000000000..dc20ed7b3c --- /dev/null +++ b/userland/upstream-src/csview/README.md @@ -0,0 +1,190 @@ +

📠 csview

+

+ A high performance csv viewer with cjk/emoji support. +

+ +

+ + CICD + + License + + Version + + + Platform + +

+ + + +### Features + +* Small and *fast* (see [benchmarks](#benchmark) below). +* Memory efficient. +* Correctly align [CJK](https://en.wikipedia.org/wiki/CJK_characters) and emoji characters. +* Support `tsv` and custom delimiters. +* Support different styles, including markdown table. + +### Usage +``` +$ cat example.csv +Year,Make,Model,Description,Price +1997,Ford,E350,"ac, abs, moon",3000.00 +1999,Chevy,"Venture ""Extended Edition""","",4900.00 +1999,Chevy,"Venture ""Extended Edition, Large""",,5000.00 +1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof",4799.00 + +$ csview example.csv +┌──────┬───────┬───────────────────────────────────┬───────────────────────────┬─────────┐ +│ Year │ Make │ Model │ Description │ Price │ +├──────┼───────┼───────────────────────────────────┼───────────────────────────┼─────────┤ +│ 1997 │ Ford │ E350 │ ac, abs, moon │ 3000.00 │ +│ 1999 │ Chevy │ Venture "Extended Edition" │ │ 4900.00 │ +│ 1999 │ Chevy │ Venture "Extended Edition, Large" │ │ 5000.00 │ +│ 1996 │ Jeep │ Grand Cherokee │ MUST SELL! air, moon roof │ 4799.00 │ +└──────┴───────┴───────────────────────────────────┴───────────────────────────┴─────────┘ + +$ head /etc/passwd | csview -H -d: +┌────────────────────────┬───┬───────┬───────┬────────────────────────────┬─────────────────┐ +│ root │ x │ 0 │ 0 │ │ /root │ +│ bin │ x │ 1 │ 1 │ │ / │ +│ daemon │ x │ 2 │ 2 │ │ / │ +│ mail │ x │ 8 │ 12 │ │ /var/spool/mail │ +│ ftp │ x │ 14 │ 11 │ │ /srv/ftp │ +│ http │ x │ 33 │ 33 │ │ /srv/http │ +│ nobody │ x │ 65534 │ 65534 │ Nobody │ / │ +│ dbus │ x │ 81 │ 81 │ System Message Bus │ / │ +│ systemd-journal-remote │ x │ 981 │ 981 │ systemd Journal Remote │ / │ +│ systemd-network │ x │ 980 │ 980 │ systemd Network Management │ / │ +└────────────────────────┴───┴───────┴───────┴────────────────────────────┴─────────────────┘ +``` + +Run `csview --help` to view detailed usage. + +### Installation + +#### On Arch Linux + +`csview` is available in the Arch User Repository. To install it from [AUR](https://aur.archlinux.org/packages/csview): + +``` +yay -S csview +``` + +#### On macOS + +You can install `csview` with Homebrew: + +``` +brew install csview +``` + +#### On NetBSD + +`csview` is available from the main pkgsrc Repositories. To install simply run + +``` +pkgin install csview +``` + +or, if you prefer to build from source using [pkgsrc](https://pkgsrc.se/textproc/csview) on any of the supported platforms: + +``` +cd /usr/pkgsrc/textproc/csview +make install +``` + +#### On Windows + +You can install `csview` with [Scoop](https://scoop.sh/): +``` +scoop install csview +``` + +#### From binaries + +Pre-built versions of `csview` for various architectures are available at [Github release page](https://github.com/wfxr/csview/releases). + +*Note that you can try the `musl` version (which is statically-linked) if runs into dependency related errors.* + +#### From source + +`csview` is also published on [crates.io](https://crates.io). If you have latest Rust toolchains installed you can use `cargo` to install it from source: + +``` +cargo install --locked csview +``` + +If you want the latest version, clone this repository and run `cargo build --release`. + +### Benchmark + +- [small.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-action-csv) (10 rows, 4 cols, 695 bytes): + +| Tool | Command | Mean Time | Min Time | Memory | +|:----------------------------------------------------------------------------------------:|---------------------------|----------:|----------:|----------:| +| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table small.csv` | 2.0ms | 1.8ms | 3.9mb | +| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview small.csv` | **0.3ms** | **0.1ms** | **2.4mb** | +| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 1.3ms | 1.1ms | **2.4mb** | +| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook small.csv` | 148.1ms | 142.4ms | 27.3mb | + +- [medium.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-sample-csv) (10,000 rows, 10 cols, 624K bytes): + +| Tool | Command | Mean Time | Min Time | Memory | +|:----------------------------------------------------------------------------------------:|---------------------------|-----------:|-----------:|----------:| +| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table medium.csv` | 0.031s | 0.029s | 4.4mb | +| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview medium.csv` | **0.017s** | **0.016s** | **2.8mb** | +| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 0.052s | 0.050s | 9.9mb | +| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook medium.csv` | 2.664s | 2.617s | 46.8mb | + +- `large.csv` (1,000,000 rows, 10 cols, 61M bytes, generated by concatenating [medium.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-sample-csv) 100 times): + +| Tool | Command | Mean Time | Min Time | Memory | +|:----------------------------------------------------------------------------------------:|---------------------------|-----------:|-----------:|----------:| +| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table large.csv` | 2.912s | 2.820s | 4.4mb | +| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview large.csv` | **1.686s** | **1.665s** | **2.8mb** | +| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 5.777s | 5.759s | 767.6mb | +| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook large.csv` | 20.665s | 20.549s | 1105.7mb | + +### F.A.Q. + +--- +#### We already have [xsv](https://github.com/BurntSushi/xsv), why not contribute to it but build a new tool? + +`xsv` is great. But it's aimed for analyzing and manipulating csv data. +`csview` is designed for formatting and viewing. See also: [xsv/issues/156](https://github.com/BurntSushi/xsv/issues/156) + +--- +#### I encountered UTF-8 related errors, how to solve it? + +The file may use a non-UTF8 encoding. You can check the file encoding using `file` command: + +``` +$ file -i a.csv +a.csv: application/csv; charset=iso-8859-1 +``` +And then convert it to `utf8`: + +``` +$ iconv -f iso-8859-1 -t UTF8//TRANSLIT a.csv -o b.csv +$ csview b.csv +``` + +Or do it in place: + +``` +$ iconv -f iso-8859-1 -t UTF8//TRANSLIT a.csv | csview +``` + +### Credits + +* [csv-rust](https://github.com/BurntSushi/rust-csv) +* [prettytable-rs](https://github.com/phsym/prettytable-rs) +* [structopt](https://github.com/TeXitoi/structopt) + +### License + +`csview` is distributed under the terms of both the MIT License and the Apache License 2.0. + +See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details. diff --git a/userland/upstream-src/csview/build.rs b/userland/upstream-src/csview/build.rs new file mode 100644 index 0000000000..7436e59460 --- /dev/null +++ b/userland/upstream-src/csview/build.rs @@ -0,0 +1,20 @@ +use clap::CommandFactory; +use clap_complete::Shell; +use std::{fs, path::Path}; + +include!("src/cli.rs"); + +fn main() -> Result<(), Box> { + let outdir = std::env::var_os("SHELL_COMPLETIONS_DIR") + .or_else(|| std::env::var_os("OUT_DIR")) + .expect("OUT_DIR not found"); + let outdir_path = Path::new(&outdir); + let app = &mut App::command(); + + for shell in Shell::value_variants() { + let dir = outdir_path.join(shell.to_string()); + fs::create_dir_all(&dir)?; + clap_complete::generate_to(*shell, app, app.get_name().to_string(), &dir)?; + } + Ok(()) +} diff --git a/userland/upstream-src/csview/cliff.toml b/userland/upstream-src/csview/cliff.toml new file mode 100644 index 0000000000..2809a1c8a3 --- /dev/null +++ b/userland/upstream-src/csview/cliff.toml @@ -0,0 +1,107 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% macro print_commit(commit) -%} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }} - \ + ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ +{% endmacro -%} + +{% if version %}\ + {% if previous.version %}\ + ## [{{ version | trim_start_matches(pat="v") }}]\ + ({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }}) + {% else %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} + {% endif %}\ +{% else %}\ + ## [unreleased] +{% endif %}\ + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits + | filter(attribute="scope") + | sort(attribute="scope") %} + {{ self::print_commit(commit=commit) }} + {%- endfor -%} + {% raw %}\n{% endraw %}\ + {%- for commit in commits %} + {%- if not commit.scope -%} + {{ self::print_commit(commit=commit) }} + {% endif -%} + {% endfor -%} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true +# postprocessors +postprocessors = [ + { pattern = '', replace = "https://github.com/wfxr/csview" }, # replace repository URL +] + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + { pattern = ' (#[0-9]+)', replace = '(${1})' }, + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "🚀 Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor\\(clippy\\)", skip = true }, + { message = "^refactor", group = "🚜 Refactor" }, + { message = "^style", group = "🎨 Styling" }, + { message = "^test", group = "🧪 Testing" }, + { message = "^chore: [rR]elease", skip = true }, + { message = "^\\(cargo-release\\)", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for matching git tags +tag_pattern = "v[0-9].*" +# regex for skipping tags +skip_tags = "beta|alpha" +# regex for ignoring tags +ignore_tags = "rc|v2.1.0|v2.1.1" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "newest" diff --git a/userland/upstream-src/csview/release.toml b/userland/upstream-src/csview/release.toml new file mode 100644 index 0000000000..c1e02354b0 --- /dev/null +++ b/userland/upstream-src/csview/release.toml @@ -0,0 +1,4 @@ +allow-branch = ["master"] +pre-release-hook = ["sh", "-c", "git cliff -o CHANGELOG.md --tag {{version}} && git add CHANGELOG.md"] +pre-release-commit-message = "chore: release {{crate_name}} version {{version}}" +tag-message = "chore: release {{crate_name}} version {{version}}" diff --git a/userland/upstream-src/csview/rust-toolchain.toml b/userland/upstream-src/csview/rust-toolchain.toml new file mode 100644 index 0000000000..3fe7418d20 --- /dev/null +++ b/userland/upstream-src/csview/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy", "rust-analyzer"] diff --git a/userland/upstream-src/csview/rustfmt.toml b/userland/upstream-src/csview/rustfmt.toml new file mode 100644 index 0000000000..0bcc3d2515 --- /dev/null +++ b/userland/upstream-src/csview/rustfmt.toml @@ -0,0 +1,13 @@ +edition = "2021" +max_width = 120 +reorder_imports = true +struct_lit_width = 60 +struct_variant_width = 60 + +# struct_field_align_threshold = 40 +# comment_width = 120 +# fn_single_line = false +# imports_layout = "HorizontalVertical" +# match_arm_blocks = false +# overflow_delimited_expr = true +# imports_granularity = "Crate" diff --git a/userland/upstream-src/csview/src/cli.rs b/userland/upstream-src/csview/src/cli.rs new file mode 100644 index 0000000000..95113ccba0 --- /dev/null +++ b/userland/upstream-src/csview/src/cli.rs @@ -0,0 +1,92 @@ +use std::path::PathBuf; + +use clap::ValueHint; +use clap::{ + builder::{ + styling::{AnsiColor, Effects}, + Styles, + }, + Parser, ValueEnum, +}; + +#[derive(Parser)] +#[clap(about, version)] +#[clap(disable_help_subcommand = true)] +#[clap(next_line_help = true)] +#[clap( + styles(Styles::styled() + .header(AnsiColor::Yellow.on_default() | Effects::BOLD) + .usage(AnsiColor::Yellow.on_default() | Effects::BOLD) + .literal(AnsiColor::Green.on_default() | Effects::BOLD) + .placeholder(AnsiColor::Cyan.on_default()) + ) +)] +pub struct App { + /// File to view. + #[arg(name = "FILE", value_hint = ValueHint::FilePath)] + pub file: Option, + + /// Specify that the input has no header row. + #[arg(short = 'H', long = "no-headers")] + pub no_headers: bool, + + /// Prepend a column of line numbers to the table. + #[arg(short, long, alias = "seq")] + pub number: bool, + + /// Use '\t' as delimiter for tsv. + #[arg(short, long, conflicts_with = "delimiter")] + pub tsv: bool, + + /// Specify the field delimiter. + #[arg(short, long, default_value_t = ',')] + pub delimiter: char, + + /// Specify the border style. + #[arg(short, long, value_enum, default_value_t = TableStyle::Sharp, ignore_case = true)] + pub style: TableStyle, + + /// Specify padding for table cell. + #[arg(short, long, default_value_t = 1)] + pub padding: usize, + + /// Specify global indent for table. + #[arg(short, long, default_value_t = 0)] + pub indent: usize, + + /// Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit. + #[arg(long, default_value_t = 1000, name = "LIMIT")] + pub sniff: usize, + + /// Specify the alignment of the table header. + #[arg(long, value_enum, default_value_t = Alignment::Center, ignore_case = true)] + pub header_align: Alignment, + + /// Specify the alignment of the table body. + #[arg(long, value_enum, default_value_t = Alignment::Left, ignore_case = true)] + pub body_align: Alignment, + + #[cfg(all(feature = "pager", unix))] + /// Disable pager. + #[arg(long, short = 'P')] + pub disable_pager: bool, +} + +#[derive(Copy, Clone, ValueEnum)] +pub enum TableStyle { + None, + Ascii, + Ascii2, + Sharp, + Rounded, + Reinforced, + Markdown, + Grid, +} + +#[derive(Copy, Clone, ValueEnum)] +pub enum Alignment { + Left, + Center, + Right, +} diff --git a/userland/upstream-src/csview/src/main.rs b/userland/upstream-src/csview/src/main.rs new file mode 100644 index 0000000000..8251e180d1 --- /dev/null +++ b/userland/upstream-src/csview/src/main.rs @@ -0,0 +1,102 @@ +mod cli; +mod table; +mod util; + +use anyhow::bail; +use clap::Parser; +use cli::App; +use csv::{ErrorKind, ReaderBuilder}; +use std::{ + fs::File, + io::{self, BufWriter, IsTerminal, Read}, + process, +}; +use table::TablePrinter; +use util::table_style; + +#[cfg(all(feature = "pager", unix))] +use pager::Pager; + +fn main() { + if let Err(e) = try_main() { + if let Some(ioerr) = e.root_cause().downcast_ref::() { + if ioerr.kind() == io::ErrorKind::BrokenPipe { + process::exit(exitcode::OK); + } + } + + if let Some(csverr) = e.root_cause().downcast_ref::() { + match csverr.kind() { + ErrorKind::Utf8 { .. } => { + eprintln!("[error] input is not utf8 encoded"); + process::exit(exitcode::DATAERR) + } + ErrorKind::UnequalLengths { pos, expected_len, len } => { + let pos_info = pos + .as_ref() + .map(|p| format!(" at (byte: {}, line: {}, record: {})", p.byte(), p.line(), p.record())) + .unwrap_or_else(|| "".to_string()); + eprintln!( + "[error] unequal lengths{}: expected length is {}, but got {}", + pos_info, expected_len, len + ); + process::exit(exitcode::DATAERR) + } + ErrorKind::Io(e) => { + eprintln!("[error] io error: {}", e); + process::exit(exitcode::IOERR) + } + e => { + eprintln!("[error] failed to process input: {:?}", e); + process::exit(exitcode::DATAERR) + } + } + } + + eprintln!("{}: {}", env!("CARGO_PKG_NAME"), e); + std::process::exit(1) + } +} + +fn try_main() -> anyhow::Result<()> { + let App { + file, + no_headers, + number, + tsv, + delimiter, + style, + padding, + indent, + sniff, + header_align, + body_align, + #[cfg(all(feature = "pager", unix))] + disable_pager, + } = App::parse(); + + #[cfg(all(feature = "pager", unix))] + if !disable_pager && io::stdout().is_terminal() { + match std::env::var("CSVIEW_PAGER") { + Ok(pager) => Pager::with_pager(&pager).setup(), + // XXX: the extra null byte can be removed once https://gitlab.com/imp/pager-rs/-/merge_requests/8 is merged + Err(_) => Pager::with_pager("less").pager_envs(["LESS=-SF\0"]).setup(), + } + } + + let stdout = io::stdout(); + let wtr = &mut BufWriter::new(stdout.lock()); + let rdr = ReaderBuilder::new() + .delimiter(if tsv { b'\t' } else { delimiter as u8 }) + .has_headers(!no_headers) + .from_reader(match file { + Some(path) => Box::new(File::open(path)?) as Box, + None if io::stdin().is_terminal() => bail!("no input file specified (use -h for help)"), + None => Box::new(io::stdin()), + }); + + let sniff = if sniff == 0 { usize::MAX } else { sniff }; + let table = TablePrinter::new(rdr, sniff, number)?; + table.writeln(wtr, &table_style(style, padding, indent, header_align, body_align))?; + Ok(()) +} diff --git a/userland/upstream-src/csview/src/table/mod.rs b/userland/upstream-src/csview/src/table/mod.rs new file mode 100644 index 0000000000..0a5513e81f --- /dev/null +++ b/userland/upstream-src/csview/src/table/mod.rs @@ -0,0 +1,6 @@ +mod printer; +mod row; +mod style; + +pub use printer::TablePrinter; +pub use style::{RowSep, Style, StyleBuilder}; diff --git a/userland/upstream-src/csview/src/table/printer.rs b/userland/upstream-src/csview/src/table/printer.rs new file mode 100644 index 0000000000..8c3e56a5a9 --- /dev/null +++ b/userland/upstream-src/csview/src/table/printer.rs @@ -0,0 +1,260 @@ +use super::{row::Row, style::Style}; +use csv::{Reader, StringRecord}; +use std::io::{self, Result, Write}; +use unicode_width::UnicodeWidthStr; + +pub struct TablePrinter { + header: Option, + widths: Vec, + records: Box>>, + with_seq: bool, +} + +impl TablePrinter { + pub(crate) fn new(mut rdr: Reader, sniff_rows: usize, with_seq: bool) -> Result { + let header = rdr.has_headers().then(|| rdr.headers()).transpose()?.cloned(); + let (widths, buf) = sniff_widths(&mut rdr, header.as_ref(), sniff_rows, with_seq)?; + let records = Box::new(buf.into_iter().map(Ok).chain(rdr.into_records())); + Ok(Self { header, widths, records, with_seq }) + } + + pub(crate) fn writeln(self, wtr: &mut W, fmt: &Style) -> Result<()> { + let widths = &self.widths; + fmt.rowseps + .top + .map(|sep| fmt.write_row_sep(wtr, widths, &sep)) + .transpose()?; + + let mut iter = self.records.peekable(); + + if let Some(header) = self.header { + let row: Row = self.with_seq.then_some("#").into_iter().chain(header.iter()).collect(); + row.writeln(wtr, fmt, widths, fmt.header_align)?; + if iter.peek().is_some() { + fmt.rowseps + .snd + .map(|sep| fmt.write_row_sep(wtr, widths, &sep)) + .transpose()?; + } + } + + let mut seq = 1; + while let Some(record) = iter.next().transpose()? { + let seq_str = self.with_seq.then(|| seq.to_string()); + let row: Row = seq_str.iter().map(|s| s.as_str()).chain(record.into_iter()).collect(); + row.writeln(wtr, fmt, widths, fmt.body_align)?; + if let Some(mid) = &fmt.rowseps.mid { + if iter.peek().is_some() { + fmt.write_row_sep(wtr, widths, mid)?; + } + } + seq += 1; + } + + fmt.rowseps + .bot + .map(|sep| fmt.write_row_sep(wtr, widths, &sep)) + .transpose()?; + + wtr.flush() + } +} + +fn sniff_widths( + rdr: &mut Reader, + header: Option<&StringRecord>, + sniff_rows: usize, + with_seq: bool, +) -> Result<(Vec, Vec)> { + let mut widths = Vec::new(); + let mut buf = Vec::new(); + + fn update_widths(record: &StringRecord, widths: &mut Vec) { + widths.resize(record.len(), 0); + record + .into_iter() + .map(UnicodeWidthStr::width_cjk) + .enumerate() + .for_each(|(i, width)| widths[i] = widths[i].max(width)) + } + + let mut record = header.cloned().unwrap_or_default(); + update_widths(&record, &mut widths); + + let mut seq = 1; + while seq <= sniff_rows && rdr.read_record(&mut record)? { + update_widths(&record, &mut widths); + seq += 1; + buf.push(record.clone()); + } + + if with_seq { + widths.insert(0, seq.to_string().width()); + } + Ok((widths, buf)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::table::{RowSep, StyleBuilder}; + use anyhow::Result; + use csv::ReaderBuilder; + + macro_rules! gen_table { + ($($line:expr)*) => { + concat!( + $($line, "\n",)* + ) + }; + } + + #[test] + fn test_write() -> Result<()> { + let text = "a,b,c\n1,2,3\n4,5,6"; + let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, false)?; + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &Style::default())?; + + assert_eq!( + gen_table!( + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| 4 | 5 | 6 |" + "+---+---+---+" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } + + #[test] + fn test_write_without_padding() -> Result<()> { + let text = "a,b,c\n1,2,3\n4,5,6"; + let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, false)?; + let fmt = StyleBuilder::default().padding(0).build(); + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &fmt)?; + + assert_eq!( + gen_table!( + "+-+-+-+" + "|a|b|c|" + "+-+-+-+" + "|1|2|3|" + "+-+-+-+" + "|4|5|6|" + "+-+-+-+" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } + + #[test] + fn test_write_with_indent() -> Result<()> { + let text = "a,b,c\n1,2,3\n4,5,6"; + let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, false)?; + let fmt = StyleBuilder::default().indent(4).build(); + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &fmt)?; + + assert_eq!( + gen_table!( + " +---+---+---+" + " | a | b | c |" + " +---+---+---+" + " | 1 | 2 | 3 |" + " +---+---+---+" + " | 4 | 5 | 6 |" + " +---+---+---+" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } + + #[test] + fn test_only_header() -> Result<()> { + let text = "a,ab,abc"; + let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, false)?; + let fmt = Style::default(); + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &fmt)?; + + assert_eq!( + gen_table!( + "+---+----+-----+" + "| a | ab | abc |" + "+---+----+-----+" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } + + #[test] + fn test_without_header() -> Result<()> { + let text = "1,123,35\n383,2, 17"; + let rdr = ReaderBuilder::new().has_headers(false).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, false)?; + let fmt = StyleBuilder::new() + .col_sep('│') + .row_seps( + RowSep::new('─', '╭', '┬', '╮'), + RowSep::new('─', '├', '┼', '┤'), + None, + RowSep::new('─', '╰', '┴', '╯'), + ) + .build(); + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &fmt)?; + + assert_eq!( + gen_table!( + "╭─────┬─────┬─────╮" + "│ 1 │ 123 │ 35 │" + "│ 383 │ 2 │ 17 │" + "╰─────┴─────┴─────╯" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } + + #[test] + fn test_with_seq() -> Result<()> { + let text = "a,b,c\n1,2,3\n4,5,6"; + let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes()); + let wtr = TablePrinter::new(rdr, 3, true)?; + + let mut buf = Vec::new(); + wtr.writeln(&mut buf, &Style::default())?; + + assert_eq!( + gen_table!( + "+---+---+---+---+" + "| # | a | b | c |" + "+---+---+---+---+" + "| 1 | 1 | 2 | 3 |" + "+---+---+---+---+" + "| 2 | 4 | 5 | 6 |" + "+---+---+---+---+" + ), + std::str::from_utf8(&buf)? + ); + Ok(()) + } +} diff --git a/userland/upstream-src/csview/src/table/row.rs b/userland/upstream-src/csview/src/table/row.rs new file mode 100644 index 0000000000..024f384b6e --- /dev/null +++ b/userland/upstream-src/csview/src/table/row.rs @@ -0,0 +1,96 @@ +use std::io::{Result, Write}; + +use itertools::Itertools; +use unicode_truncate::{Alignment, UnicodeTruncateStr}; + +use crate::table::Style; + +/// Represent a table row made of cells +#[derive(Clone, Debug)] +pub struct Row<'a> { + cells: Vec<&'a str>, +} + +impl<'a> FromIterator<&'a str> for Row<'a> { + fn from_iter>(iter: I) -> Self { + Self { cells: iter.into_iter().collect() } + } +} + +impl Row<'_> { + pub fn write(&self, wtr: &mut T, fmt: &Style, widths: &[usize], align: Alignment) -> Result<()> { + let sep = fmt.colseps.mid.map(|c| c.to_string()).unwrap_or_default(); + write!(wtr, "{:indent$}", "", indent = fmt.indent)?; + fmt.colseps.lhs.map(|sep| fmt.write_col_sep(wtr, sep)).transpose()?; + Itertools::intersperse( + self.cells + .iter() + .zip(widths) + .map(|(cell, &width)| cell.unicode_pad(width, align, true)) + .map(|s| format!("{:pad$}{}{:pad$}", "", s, "", pad = fmt.padding)), + sep, + ) + .try_for_each(|s| write!(wtr, "{}", s))?; + fmt.colseps.rhs.map(|sep| fmt.write_col_sep(wtr, sep)).transpose()?; + Ok(()) + } + + pub fn writeln(&self, wtr: &mut T, fmt: &Style, widths: &[usize], align: Alignment) -> Result<()> { + self.write(wtr, fmt, widths, align).and_then(|_| writeln!(wtr)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use anyhow::Result; + use std::str; + + #[test] + fn write_ascii_row() -> Result<()> { + let row = Row::from_iter(["a", "b"]); + let buf = &mut Vec::new(); + let fmt = Style::default(); + let widths = [3, 4]; + + row.writeln(buf, &fmt, &widths, Alignment::Left)?; + assert_eq!("| a | b |\n", str::from_utf8(buf)?); + Ok(()) + } + + #[test] + fn write_cjk_row() -> Result<()> { + let row = Row::from_iter(["李磊(Jack)", "四川省成都市", "💍"]); + let buf = &mut Vec::new(); + let fmt = Style::default(); + let widths = [10, 8, 2]; + + row.writeln(buf, &fmt, &widths, Alignment::Left)?; + assert_eq!("| 李磊(Jack) | 四川省成 | 💍 |\n", str::from_utf8(buf)?); + Ok(()) + } + + #[test] + fn write_align_center() -> Result<()> { + let row = Row::from_iter(["a", "b"]); + let buf = &mut Vec::new(); + let fmt = Style::default(); + let widths = [3, 4]; + + row.writeln(buf, &fmt, &widths, Alignment::Center)?; + assert_eq!("| a | b |\n", str::from_utf8(buf)?); + Ok(()) + } + + #[test] + fn write_align_right() -> Result<()> { + let row = Row::from_iter(["a", "b"]); + let buf = &mut Vec::new(); + let fmt = Style::default(); + let widths = [3, 4]; + + row.writeln(buf, &fmt, &widths, Alignment::Right)?; + assert_eq!("| a | b |\n", str::from_utf8(buf)?); + Ok(()) + } +} diff --git a/userland/upstream-src/csview/src/table/style.rs b/userland/upstream-src/csview/src/table/style.rs new file mode 100644 index 0000000000..9481941b8d --- /dev/null +++ b/userland/upstream-src/csview/src/table/style.rs @@ -0,0 +1,277 @@ +use std::io::{Result, Write}; + +use unicode_truncate::Alignment; + +#[derive(Debug, Clone, Copy)] +pub struct RowSeps { + /// Top separator row (top border) + /// ``` + /// >┌───┬───┐ + /// │ a │ b │ + /// ``` + pub top: Option, + /// Second separator row (between the header row and the first data row) + /// ``` + /// ┌───┬───┐ + /// │ a │ b │ + /// >├───┼───┤ + /// ``` + pub snd: Option, + /// Middle separator row (between data rows) + /// ``` + /// >├───┼───┤ + /// │ 2 │ 2 │ + /// >├───┼───┤ + /// ``` + pub mid: Option, + /// Bottom separator row (bottom border) + /// ``` + /// │ 3 │ 3 │ + /// >└───┴───┘ + /// ``` + pub bot: Option, +} + +/// The characters used for printing a row separator +#[derive(Debug, Clone, Copy)] +pub struct RowSep { + /// Inner row separator + /// ``` + /// ┌───┬───┐ + /// ^ + /// ``` + inner: char, + /// Left junction separator + /// ``` + /// ┌───┬───┐ + /// ^ + /// ``` + ljunc: char, + /// Crossing junction separator + /// ``` + /// ┌───┬───┐ + /// ^ + /// ``` + cjunc: char, + /// Right junction separator + /// ``` + /// ┌───┬───┐ + /// ^ + /// ``` + rjunc: char, +} + +#[derive(Debug, Clone, Copy)] +pub struct ColSeps { + /// Left separator column (left border) + /// ``` + /// │ 1 │ 2 │ + /// ^ + /// ``` + pub lhs: Option, + /// Middle column separators + /// ``` + /// │ 1 │ 2 │ + /// ^ + /// ``` + pub mid: Option, + /// Right separator column (right border) + /// ``` + /// │ 1 │ 2 │ + /// ^ + /// ``` + pub rhs: Option, +} + +impl RowSep { + pub fn new(sep: char, ljunc: char, cjunc: char, rjunc: char) -> RowSep { + RowSep { inner: sep, ljunc, cjunc, rjunc } + } +} + +impl Default for RowSep { + fn default() -> Self { + RowSep::new('-', '+', '+', '+') + } +} + +impl Default for RowSeps { + fn default() -> Self { + Self { + top: Some(RowSep::default()), + snd: Some(RowSep::default()), + mid: Some(RowSep::default()), + bot: Some(RowSep::default()), + } + } +} + +impl Default for ColSeps { + fn default() -> Self { + Self { lhs: Some('|'), mid: Some('|'), rhs: Some('|') } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Style { + /// Column style + pub colseps: ColSeps, + + /// Row style + pub rowseps: RowSeps, + + /// Left and right padding + pub padding: usize, + + /// Global indentation + pub indent: usize, + + /// Header alignment + pub header_align: Alignment, + + /// Data alignment + pub body_align: Alignment, +} + +impl Default for Style { + fn default() -> Self { + Self { + indent: 0, + padding: 1, + colseps: ColSeps::default(), + rowseps: RowSeps::default(), + header_align: Alignment::Center, + body_align: Alignment::Left, + } + } +} + +impl Style { + pub fn write_row_sep(&self, wtr: &mut W, widths: &[usize], sep: &RowSep) -> Result<()> { + write!(wtr, "{:indent$}", "", indent = self.indent)?; + if self.colseps.lhs.is_some() { + write!(wtr, "{}", sep.ljunc)?; + } + let mut iter = widths.iter().peekable(); + while let Some(width) = iter.next() { + for _ in 0..width + self.padding * 2 { + write!(wtr, "{}", sep.inner)?; + } + if self.colseps.mid.is_some() && iter.peek().is_some() { + write!(wtr, "{}", sep.cjunc)?; + } + } + if self.colseps.rhs.is_some() { + write!(wtr, "{}", sep.rjunc)?; + } + writeln!(wtr) + } + + #[inline] + pub fn write_col_sep(&self, wtr: &mut W, sep: char) -> Result<()> { + write!(wtr, "{}", sep) + } +} + +#[derive(Default, Debug, Clone)] +pub struct StyleBuilder { + format: Box