From ebce574127dc52271bf5c211a8428c4b950db6b0 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Fri, 31 Jan 2025 20:32:24 -0500 Subject: [PATCH] 1-31-25 update --- flake.lock | 409 +++-- flake.nix | 9 +- hosts/desktop/config.nix | 31 +- hosts/desktop/home.nix | 2 +- modules/home/environment/stylixhome.nix | 1 + modules/home/environment/zsh/aliases.nix | 2 + modules/home/programs/kitty.nix | 3 + modules/home/programs/nixvim/plugins/lsp.nix | 7 + .../home/programs/nixvim/plugins/plugins.nix | 4 + .../programs/nixvim/plugins/rustaceanvim.nix | 3 +- overlay/overlay.nix | 4 +- overlay/pkgs/{rsh/rsh => ox}/Cargo.lock | 823 +++++++-- overlay/pkgs/ox/package.nix | 22 + overlay/pkgs/ox/update_ox.sh | 27 + overlay/pkgs/rsh/package.nix | 19 - overlay/pkgs/rsh/rsh/.envrc | 17 - overlay/pkgs/rsh/rsh/.gitignore | 26 - overlay/pkgs/rsh/rsh/Cargo.toml | 31 - overlay/pkgs/rsh/rsh/README.md | 1 - overlay/pkgs/rsh/rsh/TODO.md | 5 - overlay/pkgs/rsh/rsh/file.sh | 9 - overlay/pkgs/rsh/rsh/flake.lock | 933 ---------- overlay/pkgs/rsh/rsh/flake.nix | 47 - overlay/pkgs/rsh/rsh/ideas.md | 159 -- .../rsh/rsh/nix/devshell/flake-module.nix | 25 - .../rsh/rsh/nix/rust-overlay/flake-module.nix | 18 - overlay/pkgs/rsh/rsh/roadmap.md | 202 --- overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock | 46 - overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml | 11 - overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs | 53 - overlay/pkgs/rsh/rsh/rust-toolchain.toml | 2 - overlay/pkgs/rsh/rsh/src/builtin.rs | 716 -------- overlay/pkgs/rsh/rsh/src/comp.rs | 169 -- overlay/pkgs/rsh/rsh/src/event.rs | 522 ------ overlay/pkgs/rsh/rsh/src/execute.rs | 1115 ------------ overlay/pkgs/rsh/rsh/src/file.output | 7 - overlay/pkgs/rsh/rsh/src/file.rsh | 2 - overlay/pkgs/rsh/rsh/src/file.txt | 3 - overlay/pkgs/rsh/rsh/src/interp/debug.rs | 125 -- overlay/pkgs/rsh/rsh/src/interp/expand.rs | 639 ------- overlay/pkgs/rsh/rsh/src/interp/helper.rs | 252 --- overlay/pkgs/rsh/rsh/src/interp/mod.rs | 5 - overlay/pkgs/rsh/rsh/src/interp/parse.rs | 1567 ----------------- overlay/pkgs/rsh/rsh/src/interp/token.rs | 985 ----------- overlay/pkgs/rsh/rsh/src/main.rs | 168 -- overlay/pkgs/rsh/rsh/src/prompt.rs | 103 -- overlay/pkgs/rsh/rsh/src/shellenv.rs | 640 ------- overlay/pkgs/rsh/rsh/tests/basic_case.sh | 9 - overlay/pkgs/rsh/rsh/tests/basic_commands.sh | 4 - overlay/pkgs/rsh/rsh/tests/basic_for.sh | 4 - overlay/pkgs/rsh/rsh/tests/basic_if.sh | 12 - overlay/pkgs/rsh/rsh/tests/basic_until.sh | 6 - overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh | 3 - overlay/pkgs/rsh/rsh/tests/basic_while.sh | 6 - overlay/pkgs/rsh/rsh/tests/chain.sh | 3 - overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh | 3 - overlay/pkgs/rsh/rsh/tests/pipeline.sh | 2 - overlay/pkgs/rsh/rsh/tests/very_nested.sh | 2 - 58 files changed, 991 insertions(+), 9032 deletions(-) rename overlay/pkgs/{rsh/rsh => ox}/Cargo.lock (71%) create mode 100644 overlay/pkgs/ox/package.nix create mode 100755 overlay/pkgs/ox/update_ox.sh delete mode 100644 overlay/pkgs/rsh/package.nix delete mode 100644 overlay/pkgs/rsh/rsh/.envrc delete mode 100644 overlay/pkgs/rsh/rsh/.gitignore delete mode 100644 overlay/pkgs/rsh/rsh/Cargo.toml delete mode 100644 overlay/pkgs/rsh/rsh/README.md delete mode 100644 overlay/pkgs/rsh/rsh/TODO.md delete mode 100755 overlay/pkgs/rsh/rsh/file.sh delete mode 100644 overlay/pkgs/rsh/rsh/flake.lock delete mode 100644 overlay/pkgs/rsh/rsh/flake.nix delete mode 100644 overlay/pkgs/rsh/rsh/ideas.md delete mode 100644 overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix delete mode 100644 overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix delete mode 100644 overlay/pkgs/rsh/rsh/roadmap.md delete mode 100644 overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock delete mode 100644 overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml delete mode 100644 overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs delete mode 100644 overlay/pkgs/rsh/rsh/rust-toolchain.toml delete mode 100644 overlay/pkgs/rsh/rsh/src/builtin.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/comp.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/event.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/execute.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/file.output delete mode 100755 overlay/pkgs/rsh/rsh/src/file.rsh delete mode 100644 overlay/pkgs/rsh/rsh/src/file.txt delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/debug.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/expand.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/helper.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/mod.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/parse.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/interp/token.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/main.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/prompt.rs delete mode 100644 overlay/pkgs/rsh/rsh/src/shellenv.rs delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_case.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_commands.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_for.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_if.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_until.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/basic_while.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/chain.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/pipeline.sh delete mode 100755 overlay/pkgs/rsh/rsh/tests/very_nested.sh diff --git a/flake.lock b/flake.lock index 929aafb..e7fbf20 100755 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ ] }, "locked": { - "lastModified": 1736102453, - "narHash": "sha256-5qb4kb7Xbt8jJFL/oDqOor9Z2+E+A+ql3PiyDvsfWZ0=", + "lastModified": 1737636397, + "narHash": "sha256-F5MbBj3QVorycVSFE9qjuOTLtIQBqt2VWbXa0uwzm98=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "4846091641f3be0ad7542086d52769bb7932bde6", + "rev": "7fe006981fae53e931f513026fc754e322f13145", "type": "github" }, "original": { @@ -70,11 +70,11 @@ "base16-helix": { "flake": false, "locked": { - "lastModified": 1725860795, - "narHash": "sha256-Z2o8VBPW3I+KKTSfe25kskz0EUj7MpUh8u355Z1nVsU=", + "lastModified": 1736852337, + "narHash": "sha256-esD42YdgLlEh7koBrSqcT7p2fsMctPAcGl/+2sYJa2o=", "owner": "tinted-theming", "repo": "base16-helix", - "rev": "7f795bf75d38e0eea9fed287264067ca187b88a9", + "rev": "03860521c40b0b9c04818f2218d9cc9efc21e7a5", "type": "github" }, "original": { @@ -86,11 +86,11 @@ "base16-vim": { "flake": false, "locked": { - "lastModified": 1731949548, - "narHash": "sha256-XIDexXM66sSh5j/x70e054BnUsviibUShW7XhbDGhYo=", + "lastModified": 1735953590, + "narHash": "sha256-YbQwaApLFJobn/0lbpMKcJ8N5axKlW2QIGkDS5+xoSU=", "owner": "tinted-theming", "repo": "base16-vim", - "rev": "61165b1632409bd55e530f3dbdd4477f011cadc6", + "rev": "c2a1232aa2c0ed27dcbf005779bcfe0e0ab5e85d", "type": "github" }, "original": { @@ -125,11 +125,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1736165297, - "narHash": "sha256-OT+sF4eNDFN/OdyUfIQwyp28+CFQL7PAdWn0wGU7F0U=", + "lastModified": 1737038063, + "narHash": "sha256-rMEuiK69MDhjz1JgbaeQ9mBDXMJ2/P8vmOYRbFndXsk=", "owner": "nix-community", "repo": "disko", - "rev": "76816af65d5294761636a838917e335992a52e0c", + "rev": "bf0abfde48f469c256f2b0f481c6281ff04a5db2", "type": "github" }, "original": { @@ -138,6 +138,22 @@ "type": "github" } }, + "firefox-gnome-theme": { + "flake": false, + "locked": { + "lastModified": 1736899990, + "narHash": "sha256-S79Hqn2EtSxU4kp99t8tRschSifWD4p/51++0xNWUxw=", + "owner": "rafaelmardojai", + "repo": "firefox-gnome-theme", + "rev": "91ca1f82d717b02ceb03a3f423cbe8082ebbb26d", + "type": "github" + }, + "original": { + "owner": "rafaelmardojai", + "repo": "firefox-gnome-theme", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -172,12 +188,12 @@ }, "flake-compat_3": { "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "revCount": 69, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" }, "original": { "type": "tarball", @@ -203,11 +219,11 @@ "flake-compat_5": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "type": "github" }, "original": { @@ -318,11 +334,11 @@ "zig": "zig" }, "locked": { - "lastModified": 1736186781, - "narHash": "sha256-jiKBu/hVN6bKxShMCFS4RHXUQkRfmiuRYZXNc4iAiIc=", + "lastModified": 1737779447, + "narHash": "sha256-+iVqQXAUJshUYgxOHfke54Ux4f/aggl1yub86KNx2tE=", "owner": "ghostty-org", "repo": "ghostty", - "rev": "037de64ea2c3f6201948236559524986f41a72f7", + "rev": "71e62f96fa4d286eda835048428d5be96e9f87c1", "type": "github" }, "original": { @@ -344,11 +360,11 @@ ] }, "locked": { - "lastModified": 1735882644, - "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", + "lastModified": 1737465171, + "narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", + "rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17", "type": "github" }, "original": { @@ -367,19 +383,14 @@ "nixpkgs": [ "stylix", "nixpkgs" - ], - "nixpkgs-stable": [ - "stylix", - "git-hooks", - "nixpkgs" ] }, "locked": { - "lastModified": 1731363552, - "narHash": "sha256-vFta1uHnD29VUY4HJOO/D6p6rxyObnf+InnSMT4jlMU=", + "lastModified": 1735882644, + "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "cd1af27aa85026ac759d5d3fccf650abe7e1bbf0", + "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", "type": "github" }, "original": { @@ -478,11 +489,11 @@ ] }, "locked": { - "lastModified": 1736089250, - "narHash": "sha256-/LPWMiiJGPHGd7ZYEgmbE2da4zvBW0acmshUjYC3WG4=", + "lastModified": 1737762889, + "narHash": "sha256-5HGG09bh/Yx0JA8wtBMAzt0HMCL1bYZ93x4IqzVExio=", "owner": "nix-community", "repo": "home-manager", - "rev": "172b91bfb2b7f5c4a8c6ceac29fd53a01ef07196", + "rev": "daf04c5950b676f47a794300657f1d3d14c1a120", "type": "github" }, "original": { @@ -499,11 +510,11 @@ ] }, "locked": { - "lastModified": 1736089250, - "narHash": "sha256-/LPWMiiJGPHGd7ZYEgmbE2da4zvBW0acmshUjYC3WG4=", + "lastModified": 1737762889, + "narHash": "sha256-5HGG09bh/Yx0JA8wtBMAzt0HMCL1bYZ93x4IqzVExio=", "owner": "nix-community", "repo": "home-manager", - "rev": "172b91bfb2b7f5c4a8c6ceac29fd53a01ef07196", + "rev": "daf04c5950b676f47a794300657f1d3d14c1a120", "type": "github" }, "original": { @@ -520,11 +531,11 @@ ] }, "locked": { - "lastModified": 1735774425, - "narHash": "sha256-C73gLFnEh8ZI0uDijUgCDWCd21T6I6tsaWgIBHcfAXg=", + "lastModified": 1736785676, + "narHash": "sha256-TY0jUwR3EW0fnS0X5wXMAVy6h4Z7Y6a3m+Yq++C9AyE=", "owner": "nix-community", "repo": "home-manager", - "rev": "5f6aa268e419d053c3d5025da740e390b12ac936", + "rev": "fc52a210b60f2f52c74eac41a8647c1573d2071d", "type": "github" }, "original": { @@ -538,11 +549,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1733056338, - "narHash": "sha256-sp14z0mrqrtmouz1+bU4Jh8/0xi+xwQHF2l7mhGSSVU=", + "lastModified": 1737834765, + "narHash": "sha256-xiThQfn7aCcV8+aQ8qdIornefqRATY6/H+3qqyiiUbQ=", "owner": "hyprwm", "repo": "contrib", - "rev": "d7c55140f1785b8d9fef351f1cd2a4c9e1eaa466", + "rev": "296740785d25b5fe19cce4978050dc5dc704876a", "type": "github" }, "original": { @@ -567,11 +578,11 @@ ] }, "locked": { - "lastModified": 1734906540, - "narHash": "sha256-vQ/L9hZFezC0LquLo4TWXkyniWtYBlFHAKIsDc7PYJE=", + "lastModified": 1737634937, + "narHash": "sha256-Ffw4ujFpi++6pPHe+gCBOfDgAoNlzVPZN6MReC1beu8=", "owner": "hyprwm", "repo": "hyprcursor", - "rev": "69270ba8f057d55b0e6c2dca0e165d652856e613", + "rev": "9c5dd1f7c825ee47f72727ad0a4e16ca46a2688e", "type": "github" }, "original": { @@ -596,11 +607,11 @@ ] }, "locked": { - "lastModified": 1736115290, - "narHash": "sha256-Jcn6yAzfUMcxy3tN/iZRbi/QgrYm7XLyVRl9g/nbUl4=", + "lastModified": 1737634889, + "narHash": "sha256-9JZE3KxcXOqZH9zs3UeadngDiK/yIACTiAR8HSA/TNI=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "52202272d89da32a9f866c0d10305a5e3d954c50", + "rev": "0d77b4895ad5f1bb3b0ee43103a5246c58b65591", "type": "github" }, "original": { @@ -616,8 +627,8 @@ "hyprgraphics": "hyprgraphics", "hyprland-protocols": "hyprland-protocols", "hyprland-qtutils": "hyprland-qtutils", - "hyprlang": "hyprlang", - "hyprutils": "hyprutils", + "hyprlang": "hyprlang_2", + "hyprutils": "hyprutils_2", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs_3", "pre-commit-hooks": "pre-commit-hooks", @@ -625,11 +636,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1736191857, - "narHash": "sha256-2ADoC5iTawhEmEuDrl5z+gLbLHEsYqdjwnlF5Y5MZmA=", + "lastModified": 1737925585, + "narHash": "sha256-Or8v5heNJvsGITHZDeJpXs/yQrFAx/wRhSMCB4iYlRI=", "ref": "refs/heads/main", - "rev": "b9f110ef8726fcba2b4ee69856027731e73003a5", - "revCount": 5638, + "rev": "2f55806d6f11a1e81e3e821cb0327779d5cc50e6", + "revCount": 5741, "submodules": true, "type": "git", "url": "https://github.com/hyprwm/Hyprland" @@ -652,11 +663,11 @@ ] }, "locked": { - "lastModified": 1735774328, - "narHash": "sha256-vIRwLS9w+N99EU1aJ+XNOU6mJTxrUBa31i1r82l0V7s=", + "lastModified": 1737556638, + "narHash": "sha256-laKgI3mr2qz6tas/q3tuGPxMdsGhBi/w+HO+hO2f1AY=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "e3b6af97ddcfaafbda8e2828c719a5af84f662cb", + "rev": "4c75dd5c015c8a0e5a34c6d02a018a650f57feb5", "type": "github" }, "original": { @@ -665,8 +676,37 @@ "type": "github" } }, + "hyprland-qt-support": { + "inputs": { + "hyprlang": "hyprlang", + "nixpkgs": [ + "hyprland", + "hyprland-qtutils", + "nixpkgs" + ], + "systems": [ + "hyprland", + "hyprland-qtutils", + "systems" + ] + }, + "locked": { + "lastModified": 1737634706, + "narHash": "sha256-nGCibkfsXz7ARx5R+SnisRtMq21IQIhazp6viBU8I/A=", + "owner": "hyprwm", + "repo": "hyprland-qt-support", + "rev": "8810df502cdee755993cb803eba7b23f189db795", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland-qt-support", + "type": "github" + } + }, "hyprland-qtutils": { "inputs": { + "hyprland-qt-support": "hyprland-qt-support", "hyprutils": [ "hyprland", "hyprutils" @@ -681,11 +721,11 @@ ] }, "locked": { - "lastModified": 1736114838, - "narHash": "sha256-FxbuGQExtN37ToWYnGmO6weOYN6WPHN/RAqbr7gNPek=", + "lastModified": 1737811848, + "narHash": "sha256-WZ7LeiKHk5Y94MU5gHIWn0r8asWxYOvie4LqfCjVIZU=", "owner": "hyprwm", "repo": "hyprland-qtutils", - "rev": "6997fe382dcf396704227d2b98ffdd5066da6959", + "rev": "9c0831ff98856c0f312fcb8b57553fbe3dd34d5b", "type": "github" }, "original": { @@ -695,6 +735,36 @@ } }, "hyprlang": { + "inputs": { + "hyprutils": "hyprutils", + "nixpkgs": [ + "hyprland", + "hyprland-qtutils", + "hyprland-qt-support", + "nixpkgs" + ], + "systems": [ + "hyprland", + "hyprland-qtutils", + "hyprland-qt-support", + "systems" + ] + }, + "locked": { + "lastModified": 1737634606, + "narHash": "sha256-W7W87Cv6wqZ9PHegI6rH1+ve3zJPiyevMFf0/HwdbCQ=", + "owner": "hyprwm", + "repo": "hyprlang", + "rev": "f41271d35cc0f370d300413d756c2677f386af9d", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprlang", + "type": "github" + } + }, + "hyprlang_2": { "inputs": { "hyprutils": [ "hyprland", @@ -710,11 +780,11 @@ ] }, "locked": { - "lastModified": 1735393019, - "narHash": "sha256-NPpqA8rtmDLsEmZOmz+qR67zsB6Y503Jnv+nSFLKJZ8=", + "lastModified": 1737634606, + "narHash": "sha256-W7W87Cv6wqZ9PHegI6rH1+ve3zJPiyevMFf0/HwdbCQ=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "55608efdaa387af7bfdc0eddb404c409958efa43", + "rev": "f41271d35cc0f370d300413d756c2677f386af9d", "type": "github" }, "original": { @@ -725,17 +795,17 @@ }, "hyprpicker": { "inputs": { - "hyprutils": "hyprutils_2", + "hyprutils": "hyprutils_3", "hyprwayland-scanner": "hyprwayland-scanner_2", "nixpkgs": "nixpkgs_4", "systems": "systems_3" }, "locked": { - "lastModified": 1735584197, - "narHash": "sha256-B1PqiHp/jmDVXVrvyh/eu2KP3LCyi1JL0h3vuy/wVnM=", + "lastModified": 1737635601, + "narHash": "sha256-/Jb/9HqC5Ou5JtsLHY2MJtj/c0aDG3kaeh4RLvc2X2U=", "owner": "hyprwm", "repo": "hyprpicker", - "rev": "444c40e5e3dc4058a6a762ba5e73ada6d6469055", + "rev": "c3777320b358bb28a0f2112441377fe452d77ea8", "type": "github" }, "original": { @@ -748,19 +818,25 @@ "inputs": { "nixpkgs": [ "hyprland", + "hyprland-qtutils", + "hyprland-qt-support", + "hyprlang", "nixpkgs" ], "systems": [ "hyprland", + "hyprland-qtutils", + "hyprland-qt-support", + "hyprlang", "systems" ] }, "locked": { - "lastModified": 1736164519, - "narHash": "sha256-1LimBKvDpBbeX+qW7T240WEyw+DBVpDotZB4JYm8Aps=", + "lastModified": 1737632363, + "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "3c895da64b0eb19870142196fa48c07090b441c4", + "rev": "006620eb29d54ea9086538891404c78563d1bae1", "type": "github" }, "original": { @@ -770,6 +846,31 @@ } }, "hyprutils_2": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1737725508, + "narHash": "sha256-jGmcPc6y/prg/4A8KGYqJ27nSPaProCMiFadaxNAKvA=", + "owner": "hyprwm", + "repo": "hyprutils", + "rev": "fb0c2d1de3d1ef7396d19c18ac09e12bd956929e", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprutils", + "type": "github" + } + }, + "hyprutils_3": { "inputs": { "nixpkgs": [ "hyprpicker", @@ -781,11 +882,11 @@ ] }, "locked": { - "lastModified": 1733502241, - "narHash": "sha256-KAUNC4Dgq8WQjYov5auBw/usaHixhacvb7cRDd0AG/k=", + "lastModified": 1737632363, + "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "104117aed6dd68561be38b50f218190aa47f2cd8", + "rev": "006620eb29d54ea9086538891404c78563d1bae1", "type": "github" }, "original": { @@ -831,11 +932,11 @@ ] }, "locked": { - "lastModified": 1726874836, - "narHash": "sha256-VKR0sf0PSNCB0wPHVKSAn41mCNVCnegWmgkrneKDhHM=", + "lastModified": 1735493474, + "narHash": "sha256-fktzv4NaqKm94VAkAoVqO/nqQlw+X0/tJJNAeCSfzK4=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "500c81a9e1a76760371049a8d99e008ea77aa59e", + "rev": "de913476b59ee88685fdc018e77b8f6637a2ae0b", "type": "github" }, "original": { @@ -846,11 +947,11 @@ }, "impermanence": { "locked": { - "lastModified": 1734945620, - "narHash": "sha256-olIfsfJK4/GFmPH8mXMmBDAkzVQ1TWJmeGT3wBGfQPY=", + "lastModified": 1737831083, + "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", "owner": "nix-community", "repo": "impermanence", - "rev": "d000479f4f41390ff7cf9204979660ad5dd16176", + "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", "type": "github" }, "original": { @@ -895,11 +996,11 @@ ] }, "locked": { - "lastModified": 1736085891, - "narHash": "sha256-bTl9fcUo767VaSx4Q5kFhwiDpFQhBKna7lNbGsqCQiA=", + "lastModified": 1737504076, + "narHash": "sha256-/B4XJnzYU/6K1ZZOBIgsa3K4pqDJrnC2579c44c+4rI=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "ba9b3173b0f642ada42b78fb9dfc37ca82266f6c", + "rev": "65cc1fa8e36ceff067daf6cfb142331f02f524d3", "type": "github" }, "original": { @@ -910,11 +1011,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1735915915, - "narHash": "sha256-Q4HuFAvoKAIiTRZTUxJ0ZXeTC7lLfC9/dggGHNXNlCw=", + "lastModified": 1736241350, + "narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a27871180d30ebee8aa6b11bf7fef8a52f024733", + "rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f", "type": "github" }, "original": { @@ -974,11 +1075,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1736012469, - "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", + "lastModified": 1737632463, + "narHash": "sha256-38J9QfeGSej341ouwzqf77WIHAScihAKCt8PQJ+NH28=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", + "rev": "0aa475546ed21629c4f5bbf90e38c846a99ec9e9", "type": "github" }, "original": { @@ -990,11 +1091,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1734119587, - "narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=", + "lastModified": 1737469691, + "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5", + "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", "type": "github" }, "original": { @@ -1006,11 +1107,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1736012469, - "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", + "lastModified": 1737885589, + "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", + "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", "type": "github" }, "original": { @@ -1022,11 +1123,27 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1735648875, - "narHash": "sha256-fQ4k/hyQiH9RRPznztsA9kbcDajvwV1sRm01el6Sr3c=", + "lastModified": 1737746512, + "narHash": "sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47e29c20abef74c45322eca25ca1550cdf5c3b50", + "rev": "825479c345a7f806485b7f00dbe3abb50641b083", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_7": { + "locked": { + "lastModified": 1736798957, + "narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3", "type": "github" }, "original": { @@ -1051,11 +1168,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1736157655, - "narHash": "sha256-/ggXMK8Q/rN94kaaSHPtEcf4SPKgPXfzSbDgAR6Odzs=", + "lastModified": 1737914312, + "narHash": "sha256-PBF4R+yQt5Sls7CsA9Miwx28XtOP/yqaqejZ3RKSes0=", "owner": "nix-community", "repo": "nixvim", - "rev": "31139e0605fd886d981e0a197e30ceac4b859d6e", + "rev": "8e5422bf3e76f410b97d2da640d0829e87657de9", "type": "github" }, "original": { @@ -1074,11 +1191,11 @@ ] }, "locked": { - "lastModified": 1735854821, - "narHash": "sha256-Iv59gMDZajNfezTO0Fw6LHE7uKAShxbvMidmZREit7c=", + "lastModified": 1737823349, + "narHash": "sha256-LAppb+sftyvJbPdrBG1uN9GYWHz6q7bUpkpDjljcSRo=", "owner": "NuschtOS", "repo": "search", - "rev": "836908e3bddd837ae0f13e215dd48767aee355f0", + "rev": "f91a0ac0f4ecf0ad1d1d88140f66520dae6ce4bd", "type": "github" }, "original": { @@ -1087,6 +1204,24 @@ "type": "github" } }, + "ox_flake": { + "inputs": { + "nixpkgs": "nixpkgs_6" + }, + "locked": { + "lastModified": 1737948235, + "narHash": "sha256-U60Ecr2Al4xQB7MFklj/LHkGjsJDK51kLtK926vuKzU=", + "owner": "pagedMov", + "repo": "ox", + "rev": "7e51932dc5e1acc2de9d941b29eb52945afb6dee", + "type": "github" + }, + "original": { + "owner": "pagedMov", + "repo": "ox", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat_2", @@ -1097,11 +1232,11 @@ ] }, "locked": { - "lastModified": 1735882644, - "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", + "lastModified": 1737465171, + "narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", + "rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17", "type": "github" }, "original": { @@ -1121,6 +1256,7 @@ "impermanence": "impermanence", "nixpkgs": "nixpkgs_5", "nixvim": "nixvim", + "ox_flake": "ox_flake", "spicetify-nix": "spicetify-nix", "stylix": "stylix" } @@ -1130,14 +1266,15 @@ "flake-compat": "flake-compat_4", "nixpkgs": [ "nixpkgs" - ] + ], + "systems": "systems_5" }, "locked": { - "lastModified": 1736136999, - "narHash": "sha256-bI3pSXx4fihlWNi4b8+Kp04RkDhctEgGdJuNQw/2O88=", + "lastModified": 1737943930, + "narHash": "sha256-Nwl2GgUjMHUK2faHKKwPsvViaKyS02e+MwCXFuC+WsM=", "owner": "gerg-l", "repo": "spicetify-nix", - "rev": "046dc4cc0bcf460cafbfd3fe26fdc584f5b0fb80", + "rev": "08b2afcc47281b4f8bacaf4303165be763f1ff51", "type": "github" }, "original": { @@ -1152,24 +1289,25 @@ "base16-fish": "base16-fish", "base16-helix": "base16-helix", "base16-vim": "base16-vim", + "firefox-gnome-theme": "firefox-gnome-theme", "flake-compat": "flake-compat_5", "flake-utils": "flake-utils_3", "git-hooks": "git-hooks_2", "gnome-shell": "gnome-shell", "home-manager": "home-manager_3", - "nixpkgs": "nixpkgs_6", - "systems": "systems_5", + "nixpkgs": "nixpkgs_7", + "systems": "systems_6", "tinted-foot": "tinted-foot", "tinted-kitty": "tinted-kitty", "tinted-tmux": "tinted-tmux", "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1736179037, - "narHash": "sha256-uhLRE3x5TrFeQm1waBz6ecqa2EHilFM4jLxYbOJPGrU=", + "lastModified": 1737930520, + "narHash": "sha256-CAgB9/o54SXzqWwypA+hL2ETxiHW92Y+Ou4fT581jdk=", "owner": "danth", "repo": "stylix", - "rev": "a6b53aa677ab9bd9098abbdc47924854c76c3eb1", + "rev": "6103431cd2f9d4352e5493a4063cf57e307d355c", "type": "github" }, "original": { @@ -1253,6 +1391,21 @@ "type": "github" } }, + "systems_6": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "tinted-foot": { "flake": false, "locked": { @@ -1290,11 +1443,11 @@ "tinted-tmux": { "flake": false, "locked": { - "lastModified": 1729501581, - "narHash": "sha256-1ohEFMC23elnl39kxWnjzH1l2DFWWx4DhFNNYDTYt54=", + "lastModified": 1735737224, + "narHash": "sha256-FO2hRBkZsjlIRqzNHCPc/52yxg11kHGA8MEtSun9RwE=", "owner": "tinted-theming", "repo": "tinted-tmux", - "rev": "f0e7f7974a6441033eb0a172a0342e96722b4f14", + "rev": "aead506a9930c717ebf81cc83a2126e9ca08fa64", "type": "github" }, "original": { @@ -1327,11 +1480,11 @@ ] }, "locked": { - "lastModified": 1736115332, - "narHash": "sha256-FBG9d7e0BTFfxVdw4b5EmNll2Mv7hfRc54hbB4LrKko=", + "lastModified": 1737483750, + "narHash": "sha256-5An1wq5U8sNycOBBg3nsDDgpwBmR9liOpDGlhliA6Xo=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "1788ca5acd4b542b923d4757d4cfe4183cc6a92d", + "rev": "f2cc121df15418d028a59c9737d38e3a90fbaf8f", "type": "github" }, "original": { @@ -1368,11 +1521,11 @@ ] }, "locked": { - "lastModified": 1734907020, - "narHash": "sha256-p6HxwpRKVl1KIiY5xrJdjcEeK3pbmc///UOyV6QER+w=", + "lastModified": 1737634991, + "narHash": "sha256-dBAnb7Kbnier30cA7AgxVSxxARmxKZ1vHZT33THSIr8=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "d7f18dda5e511749fa1511185db3536208fb1a63", + "rev": "e09dfe2726c8008f983e45a0aa1a3b7416aaeb8a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5e098e3..b0dc715 100755 --- a/flake.nix +++ b/flake.nix @@ -30,12 +30,14 @@ url = "github:gerg-l/spicetify-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + ox_flake.url = "github:pagedMov/ox"; }; - outputs = { self, home-manager, ghostty, disko, nixpkgs, impermanence, nixvim, stylix, ... }@inputs: + outputs = { self, home-manager, disko, ox_flake, nixpkgs, impermanence, nixvim, stylix, ... }@inputs: let system = "x86_64-linux"; username = "pagedmov"; + ox = ox_flake.packages.${system}.default; nixpkgsConfig = { allowUnfree = true; }; @@ -141,7 +143,7 @@ nixosConfigurations = { oganesson = nixpkgs.lib.nixosSystem { # Desktop specialArgs = { - inherit self inputs username; + inherit self inputs username ox; host = "oganesson"; overlays = [ (import ./overlay/overlay.nix { root = self; }) @@ -151,6 +153,9 @@ pkgs = import nixpkgs { inherit system; config = nixpkgsConfig; + overlays = [ + (import ./overlay/overlay.nix { root = self; }) + ]; }; modules = [ ./hosts/desktop/config.nix diff --git a/hosts/desktop/config.nix b/hosts/desktop/config.nix index 12302b0..66fb2c3 100755 --- a/hosts/desktop/config.nix +++ b/hosts/desktop/config.nix @@ -1,30 +1,5 @@ -{ pkgs, username, ... }: +{ ox, pkgs, username, ... }: -let rsh = pkgs.rustPlatform.buildRustPackage { - pname = "rsh"; - version = "0.0.1"; - buildType = "debug"; - - src = ../../overlay/pkgs/rsh/rsh; - - cargoHash = "sha256-8Lb7AohSah2A7pcoT2JPDgza0LfIyD897yj4QHNklDw="; - cargoLock = { - lockFile = ../../overlay/pkgs/rsh/rsh/Cargo.lock; - }; - - buildInputs = [ ]; - - meta = { - description = "Modern shell scripting"; - homepage = "https://github.com/pagedMov/rsh"; - license = pkgs.lib.licenses.gpl3; - maintainers = with pkgs.lib.maintainers; [ pagedMov ]; - }; - passthru = { - shellPath = "/bin/rsh"; - }; -}; -in { imports = [ ./hardware.nix ]; @@ -53,7 +28,7 @@ in environment = { variables = { PATH = "${pkgs.clang-tools}/bin:$PATH"; }; - shells = [ rsh pkgs.zsh pkgs.bash ]; + shells = [ pkgs.myPkgs.ox pkgs.zsh pkgs.bash ]; }; users = { @@ -63,7 +38,7 @@ in ${username} = { isNormalUser = true; initialPassword = "1234"; - shell = pkgs.elvish; + shell = pkgs.myPkgs.ox; extraGroups = [ "input" "wheel" "persist" "libvirtd" ]; }; }; diff --git a/hosts/desktop/home.nix b/hosts/desktop/home.nix index bc09974..77c49bf 100755 --- a/hosts/desktop/home.nix +++ b/hosts/desktop/home.nix @@ -21,7 +21,7 @@ stylixHomeConfig.enable = true; waybarConfig.enable = true; gtkConfig.enable = true; - spicetifyConfig.enable = true; + spicetifyConfig.enable = false; starshipConfig.enable = true; swayncConfig.enable = true; zshConfig = { diff --git a/modules/home/environment/stylixhome.nix b/modules/home/environment/stylixhome.nix index 42c2341..a90f6d5 100755 --- a/modules/home/environment/stylixhome.nix +++ b/modules/home/environment/stylixhome.nix @@ -19,6 +19,7 @@ in { opacity.terminal = 1.0; targets = { waybar.enable = false; + spicetify.enable = false; btop.enable = false; nixvim.enable = false; nixvim.transparentBackground = { diff --git a/modules/home/environment/zsh/aliases.nix b/modules/home/environment/zsh/aliases.nix index c68163e..a3e0c08 100644 --- a/modules/home/environment/zsh/aliases.nix +++ b/modules/home/environment/zsh/aliases.nix @@ -31,6 +31,8 @@ gpull = "gitpull_sfx"; greb = "gitrebase_sfx"; rsh = "$HOME/Coding/projects/rust/rsh/target/debug/rsh"; + vide = "neovide"; + pk9 = "pkill -9"; }; }; }; diff --git a/modules/home/programs/kitty.nix b/modules/home/programs/kitty.nix index 95c715e..b3c7116 100755 --- a/modules/home/programs/kitty.nix +++ b/modules/home/programs/kitty.nix @@ -34,6 +34,9 @@ "ctrl+shift+l" = "previous_tab"; "ctrl+shift+j" = "scroll_end"; "ctrl+shift+k" = "scroll_home"; + "ctrl+shift+equal" = "change_font_size all +2.0"; + "ctrl+shift+minus" = "change_font_size all -2.0"; + "ctrl+shift+backspace" = "change_font_size all 0"; }; }; }; diff --git a/modules/home/programs/nixvim/plugins/lsp.nix b/modules/home/programs/nixvim/plugins/lsp.nix index 6596280..37bdfe0 100755 --- a/modules/home/programs/nixvim/plugins/lsp.nix +++ b/modules/home/programs/nixvim/plugins/lsp.nix @@ -48,10 +48,17 @@ ccls.enable = true; clangd.enable = true; cmake.enable = true; + ts_ls.enable = true; + eslint.enable = true; html.enable = true; jsonls.enable = true; lua_ls.enable = true; marksman.enable = true; + rust_analyzer = { + enable = true; + installCargo = false; + installRustc = false; + }; nixd = { enable = true; settings = { diff --git a/modules/home/programs/nixvim/plugins/plugins.nix b/modules/home/programs/nixvim/plugins/plugins.nix index 5d80c25..658ad32 100755 --- a/modules/home/programs/nixvim/plugins/plugins.nix +++ b/modules/home/programs/nixvim/plugins/plugins.nix @@ -5,6 +5,10 @@ enable = true; extensions.dap-ui.enable = true; }; + dap-lldb = { + enable = true; + autoLoad = true; + }; nix.enable = true; endwise.enable = true; undotree.enable = true; diff --git a/modules/home/programs/nixvim/plugins/rustaceanvim.nix b/modules/home/programs/nixvim/plugins/rustaceanvim.nix index f0f730a..56561db 100644 --- a/modules/home/programs/nixvim/plugins/rustaceanvim.nix +++ b/modules/home/programs/nixvim/plugins/rustaceanvim.nix @@ -2,9 +2,10 @@ { programs.nixvim.plugins.rustaceanvim = { - enable = true; + enable = false; settings = { server.auto_attach = true; + dap.adapter = false; }; }; } diff --git a/overlay/overlay.nix b/overlay/overlay.nix index 7224bbe..ed76378 100644 --- a/overlay/overlay.nix +++ b/overlay/overlay.nix @@ -20,9 +20,7 @@ in }); myPkgs = { # Packages that I've made - tinyfetch = super.callPackage ./tinyfetch/package.nix {}; - breezex-cursor = super.callPackage ./breezex-cursor/package.nix {}; - rsh = super.callPackage ./rsh/package.nix {}; + ox = super.callPackage ./pkgs/ox/package.nix {}; }; myScripts = { # Scripts written using pkgs.writeShellApplication diff --git a/overlay/pkgs/rsh/rsh/Cargo.lock b/overlay/pkgs/ox/Cargo.lock similarity index 71% rename from overlay/pkgs/rsh/rsh/Cargo.lock rename to overlay/pkgs/ox/Cargo.lock index c963753..6977ae0 100644 --- a/overlay/pkgs/rsh/rsh/Cargo.lock +++ b/overlay/pkgs/ox/Cargo.lock @@ -82,11 +82,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -96,6 +97,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.4.0", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -117,12 +127,31 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "beef" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -131,24 +160,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", @@ -175,9 +195,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -210,9 +230,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -220,9 +240,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -232,9 +252,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -257,12 +277,33 @@ dependencies = [ "error-code", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -331,7 +372,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "crossterm_winapi", "mio", "parking_lot", @@ -462,6 +503,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "endian-type" version = "0.1.2" @@ -474,7 +521,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ - "log", + "log 0.4.25", "regex", ] @@ -494,7 +541,7 @@ dependencies = [ "anstyle", "env_filter", "humantime", - "log", + "log 0.4.25", ] [[package]] @@ -503,17 +550,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[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.10" @@ -524,16 +560,6 @@ dependencies = [ "windows-sys 0.59.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 = "error-code" version = "3.3.1" @@ -557,6 +583,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures-core" version = "0.3.31" @@ -586,7 +618,7 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -597,9 +629,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "hashbrown" @@ -613,6 +645,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "home" version = "0.5.11" @@ -622,12 +660,37 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64", + "httparse", + "language-tags", + "log 0.3.9", + "mime", + "num_cpus", + "time 0.1.45", + "traitobject", + "typeable", + "unicase", + "url", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -658,34 +721,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "im" -version = "15.1.0" +name = "idna" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", + "matches", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] -name = "ioctl-sys" -version = "0.5.2" +name = "insta" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" +dependencies = [ + "console", + "linked-hash-map", + "once_cell", + "pin-project", + "similar", +] + +[[package]] +name = "iron" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" +dependencies = [ + "hyper", + "log 0.3.9", + "mime_guess", + "modifier", + "num_cpus", + "plugin", + "typemap", + "url", +] [[package]] name = "is_terminal_polyfill" @@ -694,15 +777,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "js-sys" -version = "0.3.76" +name = "itoa" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +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 = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.5.0" @@ -721,15 +816,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", ] [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -737,15 +838,41 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "autocfg", + "autocfg 1.4.0", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.25", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c9172cb4c2f6c52117e25570983edcbb322f130b1031ae5d5d6b1abe7eeb493" +dependencies = [ + "iron", + "log 0.3.9", + "time 0.1.45", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" @@ -754,10 +881,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "miniz_oxide" -version = "0.8.2" +name = "mime" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "mime_guess" +version = "1.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" +dependencies = [ + "mime", + "phf", + "phf_codegen", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -769,11 +917,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "log", - "wasi", + "log 0.4.25", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -800,7 +954,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "cfg_aliases", "libc", @@ -818,7 +972,17 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg", + "autocfg 1.4.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] @@ -836,6 +1000,34 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "ox" +version = "0.1.0" +dependencies = [ + "bincode", + "bitflags 2.8.0", + "chrono", + "crossterm", + "env_logger", + "glob", + "insta", + "lazy_static", + "libc", + "log 0.4.25", + "logger", + "nix 0.29.0", + "once_cell", + "rayon", + "regex", + "rustyline", + "serde", + "serde_json", + "signal-hook", + "skim", + "tokio", + "tokio-stream", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -860,21 +1052,84 @@ dependencies = [ ] [[package]] -name = "perf" -version = "0.0.2" +name = "percent-encoding" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68568360d0ba38ced2eb560de9b408fd2c95d1acd271ddb7690bbf2b9b03c739" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" dependencies = [ - "errno 0.2.8", - "ioctl-sys", - "libc", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared", + "rand 0.6.5", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", + "unicase", +] + +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" +dependencies = [ + "typemap", +] [[package]] name = "powerfmt" @@ -893,9 +1148,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -919,6 +1174,25 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -926,8 +1200,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", ] [[package]] @@ -937,9 +1221,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -950,12 +1249,65 @@ dependencies = [ ] [[package]] -name = "rand_xoshiro" -version = "0.6.0" +name = "rand_hc" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" dependencies = [ - "rand_core", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -978,13 +1330,22 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -995,7 +1356,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1027,31 +1388,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rsh" -version = "0.1.0" -dependencies = [ - "bitflags 2.6.0", - "chrono", - "crossterm", - "env_logger", - "glob", - "im", - "lazy_static", - "libc", - "log", - "nix 0.29.0", - "once_cell", - "perf", - "regex", - "rustyline", - "signal-hook", - "skim", - "thiserror 2.0.9", - "tokio", - "tokio-stream", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1060,12 +1396,12 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", - "errno 0.3.10", + "bitflags 2.8.0", + "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", @@ -1083,13 +1419,13 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "clipboard-win", "fd-lock", "home", "libc", - "log", + "log 0.4.25", "memchr", "nix 0.29.0", "radix_trie", @@ -1111,6 +1447,18 @@ dependencies = [ "syn", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1138,10 +1486,22 @@ dependencies = [ ] [[package]] -name = "shell-quote" -version = "0.7.1" +name = "serde_json" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae4c63bdcc11eea49b562941b914d5ac30d42cad982e3f6e846a513ee6a3ce7e" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shell-quote" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb502615975ae2365825521fa1529ca7648fd03ce0b0746604e0683856ecd7e4" dependencies = [ "bstr", ] @@ -1183,14 +1543,16 @@ dependencies = [ ] [[package]] -name = "sized-chunks" -version = "0.6.5" +name = "similar" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" [[package]] name = "skim" @@ -1209,14 +1571,14 @@ dependencies = [ "fuzzy-matcher", "indexmap", "lazy_static", - "log", + "log 0.4.25", "nix 0.29.0", - "rand", + "rand 0.8.5", "rayon", "regex", "shell-quote", "shlex", - "time", + "time 0.3.37", "timer", "tuikit", "unicode-width 0.2.0", @@ -1248,9 +1610,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.92" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1274,16 +1636,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" -dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl", ] [[package]] @@ -1297,17 +1650,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror-impl" -version = "2.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.8" @@ -1318,6 +1660,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.37" @@ -1347,10 +1700,25 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.42.0" +name = "tinyvec" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -1366,9 +1734,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -1400,6 +1768,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + [[package]] name = "tuikit" version = "0.5.0" @@ -1408,23 +1782,56 @@ checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8" dependencies = [ "bitflags 1.3.2", "lazy_static", - "log", + "log 0.4.25", "nix 0.24.3", "term", "unicode-width 0.1.14", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "typeable" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +dependencies = [ + "unsafe-any", +] + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-segmentation" @@ -1444,6 +1851,26 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" +dependencies = [ + "traitobject", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -1452,15 +1879,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.5" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "vte" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b" +checksum = "9a0b683b20ef64071ff03745b14391751f6beab06a54347885459b77a3f2caa5" dependencies = [ "arrayvec", "utf8parse", @@ -1477,6 +1904,12 @@ dependencies = [ "quote", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1485,23 +1918,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", - "log", + "log 0.4.25", "proc-macro2", "quote", "syn", @@ -1510,9 +1944,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1520,9 +1954,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -1533,9 +1967,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "which" diff --git a/overlay/pkgs/ox/package.nix b/overlay/pkgs/ox/package.nix new file mode 100644 index 0000000..43343ee --- /dev/null +++ b/overlay/pkgs/ox/package.nix @@ -0,0 +1,22 @@ +{ pkgs ? import {} }: + +pkgs.rustPlatform.buildRustPackage rec { + pname = "ox"; + version = "v0.3.0-alpha_16aa803"; + + src = pkgs.fetchFromGitHub { + owner = "pagedMov"; + repo = "ox"; + rev = "16aa803faad2db175298f75278947b91b1e91267"; + hash = "sha256-qhH6gPETDIMgRHtWPwmiwalsT1kAqx0ODtlWStVP1d0="; + }; + + + doCheck = false; # TODO: Find a way to make tests work + + cargoLock.lockFile = ./Cargo.lock; + + passthru = { + shellPath = "/bin/ox"; + }; + } diff --git a/overlay/pkgs/ox/update_ox.sh b/overlay/pkgs/ox/update_ox.sh new file mode 100755 index 0000000..f6feec9 --- /dev/null +++ b/overlay/pkgs/ox/update_ox.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +curl -O https://raw.githubusercontent.com/pagedMov/ox/refs/heads/master/Cargo.lock +NEW_SRC=$(fetchfromgh pagedMov/ox) + +REV_SHORT=$(git ls-remote git@github.com:pagedMov/ox.git HEAD | awk '{print substr($1, 1, 7)}') +CUR_TAG=$(git ls-remote --tags git@github.com:pagedMov/ox.git | tail -n 2 | head -n 1 | cut -f2 | cut -d'/' -f3) +NEW_VER="${CUR_TAG}_$REV_SHORT" + +cat < {} }: + +pkgs.rustPlatform.buildRustPackage rec { + pname = "ox"; + version = "$NEW_VER"; + + $NEW_SRC + + doCheck = false; # TODO: Find a way to make tests work + + cargoLock.lockFile = ./Cargo.lock; + + passthru = { + shellPath = "/bin/ox"; + }; + } +EOF diff --git a/overlay/pkgs/rsh/package.nix b/overlay/pkgs/rsh/package.nix deleted file mode 100644 index 8d8c09f..0000000 --- a/overlay/pkgs/rsh/package.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.rustPlatform.buildRustPackage { - pname = "rsh"; - version = "0.0.1"; - - src = ./rsh; - - cargoHash = "sha256-8Lb7AohSah2A7pcoT2JPDgza0LfIyD897yj4QHNklDw="; - - buildInputs = [ ]; - - meta = { - description = "Modern shell scripting"; - homepage = "https://github.com/pagedMov/rsh"; - license = pkgs.lib.licenses.gpl3; - maintainers = with pkgs.lib.maintainers; [ pagedMov ]; - }; -} diff --git a/overlay/pkgs/rsh/rsh/.envrc b/overlay/pkgs/rsh/rsh/.envrc deleted file mode 100644 index 412cbef..0000000 --- a/overlay/pkgs/rsh/rsh/.envrc +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# ^ make editor happy - -# -# Use https://direnv.net/ to automatically load the dev shell. -# - -if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then - source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" -fi - -watch_file nix/** -watch_file -- **/*.nix -# Adding files to git includes them in a flake -# But it is also a bit much reloading. -# watch_file .git/index .git/HEAD -use flake . --show-trace diff --git a/overlay/pkgs/rsh/rsh/.gitignore b/overlay/pkgs/rsh/rsh/.gitignore deleted file mode 100644 index d2026e5..0000000 --- a/overlay/pkgs/rsh/rsh/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -target -**/*.rs.bk -.idea -*.iml -/result* -*.log -*~ - -# cachix tmp file -store-path-pre-build - -# Devenv -.devenv* -devenv.local.nix - -# direnv -.direnv - -# pre-commit -.pre-commit-config.yaml - -template/flake.lock -ideas.md -roadmap.md -README.md -file.* diff --git a/overlay/pkgs/rsh/rsh/Cargo.toml b/overlay/pkgs/rsh/rsh/Cargo.toml deleted file mode 100644 index f347fe5..0000000 --- a/overlay/pkgs/rsh/rsh/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "rsh" -description = "A linux shell written in rust" -publish = false -version = "0.1.0" - -edition = "2021" - -[profile.release] -debug = true - -[dependencies] -regex = "1.11.1" -log = "0.4.22" -tokio = { version = "1.41.1", features = ["full"] } -env_logger = "0.11.5" -thiserror = "2.0.3" -tokio-stream = { version = "0.1.16", features = ["full"] } -lazy_static = "1.5.0" -nix = { version = "0.29.0", features = ["user", "hostname", "fs", "default", "signal", "process", "event", "ioctl"] } -libc = "0.2.167" -once_cell = "1.20.2" -glob = "0.3.1" -bitflags = "2.6.0" -im = "15.1.0" -rustyline = { version = "15.0.0", features = [ "derive" ] } -chrono = "0.4.39" -crossterm = "0.28.1" -skim = "0.15.7" -perf = "0.0.2" -signal-hook = "0.3.17" diff --git a/overlay/pkgs/rsh/rsh/README.md b/overlay/pkgs/rsh/rsh/README.md deleted file mode 100644 index b878f8c..0000000 --- a/overlay/pkgs/rsh/rsh/README.md +++ /dev/null @@ -1 +0,0 @@ -A shell program written in rust. Currently a very experimental program, though approaching usability. Uses a custom tokenizer/parser with syntax based on bash/sh. diff --git a/overlay/pkgs/rsh/rsh/TODO.md b/overlay/pkgs/rsh/rsh/TODO.md deleted file mode 100644 index c96b81e..0000000 --- a/overlay/pkgs/rsh/rsh/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -## Stuff that needs to be done - -* rework variable substitution; the logic is too flimsy in it's current state. It currently works with raw strings rather than tokens which makes the logic brittle and largely incompatible with large swathes of the code-base. The hacky implementation of "$@" is a good example of this. - -* Implement behavior for shell opt flags diff --git a/overlay/pkgs/rsh/rsh/file.sh b/overlay/pkgs/rsh/rsh/file.sh deleted file mode 100755 index bf3c670..0000000 --- a/overlay/pkgs/rsh/rsh/file.sh +++ /dev/null @@ -1,9 +0,0 @@ -(#!python - -import sys - -word1 = sys.argv[1] -word2 = sys.argv[2] - -print(word1,word2) -) diff --git a/overlay/pkgs/rsh/rsh/flake.lock b/overlay/pkgs/rsh/rsh/flake.lock deleted file mode 100644 index e752871..0000000 --- a/overlay/pkgs/rsh/rsh/flake.lock +++ /dev/null @@ -1,933 +0,0 @@ -{ - "nodes": { - "cachix": { - "inputs": { - "devenv": [ - "crate2nix" - ], - "flake-compat": [ - "crate2nix" - ], - "nixpkgs": "nixpkgs", - "pre-commit-hooks": [ - "crate2nix" - ] - }, - "locked": { - "lastModified": 1709700175, - "narHash": "sha256-A0/6ZjLmT9qdYzKHmevnEIC7G+GiZ4UCr8v0poRPzds=", - "owner": "cachix", - "repo": "cachix", - "rev": "be97b37989f11b724197b5f4c7ffd78f12c8c4bf", - "type": "github" - }, - "original": { - "owner": "cachix", - "ref": "latest", - "repo": "cachix", - "type": "github" - } - }, - "cachix_2": { - "inputs": { - "devenv": [ - "crate2nix", - "crate2nix_stable" - ], - "flake-compat": [ - "crate2nix", - "crate2nix_stable" - ], - "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": [ - "crate2nix", - "crate2nix_stable" - ] - }, - "locked": { - "lastModified": 1716549461, - "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=", - "owner": "cachix", - "repo": "cachix", - "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4", - "type": "github" - }, - "original": { - "owner": "cachix", - "ref": "latest", - "repo": "cachix", - "type": "github" - } - }, - "cachix_3": { - "inputs": { - "devenv": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable" - ], - "flake-compat": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable" - ], - "nixpkgs": "nixpkgs_3", - "pre-commit-hooks": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable" - ] - }, - "locked": { - "lastModified": 1716549461, - "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=", - "owner": "cachix", - "repo": "cachix", - "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4", - "type": "github" - }, - "original": { - "owner": "cachix", - "ref": "latest", - "repo": "cachix", - "type": "github" - } - }, - "crate2nix": { - "inputs": { - "cachix": "cachix", - "crate2nix_stable": "crate2nix_stable", - "devshell": "devshell_3", - "flake-compat": "flake-compat_3", - "flake-parts": "flake-parts_3", - "nix-test-runner": "nix-test-runner_3", - "nixpkgs": "nixpkgs_6", - "pre-commit-hooks": "pre-commit-hooks_3" - }, - "locked": { - "lastModified": 1734429562, - "narHash": "sha256-V2XNs3Ir8WXNHdocfzkR/fu0FzkZ9uTDJkVecxJrGmQ=", - "owner": "nix-community", - "repo": "crate2nix", - "rev": "8537c2d7cb623679aaeff62c4c4c43a91566ab09", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "crate2nix", - "type": "github" - } - }, - "crate2nix_stable": { - "inputs": { - "cachix": "cachix_2", - "crate2nix_stable": "crate2nix_stable_2", - "devshell": "devshell_2", - "flake-compat": "flake-compat_2", - "flake-parts": "flake-parts_2", - "nix-test-runner": "nix-test-runner_2", - "nixpkgs": "nixpkgs_5", - "pre-commit-hooks": "pre-commit-hooks_2" - }, - "locked": { - "lastModified": 1719760004, - "narHash": "sha256-esWhRnt7FhiYq0CcIxw9pvH+ybOQmWBfHYMtleaMhBE=", - "owner": "nix-community", - "repo": "crate2nix", - "rev": "1dee214bb20855fa3e1e7bb98d28922ddaff8c57", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "0.14.1", - "repo": "crate2nix", - "type": "github" - } - }, - "crate2nix_stable_2": { - "inputs": { - "cachix": "cachix_3", - "crate2nix_stable": "crate2nix_stable_3", - "devshell": "devshell", - "flake-compat": "flake-compat", - "flake-parts": "flake-parts", - "nix-test-runner": "nix-test-runner", - "nixpkgs": "nixpkgs_4", - "pre-commit-hooks": "pre-commit-hooks" - }, - "locked": { - "lastModified": 1712821484, - "narHash": "sha256-rGT3CW64cJS9nlnWPFWSc1iEa3dNZecVVuPVGzcsHe8=", - "owner": "nix-community", - "repo": "crate2nix", - "rev": "42883afcad3823fa5811e967fb7bff54bc3c9d6d", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "0.14.0", - "repo": "crate2nix", - "type": "github" - } - }, - "crate2nix_stable_3": { - "inputs": { - "flake-utils": "flake-utils" - }, - "locked": { - "lastModified": 1702842982, - "narHash": "sha256-A9AowkHIjsy1a4LuiPiVP88FMxyCWK41flZEZOUuwQM=", - "owner": "nix-community", - "repo": "crate2nix", - "rev": "75ac2973affa6b9b4f661a7b592cba6e4f51d426", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "0.12.0", - "repo": "crate2nix", - "type": "github" - } - }, - "devshell": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1717408969, - "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=", - "owner": "numtide", - "repo": "devshell", - "rev": "1ebbe68d57457c8cae98145410b164b5477761f4", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, - "devshell_2": { - "inputs": { - "flake-utils": "flake-utils_3", - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1717408969, - "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=", - "owner": "numtide", - "repo": "devshell", - "rev": "1ebbe68d57457c8cae98145410b164b5477761f4", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, - "devshell_3": { - "inputs": { - "flake-utils": "flake-utils_4", - "nixpkgs": [ - "crate2nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711099426, - "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", - "owner": "numtide", - "repo": "devshell", - "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, - "flake-compat": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-compat_2": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-compat_3": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1719745305, - "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-parts_2": { - "inputs": { - "nixpkgs-lib": [ - "crate2nix", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1719745305, - "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-parts_3": { - "inputs": { - "nixpkgs-lib": [ - "crate2nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1712014858, - "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_4": { - "inputs": { - "systems": "systems_4" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_5": { - "inputs": { - "systems": "systems_5" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_6": { - "inputs": { - "systems": "systems_6" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "gitignore_2": { - "inputs": { - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "gitignore_3": { - "inputs": { - "nixpkgs": [ - "crate2nix", - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "nix-test-runner": { - "flake": false, - "locked": { - "lastModified": 1588761593, - "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", - "owner": "stoeffel", - "repo": "nix-test-runner", - "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", - "type": "github" - }, - "original": { - "owner": "stoeffel", - "repo": "nix-test-runner", - "type": "github" - } - }, - "nix-test-runner_2": { - "flake": false, - "locked": { - "lastModified": 1588761593, - "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", - "owner": "stoeffel", - "repo": "nix-test-runner", - "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", - "type": "github" - }, - "original": { - "owner": "stoeffel", - "repo": "nix-test-runner", - "type": "github" - } - }, - "nix-test-runner_3": { - "flake": false, - "locked": { - "lastModified": 1588761593, - "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", - "owner": "stoeffel", - "repo": "nix-test-runner", - "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", - "type": "github" - }, - "original": { - "owner": "stoeffel", - "repo": "nix-test-runner", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1700612854, - "narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1715534503, - "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2057814051972fa1453ddfb0d98badbea9b83c06", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1715534503, - "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2057814051972fa1453ddfb0d98badbea9b83c06", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_4": { - "locked": { - "lastModified": 1719506693, - "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=", - "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source", - "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a", - "type": "path" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_5": { - "locked": { - "lastModified": 1719506693, - "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=", - "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source", - "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a", - "type": "path" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_6": { - "locked": { - "lastModified": 1712026416, - "narHash": "sha256-N/3VR/9e1NlN49p7kCiATiEY6Tzdo+CbrAG8kqCQKcI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "080a4a27f206d07724b88da096e27ef63401a504", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_7": { - "locked": { - "lastModified": 1735291276, - "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "634fd46801442d760e09493a794c4f15db2d0cbb", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_8": { - "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "flake-compat" - ], - "gitignore": "gitignore", - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "nixpkgs" - ], - "nixpkgs-stable": [ - "crate2nix", - "crate2nix_stable", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1719259945, - "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "pre-commit-hooks_2": { - "inputs": { - "flake-compat": [ - "crate2nix", - "crate2nix_stable", - "flake-compat" - ], - "gitignore": "gitignore_2", - "nixpkgs": [ - "crate2nix", - "crate2nix_stable", - "nixpkgs" - ], - "nixpkgs-stable": [ - "crate2nix", - "crate2nix_stable", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1719259945, - "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "pre-commit-hooks_3": { - "inputs": { - "flake-compat": [ - "crate2nix", - "flake-compat" - ], - "flake-utils": "flake-utils_5", - "gitignore": "gitignore_3", - "nixpkgs": [ - "crate2nix", - "nixpkgs" - ], - "nixpkgs-stable": [ - "crate2nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1712055707, - "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "root": { - "inputs": { - "crate2nix": "crate2nix", - "flake-utils": "flake-utils_6", - "nixpkgs": "nixpkgs_7", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": "nixpkgs_8" - }, - "locked": { - "lastModified": 1735352767, - "narHash": "sha256-3zXufMRWUdwmp8/BTmxVW/k4MyqsPjLnnt/IlQyZvhc=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "a16b9a7cac7f4d39a84234d62e91890370c57d76", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_4": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_5": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_6": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/overlay/pkgs/rsh/rsh/flake.nix b/overlay/pkgs/rsh/rsh/flake.nix deleted file mode 100644 index 440d75f..0000000 --- a/overlay/pkgs/rsh/rsh/flake.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - rust-overlay.url = "github:oxalica/rust-overlay"; - crate2nix.url = "github:nix-community/crate2nix"; - }; - outputs = { self, crate2nix, nixpkgs, flake-utils, rust-overlay }: - flake-utils.lib.eachDefaultSystem - (system: - let - overlays = [ (import rust-overlay) ]; - cargoNix = crate2nix.tools.${system}.appliedCargoNix { - name = "rsh"; - src = ./.; - }; - pkgs = import nixpkgs { - inherit system overlays; - }; - in - with pkgs; - rec { - checks = { - rustnix = cargoNix.rootCrate.build.override { - runTests = true; - }; - }; - packages = { - rustnix = cargoNix.rootCrate.build; - default = packages.rustnix; - inherit rust-toolchain; - }; - devShells.default = mkShell { - buildInputs = [ - rust-bin.nightly.latest.default - clang - llvm - libclang - pam - ]; - shellHook = '' - exec zsh - ''; - }; - } - ); -} diff --git a/overlay/pkgs/rsh/rsh/ideas.md b/overlay/pkgs/rsh/rsh/ideas.md deleted file mode 100644 index b1e1c6e..0000000 --- a/overlay/pkgs/rsh/rsh/ideas.md +++ /dev/null @@ -1,159 +0,0 @@ -Bash limitations: -1. Data structures - - Arrays are extremely clunky to make use of and have extremely specific syntax symantics in regards to indexing for instance - - Essentially no native support for dictionaries - - JSON parsing and manipulation requires tools like jq which themselves have their own syntax rules to learn. Requiring these external tools could be eliminated entirely by simply having support for JSON-like structures - -2. Error handling - - Error handling in bash is another pain point for the language. - - Propagating errors is entirely manual and modern shells mainly use exit codes to communicate errors, which for codes beyond 0 and 1 are essentially meaningless to all but the most dedicated shell users. - - Essentially no tracebacks, and no good way to pinpoint where and when an error occurs. - - This could be fixed by taking queues from python or rust with their very robust error handling paradigms - - Solving this problem alone would vastly improve developer experience with shell scripting - -3. String operation - - String operation in bash often requires the use of external tools such as awk, sed, or cut which each have their own syntax rules that you need to memorize. I mean, awk is literally it's own programming language. - - No multiline strings except with heredocs, which are an extremely clunky and incompatible method of doing so as heredocs can only be used with input redirection to something else. - - rush could include builtins similar to most modern languages like split, format, etc - -4. Syntax - - Syntax in bash is often extremely mind-numbing to read. Even string operations of a moderate complexity (e.g. a cat into a sed into an awk) will require you to remember how to read syntax semantics and remember what individual flags do for several different commands as you try to mentally parse the pipeline. - - This can easily be cleaned up by having builtins with consistent syntax for the most common operations that are usually outsourced to external commands. - -5. Types - - Everything in bash is a string, there are no types. This often makes script behavior difficult to predict. - - Since everything is a string, it's difficult for your script to know if it's working with the right kind of data without overly verbose conditional checks. - - A typing system will make scripts far easier to reason about, far easier to maintain, and far easier to debug - -6. Builtins - - If you've ever used bash on a very minimal system, you'll find that it is woefully limited with just the core gnu utils installed. - - Much of the functionality of modern bash comes from external binaries like grep, awk, and sed. This raises issues when running scripts on systems that may not have these binaries installed. - - Many of the functionalities most commonly afforded by external commands could easily be adapted into a standard library of sorts. - -7. Prone to errors - - Bash syntax is riddled with pitfalls and gotchas - - Quoting rules, working with elements in arrays, passing arguments to functions, the list goes on - - Making the quoting and escaping rules clearer and also allowing functions to be defined like 'function(arg1,arg2) {}' would make it leagues easier to work with. - -8. Concurrency and Async - - While bash allows you to run processes in the background non-blockingly, the support for this functionality is woefully minimal. - - There is essentially no native support for async programming beyond forking processes. - -9. Error messages - - Bash can be extremely frustrating to work with at times. Error messages are often extremely unhelpful, usually just canned responses rather than anything specific to the code being run. - - A single unclosed delimiter in a long script becomes a hunt for a needle in a haystack. - - This is actually a pretty simple fix too - -10. Portability issues - - It is very often that bash scripts will work on one machine and not on another. This is because shells in general are very dependent upon state, which is obviously not shared between machines. The dependency on external tools and environment state leads to unpredictability across machines and platforms. - - A decent approach for this might be to check for the availability of each command specified in a script and then produce an error message listing the missing commands, rather than running it and having the script actually execute code until it reaches something that it can't do, leading to unpredictable, half-finished logic - -11. No real way to test code - - The best you can really do is create a separate script that sources another script and then create test functions in the second file. This is of course a very clunky way to test code and sourcing the other file can have unintended side effects. - - -# Main Feature Ideas - -1. Detailed error messages, fine grained error handling - - In order to bring shell scripting to the modern age, error messages and error handling must be on par with that of Rust or Python. - - Line/Column numbers, "try except" blocks, the whole 9 yards - -2. Multiple shebangs in one script - - Shell scripting has limitations, just like every other language. When you run into something that isn't well suited to shell scripting, your only option is to either suck it up and do it in bash anyway, or create an entire separate file to solve that one problem and then execute it in your script - - This could be solved by allowing users to write shebangs in subshells, allowing the script to dynamically switch interpreters to execute code in other languages in said subshells. This way you could get the granular input/output control of shell scripting alongside the power of higher order languages like python. - -3. Macros - - Think macros in Rust, but brought to shell scripting. The syntax would be something like this: `split!echo "hello world" -> ["hello","world"]` or `replace("world","rush")!echo "hello world" -> "hello rush"`. - - This would allow for users to dynamically create operations that can be applied to any command that produces the expected output - -4. Types - - Shell scripting to this day still has no real support for types. This feature alone would be groundbreaking for shell scripting. - - Adding not only proper native support for booleans and floats, but also higher order data structures such as dictionaries or sets would revolutionize what shell scripting is capable of. - -5. Standard Library - - The GNU core utils alone provide woefully incomplete utility. Bash often relies on external commands like `sed`, `awk`, or `grep` in order to perform many of it's main features. This produces a lack of portability as scripts have to make many assumptions about the end user's environment. - - This could be fixed by simply including many of the most common operations performed in bash (pattern searching, regex operations, field extraction) into macros that are built into rush. This would not only increase portability, it would also unify syntax, reducing the mental overhead associated with needing to learn many different bespoke flags and syntaxes for various commands, especially in the case of `awk` and `sed`. - - This is probably the most ambitious part of this project, so I should most like try to get the above ideas implemented before I even begin to approach this. - - -# Opinionation of Features - -* Type declarations - - I want rush to be both backwards compatible with bash while also giving greater utility as a scripting language. - - This means that variable declarations such as `var=value` should remain valid. Bash is weakly typed by default, so the normal variable declaration syntax should work that way. - - However, I also wish to include new shell builtins that allow for strongly typing variables. Stuff like `int var=10` or `string var="hello"` so that the benefits of strong typing (robustness, predictability) can be reaped for longer/more complex scripts. - - For arrays, I want to keep them as they are and also maintain the usual gross syntax for working with them, just for backwards compatibility as a legacy feature. Eventually I want to implement a better type for this similar to Python's lists. - -* Macros - - I want to implement macros in a way that makes sense in the context of shell scripting. I'll include several macros that are in sort of a standard library, but each macro will essentially be a command expansion. This is how I want it to work: `split @ echo "hello world" = ["hello", "world"]` would internally be the same as writing `echo "hello world" | tr ' ' '\n'`. Macros would be defined using a syntax like `macro split { "$cmd" | tr ' ' '\n' }` and there would even be support for macros with arguments. - - My idea for handling arguments is to do keep parameters passed before the '@' as positional params for the macro, and then pass input from following commands into a variable '$~'. - - The argument for macros as a feature is to provide a middleground between aliases and functions. There are many cases where aliases are too limited and functions are somewhat overkill, like in the case of the above split macro. - - The main use case will mainly be code *injection* rather than code *execution*. This allows for dynamic generation of code, such as generating many function definitions. This can heavily reduce boilerplate and make scripts more expressive. - - Macros provide inline substitution while functions produce a brand new scope in a subshell. Macros are more suited to logic that can be easily written on one line, and are therefore too simple to really justify an entire function definition, but are slightly too complex to be put into an alias, while still being an operation that is performed frequently. - -* Builtins - - Many core functionalities of shell scripting are actually offloaded to external commands like awk, grep, and sed. While these tools are *extremely* powerful, they are mostly used for extremely simple tasks. I can probably count on one hand the amount of times I've had to make real use of awk's full capabilities rather than just using it for field extraction, for instance. These *simple* functionalities can easily be implemented as builtins, and users could utilize the corresponding external commands if they require their more advanced functionalities. - - Including these as builtins opens the door for having a library of builtin macros such as split!, find("pattern")!, or extract(' ',[1,3])! for instance. - -* Many Interpreters - - Shell scripting is powerful, but not perfect. There are many cases where you will run into a problem in a script that shell scripting struggles with but Python handles effortlessly, for instance. The builtins I wish to include will most likely not cover these bases. - - Therefore, I wish to expand subshell syntax to allow users to declare a shebang in subshells to dynamically offload subshell content to a secondary interpreter. Here's an example: -``` -#!/usr/bin/env rush - -echo "Greetings from rush!" - -( - #!/usr/bin/env python3 - print("Greetings from python!") -) -``` - - To achieve a similar functionality in bash, you have to use heredocs which are a clunky and archaic feature that is incompatible with modern development for many reasons, most of which being that heredocs exist entirely as a string until runtime, meaning that IDEs and language servers are completely unable to reason about them. - - A language server for rush would be able to dynamically update syntax highlighting in subshells to match the chosen interpreter, ideally. - - This centralization of concerns would allow users to utilize the system-level power of the shell and programmatic power of modern scripting languages all in the same place with a simple and intuitive syntax - - *Note: Have to figure out how to move variables from the outer shell to the inner one. Probably just expanding them during the tokenization I guess.* - -* Shebangs - - Shebangs can be expanded upon to make handling arguments much clearer and self-documenting. For instance: `#!/usr/bin/env rush arg1 arg2=default arg3`, and then reference the args in the script like `$arg1`, `$arg2`, `$arg3`, calling the script like `./script arg1 arg2 arg3`. This is much easier to reason about than the usual `$1`, `$2`, etc. syntax and heavily reduces the boilerplate usually included in scripts used to improve readability - - Rush could additionally validate the arguments at runtime, raising an error if any are missing. - - If no args are specified in the shebang, Rush would default to using traditional positional variable syntax. - - This could even be extended to include the strong typing system mentioned earlier: `#!/usr/bin/env rush int:arg1 string:arg2="default" list:arg3` - - Scripts that require certain arguments but accept further arguments could be written like: `#!/usr/bin/env rush arg1 arg2 ...` and then the extra positional args could be accessed using the original "$@" syntax or something like "$args" - -## Macro ideas - - - `split !` -> splits a string, optionally takes an arg as a delimiter like `split "," ! echo "foo,bar,baz"` - - `replace !` -> simple find and replace in a string or file - - `range !` -> needs work, but this idea is useful - - `filter !` -> only print lines from cmd output with - - `toupper/tolower !` -> change all text to upper or lower case - - `wordcount/linecount/charcount !` -> self explanatory - - `sum/max/average/othermathstuff !` -> simple math operations - - `waitall !` -> wait for all background operations to finish - -### newer version - - called like `macro @ ?` because the exclamation point looks too much like a pipe - - Insert code to be executed once it is reached - - Can be used to generate function definitions dynamically - - Or reduce boilerplate for error handling, etc - -## Types to include - - from python: - - ints - - floats - - booleans - - lists - - dictionaries - - new types: - - file: stores a file in a variable. will have builtins that allow for more fine grained control than simply storing the path as a string in a variable - - bytes: raw data, essentially an abstraction over a Vec in rust. - -## Misc feature ideas -- Some way to tell if you terminal is in an ssh session or a nested shell? Alternate prompt or color scheme? -- Actually having color scheme support in the first place -- Fuzzy finder autocompletion window -- Fuzzy finder integrated as builtin? -- A way to run scripts/programs in a totally clean environment, similar to nix package building -- Terminal display layers (sounds painful) diff --git a/overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix b/overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix deleted file mode 100644 index 146490e..0000000 --- a/overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ inputs, lib, ... }: { - imports = [ - inputs.devshell.flakeModule - ]; - - config.perSystem = - { pkgs - , ... - }: { - config.devshells.default = { - imports = [ - "${inputs.devshell}/extra/language/c.nix" - # "${inputs.devshell}/extra/language/rust.nix" - ]; - - commands = with pkgs; [ - { package = rust-toolchain; category = "rust"; } - ]; - - language.c = { - libraries = lib.optional pkgs.stdenv.isDarwin pkgs.libiconv; - }; - }; - }; -} diff --git a/overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix b/overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix deleted file mode 100644 index 52fa53d..0000000 --- a/overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ inputs, ... }: -let - overlays = [ - (import inputs.rust-overlay) - (self: super: assert !(super ? rust-toolchain); rec { - rust-toolchain = super.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml; - - # buildRustCrate/crate2nix depend on this. - rustc = rust-toolchain; - cargo = rust-toolchain; - }) - ]; -in -{ - perSystem = { system, ... }: { - _module.args.pkgs = import inputs.nixpkgs { inherit system overlays; config = { }; }; - }; -} diff --git a/overlay/pkgs/rsh/rsh/roadmap.md b/overlay/pkgs/rsh/rsh/roadmap.md deleted file mode 100644 index e049985..0000000 --- a/overlay/pkgs/rsh/rsh/roadmap.md +++ /dev/null @@ -1,202 +0,0 @@ -## TODO: -### priority -- [ ] **Phase 1** - implement essential bash compatibility -- [ ] **Phase 2** - implement essential rsh features -- [ ] **Phase 3** - implement QoL performance and UX refinements (testing, async, syntax highlighting) - -### rsh features -- [ ] macros and custom builtins - - [ ] macro definition syntax - - [ ] macro execution syntax - - [ ] macros with args - - [ ] builtin macros for very common tasks - - [ ] string replacement - - [ ] pattern searching - - [ ] field extraction -- [ ] handling for JSON and modern data structures -- [ ] modern error handling such as stack tracing and try/except (logging macros as well?) - - [ ] includes line/col numbers - - [ ] shows the line where the error occured like python/rust - - [ ] points to the exact location of the error like python/rust -- [ ] typing system - typing builtins like int, string, float, etc along with the usual weak bash types - - [ ] support for default bash typing - - [ ] support for strongly typing function arguments like function(int:arg1,string:arg2) -- [ ] improved bash control structures? -- [ ] support for explicitly declaring expected arguments - - [ ] do this in the shebang `#!/usr/bin/env rsh arg1 arg2` - - [ ] do this in functions `function(arg1,arg2)` - - [ ] allow for extra arguments `#!/usr/bin/env rsh arg1 arg2 ...`, `function(arg1, arg2, ...)` - - [ ] allow for default values `arg1=default`, `string:arg1="hello"` -- [ ] syntax highlighting in shell prompt -- [ ] language server? -- [ ] better support for asynchronous scripting -- [ ] portability features - - [ ] detection of missing commands at script runtime - - [ ] detection of unset environment variables at script runtime -- [ ] builtin testing framework for scripts using asserts - -### bash features -- [x] unquoted string suppport. (space character taken care of) (that one may still be a bit buggy) -- [x] "quoted" string support. (space character taken care of) -- [x] 'quoted' string support. (space character taken care of) -- [ ] \`backticked\` string support. (space character taken care of) -- [ ] binary numbers support (uint only for now ?). 0b mark. (i64 by default) -- [ ] octal numbers support (uint only for now ?). 0 mark. (i64 by default) -- [ ] hexadecimal numbers support (uint only for now ?). 0x mark. (i64 by default) -- [ ] (signed) int support (i64 by default). -- [ ] (signed) float support (f64 by default). exponent correctly parsed. -- [x] variable assignment. (type is autodetected) -- [ ] array assignment. (type is autodetected) -- [ ] shebang (#!) and comments are correctly parsed. -- [ ] : syntax -- [ ] compgen builtin. -- [ ] complete builtin. -- [ ] compopt builtin. -- [ ] fc builtin. -- [ ] history builtin. -- [ ] local builtin. -- [ ] mapfile builtin. -- [ ] readarray builtin. -- [ ] return builtin. -- [ ] shift builtin. -- [ ] test builtin. -- [ ] trap builtin. -- [ ] ulimit builtin. -- [ ] umask builtin. -- [ ] unalias builtin. -- [ ] arrays support (single dimension, can be both associative and indexed, and store int, float or string. -- [x] $variable, ${variable} and parameter ($1 etc). -- [ ] ${parameter-default} ${parameter:-default} If parameter not set, use default. -- [ ] ${parameter=default}, ${parameter:=default} If parameter not set, set it to default. -- [ ] ${parameter+alt_value}, ${parameter:+alt_value} If parameter set, use alt_value, else use null string. -- [ ] ${parameter?err_msg}, ${parameter:?err_msg} If parameter set, use it, else print err_msg and abort the script with an exit status of 1. -- [ ] variable builtin ${#string} (var length). -- [ ] variable builtin ${string:pos} (extract substring at pos). -- [ ] variable builtin ${string:pos:len} (extract len substring at pos). -- [ ] variable builtin ${string#substr} (deletes shortest match of $substr from front of $string). -- [ ] variable builtin ${string##substr} (deletes longest match of $substr from front of $string). -- [ ] variable builtin ${string%substr} (deletes shortest match of $substr from back of $string). -- [ ] variable builtin ${string%%substr} (deletes longest match of $substr from back of $string). -- [ ] variable builtin ${string/substr/repl} (Replace first match of $substr with $repl). -- [ ] variable builtin ${string//substr/repl} (Replace all matches of $substr with $repl). -- [ ] variable builtin ${string/#substr/repl} (If $substr matches front end of $string, substitute $repl for $substr.). -- [ ] variable builtin ${string/%substr/repl} (If $substr matches back end of $string, substitute $repl for $substr.). -- [ ] variable builtin ${!varprefix*}, ${!varprefix@} (Matches names of all previously declared variables beginning with varprefix.). -- [ ] alias substitution and builtin. -- [ ] $(command) substitution (kind of similar to backtick). -- [ ] ~ expansion. -- [ ] !! expansion (history). -- [ ] {} expansion. -- [ ] $(( )) arithmetic expansion. -- [ ] [[ ]] expansion. -- [ ] [ ] expansion. -- [ ] * ? etc expansion. -- [ ] regexp support =~ -- [ ] POSIX characters classes [:alnum:] matches alphabetic or numeric characters. This is equivalent to A-Za-z0-9. -- [ ] POSIX characters classes [:alpha:] matches alphabetic characters. This is equivalent to A-Za-z. -- [ ] POSIX characters classes [:blank:] matches a space or a tab. -- [ ] POSIX characters classes [:cntrl:] matches control characters. -- [ ] POSIX characters classes [:digit:] matches (decimal) digits. This is equivalent to 0-9. -- [ ] POSIX characters classes [:graph:] (graphic printable characters). Matches characters in the range of ASCII 33 - 126. This is the same as [:print:], below, but excluding the space character. -- [ ] POSIX characters classes [:lower:] matches lowercase alphabetic characters. This is equivalent to a-z. -- [ ] POSIX characters classes [:print:] (printable characters). Matches characters in the range of ASCII 32 - 126. This is the same as [:graph:], above, but adding the space character. -- [ ] POSIX characters classes [:space:] matches whitespace characters (space and horizontal tab). -- [ ] POSIX characters classes [:upper:] matches uppercase alphabetic characters. This is equivalent to A-Z. -- [ ] POSIX characters classes [:xdigit:] matches hexadecimal digits. This is equivalent to 0-9A-Fa-f. -- [x] if elif else fi. -- [ ] case "$var" in "value") command ;; "value2") command ;; esac -- [ ] for n in list do done { } may be used instead of do done -- [ ] for ((a=1; a < >> << 2>&1 etc redirections. don’t forget <(command list) process substitution. -- [x] || && operators -- [ ] echo (complete support) -- [ ] printf -- [ ] read -- [ ] cd -- [ ] pwd -- [ ] popd -- [ ] pushd -- [ ] dirs -- [ ] let += -= /= \*= %= -- [ ] eval -- [ ] set -- [ ] unset -- [ ] export -- [ ] declare -- [ ] typeset -- [ ] readonly -- [ ] getopts -- [ ] source . -- [ ] exit -- [ ] exec -- [ ] shopt -- [ ] caller -- [ ] true -- [ ] false -- [ ] type -- [ ] hash -- [ ] bind -- [ ] help -- [ ] jobs -- [ ] disown -- [ ] bg -- [ ] fg -- [ ] wait -- [ ] suspend -- [ ] logout -- [ ] times -- [ ] kill -- [ ] killall -- [ ] command -- [ ] builtin -- [ ] enable -- [ ] autoload -- [ ] .pest files will need a complete overhaul to be cleaner and better structured. - -### core -- [x] Variables management (simple variables, aliases and single dimension arrays). -- [x] Variables assignment. -- [ ] Arrays assignment. -- [x] $variable, ${variable} and parameter ($1 etc) expansion. -- [ ] ${parameter-default} ${parameter:-default} expansion. -- [ ] ${parameter=default}, ${parameter:=default} expansion. -- [ ] ${parameter+alt_value}, ${parameter:+alt_value} expansion. -- [ ] ${parameter?err_msg}, ${parameter:?err_msg} expansion. -- [ ] variable builtin ${#string} expansion. -- [ ] variable builtin ${string:pos} expansion. -- [ ] variable builtin ${string:pos:len} expansion. -- [ ] variable builtin ${string#substr} expansion. -- [ ] variable builtin ${string##substr} expansion. -- [ ] variable builtin ${string%substr} expansion. -- [ ] variable builtin ${string%%substr} expansion. -- [ ] variable builtin ${string/substr/repl} expansion. -- [ ] variable builtin ${string//substr/repl} expansion. -- [ ] variable builtin ${string/#substr/repl} expansion. -- [ ] variable builtin ${string/%substr/repl} expansion. -- [ ] variable builtin ${!varprefix*}, ${!varprefix@} expansion. -- [ ] Write everything linked to builtins, pipes etc (yeah, that will be *very* long). -- [ ] Have a 100% code coverage when it comes to documentation *and* testing. -- [ ] Multi-lingual support (i18n ? l20n.rs ?). -- [ ] Proper color management (using [term](https://crates.io/crates/term) crate maybe ?). -- [ ] Think of ways to get speed with rsh (read: be faster than Bash). JIT ? Some kind of « parsed script ready to be executed » ? -- [ ] Support float processing. (kind of done, see variable management above). -- [ ] Deprecate several bashy thing (older versions compatibility etc, bash is so much of a noodles plate now due to history that I won’t be able to cover every point so I’ll have to focus). -- [ ] Use [hashbrown](https://github.com/Amanieu/hashbrown) instead of [seahash](https://crates.io/crates/seahash). It will be used by [default](https://github.com/rust-lang/rust/pull/58623) in std once rust 1.36 is out. -- [x] Use of cargo clippy. -- [x] Use of cargo fmt. -- [ ] Use of tarpaulin. -- [ ] Use of criterion for benchmarking. -- [ ] Put tests and benches in their own directories. -- [ ] Use of error-chain, some fuzzer ? -- [ ] So many other things. multidimensionnal arrays ? diff --git a/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock b/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock deleted file mode 100644 index b3e48be..0000000 --- a/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock +++ /dev/null @@ -1,46 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -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 = "rsh_derive" -version = "0.1.0" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml b/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml deleted file mode 100644 index 780f9c7..0000000 --- a/overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "rsh_derive" -version = "0.1.0" -edition = "2024" - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.37" -syn = "2.0.91" diff --git a/overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs b/overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs deleted file mode 100644 index 1eb9365..0000000 --- a/overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -extern crate proc_macro; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; - -#[proc_macro_derive(Node)] -pub fn derive_node(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let name = &input.ident; - - let expanded = quote! { - impl Node for #name { - fn span(&self) -> Span { - self.span - } - - fn set_span(&mut self, span: Span) { - self.span = span; - } - - fn boxed(self) -> Box { - Box::new(self) - } - - fn get_redirs(&self) -> &Vec> { - &self.redirs - } - - fn push_redir(&mut self, redir: Box) { - self.redirs.push(redir) - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn tokens(&self) -> &VecDeque { - &self.tokens - } - - fn flags(&self) -> NdFlags { - self.flags - } - - fn mod_flags(&mut self, transform: Box) { - transform(&mut self.flags) - } - } - }; - - TokenStream::from(expanded) -} diff --git a/overlay/pkgs/rsh/rsh/rust-toolchain.toml b/overlay/pkgs/rsh/rsh/rust-toolchain.toml deleted file mode 100644 index 5d56faf..0000000 --- a/overlay/pkgs/rsh/rsh/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" diff --git a/overlay/pkgs/rsh/rsh/src/builtin.rs b/overlay/pkgs/rsh/rsh/src/builtin.rs deleted file mode 100644 index e998dc2..0000000 --- a/overlay/pkgs/rsh/rsh/src/builtin.rs +++ /dev/null @@ -1,716 +0,0 @@ -use std::collections::VecDeque; -use std::ffi::{CString, OsStr}; -use std::fs; -use std::os::fd::AsRawFd; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::{FileTypeExt, MetadataExt}; -use std::path::{Path, PathBuf}; -use bitflags::bitflags; -use libc::{getegid, geteuid}; -use log::info; -use nix::fcntl::OFlag; -use nix::sys::stat::Mode; -use nix::sys::wait::{waitpid, WaitStatus}; -use nix::unistd::{access, fork, isatty, setpgid, AccessFlags, ForkResult}; - -use crate::execute::{ProcIO, RshWaitStatus, RustFd}; -use crate::interp::parse::{NdFlags, NdType, Node, Span}; -use crate::interp::{expand, token}; -use crate::interp::token::{Redir, RedirType, Tk, TkType}; -use crate::shellenv::{EnvFlags, JobFlags, ShellEnv}; -use crate::event::ShellError; - -pub const BUILTINS: [&str; 14] = [ - "echo", "set", "shift", "export", "cd", "readonly", "declare", "local", "unset", "trap", "node", - "exec", "source", "wait", -]; - -bitflags! { - #[derive(Debug)] - pub struct EchoFlags: u8 { - const USE_ESCAPE = 0b0001; - const NO_NEWLINE = 0b0010; - const NO_ESCAPE = 0b0100; - const STDERR = 0b1000; - } -} - -fn open_file_descriptors(redirs: VecDeque) -> Result, ShellError> { - let mut fd_stack: Vec = Vec::new(); - let mut fd_dupes: Vec<(i32,i32)> = Vec::new(); - info!("Handling redirections for builtin: {:?}", redirs); - - for redir_tk in redirs { - if let NdType::Redirection { ref redir } = redir_tk.nd_type { - let Redir { fd_source, op, fd_target, file_target } = redir; - - if let Some(target) = fd_target { - fd_dupes.push((*target,*fd_source)); - } else if let Some(file_path) = file_target { - let source_fd = RustFd::new(*fd_source)?; - let flags = match op { - RedirType::Input => OFlag::O_RDONLY, - RedirType::Output => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC, - RedirType::Append => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_APPEND, - _ => unimplemented!("Heredocs and herestrings are not implemented yet."), - }; - let mut file_fd = RustFd::open(Path::new(file_path.text()), flags, Mode::from_bits(0o644).unwrap())?; - file_fd.dup2(&source_fd)?; - file_fd.close()?; - fd_stack.push(source_fd); - } - } - } - - while let Some((target,source)) = fd_dupes.pop() { - let mut target_fd = RustFd::new(target)?; - let source_fd = RustFd::new(source)?; - target_fd.dup2(&source_fd)?; - target_fd.close()?; - - fd_stack.push(source_fd); - } - - Ok(fd_stack) -} - -pub fn catstr(mut c_strings: VecDeque,newline: bool) -> CString { - let mut cat: Vec = vec![]; - let newline_bytes = b"\n\0"; - let space_bytes = b" "; - - while let Some(c_string) = c_strings.pop_front() { - let bytes = c_string.to_bytes_with_nul(); - if c_strings.is_empty() { - // Last string: include the null terminator - cat.extend_from_slice(&bytes[..bytes.len() - 1]); - } else { - // Intermediate strings: exclude the null terminator and add whitespace - cat.extend_from_slice(&bytes[..bytes.len() - 1]); - cat.extend_from_slice(space_bytes); - } - } - if newline { - cat.extend_from_slice(newline_bytes); - } else { - cat.extend_from_slice(b"\0"); - } - - CString::from_vec_with_nul(cat).unwrap() -} - -/// Performs a test on a single argument by transforming it and then applying a property check. -/// -/// This function takes a mutable reference to a `VecDeque` of `String` arguments, a transformation function, -/// and a property function. It pops the front element from the `VecDeque`, applies the transformation -/// function to it, and then checks the transformed value against the property function. -/// -/// # Arguments -/// -/// * `args` - A mutable reference to a `VecDeque` of `String` arguments. -/// * `transform` - A function that takes a `String` and returns a `Result`. -/// * `property` - A function that takes a reference to `T` and returns a `bool`. -/// -/// # Returns -/// -/// * `Ok(bool)` - If the transformation and property check are successful, returns the result of the property check. -/// * `Err(ShellError)` - If the `VecDeque` is empty or the transformation fails, returns an appropriate `ShellError`. -/// -/// # Example -/// -/// ``` -/// let mut args = VecDeque::new(); -/// args.push_back("42".to_string()); -/// let transform = |s: String| -> Result { Ok(s.parse::().unwrap()) }; -/// let property = |x: &i32| -> bool { *x > 0 }; -/// let result = do_test(&mut args, transform, property); -/// assert!(result.unwrap()); -/// ``` -fn do_test( - args: &mut VecDeque, - transform: F1, - property: F2, - span: Span -) -> Result -where - F1: FnOnce(Tk) -> Result, - F2: FnOnce(&T) -> bool -{ - if let Some(st) = args.pop_front() { - let transformed = transform(st).map_err(|_| false); - if transformed.is_err() { - return Ok(false) - } - Ok(property(&transformed.unwrap())) - } else { - Err(ShellError::from_syntax("Did not find a comparison target for integer in test", span)) - } -} - -/// Compares two arguments by transforming them and then applying a comparison function. -/// -/// This function takes a `String` argument, a mutable reference to a `VecDeque` of `String` arguments, -/// a transformation function, and a comparison function. It pops the front element from the `VecDeque`, -/// applies the transformation function to both the provided `String` and the popped element, and then -/// compares the transformed values using the comparison function. -/// -/// # Arguments -/// -/// * `arg1` - The first `String` argument to be transformed and compared. -/// * `args` - A mutable reference to a `VecDeque` of `String` arguments. -/// * `transform` - A function that takes a `String` and returns a `Result`. -/// * `comparison` - A function that takes two references to `T` and returns a `bool`. -/// -/// # Returns -/// -/// * `Ok(bool)` - If the transformation and comparison are successful, returns the result of the comparison. -/// * `Err(ShellError)` - If the `VecDeque` is empty or the transformation fails, returns an appropriate `ShellError`. -/// -/// # Example -/// -/// ``` -/// let mut args = VecDeque::new(); -/// args.push_back("42".to_string()); -/// let transform = |s: String| -> Result { Ok(s.parse::().unwrap()) }; -/// let comparison = |x: &i32, y: &i32| -> bool { x == y }; -/// let result = do_cmp("42".to_string(), &mut args, transform, comparison); -/// assert!(result.unwrap()); -/// ``` -fn do_cmp( - arg1: Tk, - args: &mut VecDeque, - transform: F1, - comparison: F2 -) -> Result -where - F1: Fn(Tk) -> Result, - F2: FnOnce(&T, &T) -> bool -{ - if let Some(st) = args.pop_front() { - let left = transform(arg1).map_err(|_| false); - let right = transform(st).map_err(|_| false); - if left.is_err() || right.is_err() { - return Ok(false) - } - Ok(comparison(&left.unwrap(), &right.unwrap())) - } else { - Err(ShellError::from_syntax("Did not find a comparison target for integer in test", arg1.span())) - } -} - -/// Performs a logical operation (AND or OR) on two boolean results. -/// -/// This function takes a mutable reference to a `VecDeque` of `String` arguments, the command (`test` or `[`), -/// the first result of a test, and the logical operator (`-a` for AND, `-o` for OR). It recursively calls the `test` -/// function to evaluate the next condition and combines the results using the specified logical operator. -/// -/// # Arguments -/// -/// * `args` - A mutable reference to a `VecDeque` of `String` arguments. -/// * `command` - The command (`test` or `[`). -/// * `result1` - The result of the first test. -/// * `operator` - The logical operator (`-a` for AND, `-o` for OR). -/// -/// # Returns -/// -/// * `Result` - Returns the combined result of the logical operation. -/// -/// # Errors -/// -/// * Returns `ShellError::from_syntax` if there is a syntax error or missing arguments. -fn do_logical_op( - args: &mut VecDeque, - command: Tk, - result1: bool, - operator: Tk -) -> Result { - args.push_front(command); - let result2 = test(std::mem::take(args)).map(|res| matches!(res,RshWaitStatus::Success {..}))?; - match operator.text() { - "!" => { Ok(!result2) }, - "-a" => { Ok(result1 && result2) }, - "-o" => { Ok(result1 || result2) }, - _ => Err(ShellError::from_syntax("Expected a logical operator (-a or -o)", operator.span())), - } -} - -/// Implements the `test` builtin command for a Unix shell. -/// -/// The `test` command evaluates conditional expressions and returns a success or failure status based on the evaluation. -/// This function handles various types of tests, including file attributes, string comparisons, and integer comparisons. -/// -/// # Arguments -/// -/// * `argv` - A vector of `CString` arguments passed to the `test` command. -/// -/// # Returns -/// -/// * `Ok(RshWaitStatus)` - Returns a success or failure status based on the evaluation of the conditional expression. -/// * `Err(ShellError)` - Returns an appropriate `ShellError` if there is a syntax error or other issues with the arguments. -/// -/// # Examples -/// -/// ``` -/// let argv = vec![CString::new("test").unwrap(), CString::new("-f").unwrap(), CString::new("file.txt").unwrap()]; -/// let result = test(argv); -/// assert!(result.is_ok()); -/// ``` -/// -/// ``` -/// let argv = vec![CString::new("[").unwrap(), CString::new("1").unwrap(), CString::new("-eq").unwrap(), CString::new("1").unwrap(), CString::new("]").unwrap()]; -/// let result = test(argv); -/// assert!(result.is_ok()); -/// ``` -/// -/// # Supported Tests -/// -/// ## File Tests -/// -/// - `-b`: Block device -/// - `-c`: Character device -/// - `-d`: Directory -/// - `-e`: File exists -/// - `-f`: Regular file -/// - `-g`: Set-group-ID flag set -/// - `-G`: Group-ID matches -/// - `-h`: Symbolic link -/// - `-L`: Symbolic link (same as `-h`) -/// - `-k`: Sticky bit set -/// - `-N`: Modified since last read -/// - `-O`: User-ID matches -/// - `-p`: Named pipe -/// - `-r`: Readable -/// - `-s`: File size greater than zero -/// - `-S`: Socket -/// - `-u`: Set-user-ID flag set -/// - `-w`: Writable -/// - `-x`: Executable -/// -/// ## String Tests -/// -/// - `-n`: String is non-empty -/// - `-z`: String is empty -/// - `=`: Strings are equal -/// - `!=`: Strings are not equal -/// -/// ## Integer Tests -/// -/// - `-eq`: Equal -/// - `-ge`: Greater than or equal -/// - `-gt`: Greater than -/// - `-le`: Less than or equal -/// - `-lt`: Less than -/// - `-ne`: Not equal -/// -/// ## Logical Operators -/// -/// - `-a`: And -/// - `-o`: Or -/// -/// ## Special Operators -/// -/// - `!`: Not -/// - `-t`: File descriptor is associated with a terminal -/// -/// # Notes -/// -/// - The `test` command can be invoked using the `test` keyword or with square brackets `[ ]`. -/// - The function handles various error conditions, such as missing arguments or invalid syntax. -/// -/// # Errors -/// -/// - Returns `ShellError::from_syntax` if there is a syntax error or missing arguments. -/// - Returns `ShellError::from_syntax` if an expected comparison operator is missing. -/// -/// # Safety -/// -/// This function uses unsafe code to get the effective user ID and group ID using `geteuid` and `getegid`. -/// -/// # Panics -/// -/// This function does not panic. -pub fn test(mut argv: VecDeque) -> Result { - info!("Starting builtin test"); - let span = Span::from(argv.front().unwrap().span().start,argv.back().unwrap().span().end); - let is_false = || -> Result { Ok(RshWaitStatus::Fail { code: 1, cmd: Some("test".into()), span }) }; - let is_true = || -> Result { Ok(RshWaitStatus::Success { span }) }; - let is_int = |tk: &Tk| -> bool { tk.text().parse::().is_ok() }; - let to_int = |tk: Tk| -> Result { - tk.text().parse::().map_err(|_| ShellError::from_syntax("Expected an integer in test", tk.span())) - }; - let is_path = |tk: &Tk| -> bool { PathBuf::from(tk.text()).exists() }; - let to_meta = |tk: Tk| -> Result { - fs::metadata(tk.text()).map_err(|_| ShellError::from_syntax(&format!("Test: Path '{}' does not exist", tk.text()), tk.span())) - }; - let string_noop = |tk: Tk| -> Result { Ok(tk.text().into()) }; - let command = argv.pop_front().unwrap(); - let is_bracket = match command.text() { - "[" => true, - "test" => false, - _ => unreachable!() - }; - - if is_bracket { - if let Some(arg) = argv.back() { - if arg.text() != "]" { - return Err(ShellError::from_syntax("Test is missing a closing bracket", arg.span())) - } - } else { - return Err(ShellError::from_syntax("Found a test with no arguments", command.span())) - } - } - - if let Some(arg) = argv.pop_front() { - let result1 = match arg.text() { - "!" => do_logical_op(&mut argv, command.clone(), true, arg)?, - "-t" => do_test(&mut argv, to_int, |int| isatty(*int).is_ok(), arg.span())?, - "-b" => do_test(&mut argv, to_meta, |meta| meta.file_type().is_block_device(), arg.span())?, - "-c" => do_test(&mut argv, to_meta, |meta| meta.file_type().is_char_device(), arg.span())?, - "-d" => do_test(&mut argv, to_meta, |meta| meta.is_dir(), arg.span())?, - "-f" => do_test(&mut argv, to_meta, |meta| meta.is_file(), arg.span())?, - "-g" => do_test(&mut argv, to_meta, |meta| meta.mode() & 0o2000 != 0, arg.span())?, - "-G" => do_test(&mut argv, to_meta, |meta| meta.gid() == unsafe { getegid() }, arg.span())?, - "-h" => do_test(&mut argv, to_meta, |meta| meta.is_symlink(), arg.span())?, - "-L" => do_test(&mut argv, to_meta, |meta| meta.is_symlink(), arg.span())?, - "-k" => do_test(&mut argv, to_meta, |meta| meta.mode() & 0o1000 != 0, arg.span())?, - "-N" => do_test(&mut argv, to_meta, |meta| meta.mtime() > meta.atime(), arg.span())?, - "-O" => do_test(&mut argv, to_meta, |meta| meta.uid() == unsafe { geteuid() }, arg.span())?, - "-p" => do_test(&mut argv, to_meta, |meta| meta.file_type().is_fifo(), arg.span())?, - "-s" => do_test(&mut argv, to_meta, |meta| meta.len() > 0, arg.span())?, - "-S" => do_test(&mut argv, to_meta, |meta| meta.file_type().is_socket(), arg.span())?, - "-u" => do_test(&mut argv, to_meta, |meta| meta.mode() & 0o4000 != 0, arg.span())?, - "-n" => do_test(&mut argv, string_noop, |st| !st.is_empty(), arg.span())?, - "-z" => do_test(&mut argv, string_noop, |st| st.is_empty(), arg.span())?, - "-e" => do_test(&mut argv, string_noop, |st| Path::new(st).exists(), arg.span())?, - "-r" => do_test(&mut argv, string_noop, |st| access(Path::new(st), AccessFlags::R_OK).is_ok(), arg.span())?, - "-w" => do_test(&mut argv, string_noop, |st| access(Path::new(st), AccessFlags::W_OK).is_ok(), arg.span())?, - "-x" => do_test(&mut argv, string_noop, |st| access(Path::new(st), AccessFlags::X_OK).is_ok(), arg.span())?, - _ if is_int(&arg) => { - if let Some(cmp) = argv.pop_front() { - match cmp.text() { - "-eq" => do_cmp(arg, &mut argv, to_int, |left, right| left == right)?, - "-ge" => do_cmp(arg, &mut argv, to_int, |left, right| left >= right)?, - "-gt" => do_cmp(arg, &mut argv, to_int, |left, right| left > right)?, - "-le" => do_cmp(arg, &mut argv, to_int, |left, right| left <= right)?, - "-lt" => do_cmp(arg, &mut argv, to_int, |left, right| left < right)?, - "-ne" => do_cmp(arg, &mut argv, to_int, |left, right| left != right)?, - _ => { - return Err(ShellError::from_syntax("Expected an integer after comparison operator in test", cmp.span())); - } - } - } else { - return Err(ShellError::from_syntax("Expected a comparison operator after integer in test", command.span())); - } - } - _ if is_path(&arg) && argv.front().is_some_and(|arg| matches!(arg.text(), "-ef" | "-nt" | "-ot")) => { - if let Some(cmp) = argv.pop_front() { - match cmp.text() { - "-ef" => do_cmp(arg, &mut argv, to_meta, |left, right| left.dev() == right.dev() && left.ino() == right.ino())?, - "-nt" => do_cmp(arg, &mut argv, to_meta, |left, right| left.mtime() > right.mtime())?, - "-ot" => do_cmp(arg, &mut argv, to_meta, |left, right| left.mtime() < right.mtime())?, - _ => { - return Err(ShellError::from_syntax("Expected a file name after -b flag", cmp.span())); - } - } - } else { - return Err(ShellError::from_syntax("Expected a comparison operator after path name in test", command.span())); - } - } - _ => { - if argv.is_empty() { - !arg.text().is_empty() // Default behavior is to return true if a string is not empty - } else if let Some(cmp) = argv.pop_front() { - match cmp.text() { - "=" => do_cmp(arg, &mut argv, string_noop, |left, right| left == right)?, - "!=" => do_cmp(arg, &mut argv, string_noop, |left, right| left != right)?, - _ => { - if cmp.text() == "==" { - return Err(ShellError::from_syntax("`==` is not a valid comparison operator, use `=` instead", cmp.span())); - } else { - return Err(ShellError::from_syntax("Expected a comparison operator after string in test", cmp.span())); - } - } - } - } else { - return Err(ShellError::from_syntax("Expected a comparison operator after string in test", command.span())); - } - } - }; - if let Some(arg) = argv.pop_front() { - match arg.text() { - "-a" | "-o" => { // And/Or - let result = do_logical_op(&mut argv, command, result1, arg)?; - match result { - true => return is_true(), - false => return is_false(), - } - } - "]" => {} - _ => { - return Err(ShellError::from_syntax("Unexpected extra argument found in test", arg.span())); - } - } - } - match result1 { - true => is_true(), - false => is_false(), - } - } else { - Err(ShellError::from_syntax("Test called with no arguments", command.span())) - } -} - -pub fn alias(shellenv: &mut ShellEnv, node: Node) -> Result { - let mut argv: VecDeque = node.get_argv()?.into(); - argv.pop_front(); - while let Some(arg) = argv.pop_front() { - if !token::REGEX["assignment"].is_match(arg.text()) { - return Err(ShellError::from_syntax(&format!("Expected an assignment pattern in alias args, got {}",arg.text()), arg.span())) - } - let (alias,value) = arg.text().split_once('=').unwrap(); - if let Err(e) = shellenv.set_alias(alias.into(), value.into()) { - return Err(ShellError::from_parse(e.as_str(), node.span())) - } - } - Ok(RshWaitStatus::Success { span: node.span() }) -} - -pub fn cd(shellenv: &mut ShellEnv, node: Node) -> Result { - let mut argv = node - .get_argv()? - .iter() - .map(|arg| CString::new(arg.text()).unwrap()) - .collect::>(); - argv.pop_front(); - let new_pwd; - if let Some(arg) = argv.pop_front() { - new_pwd = arg; - } else if let Some(home_path) = shellenv.get_variable("HOME") { - new_pwd = CString::new(home_path).unwrap(); - } else { - new_pwd = CString::new("/").unwrap(); - } - let path = Path::new(new_pwd.to_str().unwrap()); - shellenv.change_dir(path, node.span())?; - Ok(RshWaitStatus::Success { span: node.span() }) -} - -pub fn source(shellenv: &mut ShellEnv, node: Node) -> Result { - let mut argv = node - .get_argv()? - .iter() - .map(|arg| CString::new(arg.text()).unwrap()) - .collect::>(); - argv.pop_front(); - for path in argv { - let file_path = Path::new(OsStr::from_bytes(path.as_bytes())); - shellenv.source_file(file_path.to_path_buf())? - } - Ok(RshWaitStatus::Success { span: node.span() }) -} - -fn flags_from_chars(chars: &str) -> EnvFlags { - let flag_list = chars.chars(); - let mut env_flags = EnvFlags::empty(); - for ch in flag_list { - match ch { - 'a' => env_flags |= EnvFlags::EXPORT_ALL_VARS, - 'b' => env_flags |= EnvFlags::REPORT_JOBS_ASAP, - 'e' => env_flags |= EnvFlags::EXIT_ON_ERROR, - 'f' => env_flags |= EnvFlags::NO_GLOB, - 'h' => env_flags |= EnvFlags::HASH_CMDS, - 'k' => env_flags |= EnvFlags::ASSIGN_ANYWHERE, - 'm' => env_flags |= EnvFlags::ENABLE_JOB_CTL, - 'n' => env_flags |= EnvFlags::NO_EXECUTE, - 'r' => env_flags |= EnvFlags::ENABLE_RSHELL, - 't' => env_flags |= EnvFlags::EXIT_AFTER_EXEC, - 'u' => env_flags |= EnvFlags::UNSET_IS_ERROR, - 'v' => env_flags |= EnvFlags::PRINT_INPUT, - 'x' => env_flags |= EnvFlags::STACK_TRACE, - 'B' => env_flags |= EnvFlags::EXPAND_BRACES, - 'C' => env_flags |= EnvFlags::NO_OVERWRITE, - 'E' => env_flags |= EnvFlags::INHERIT_ERR, - 'H' => env_flags |= EnvFlags::HIST_SUB, - 'P' => env_flags |= EnvFlags::NO_CD_SYMLINKS, - 'T' => env_flags |= EnvFlags::INHERIT_RET, - _ => eprintln!("set: no such option '{}'",ch) - } - } - env_flags -} - -pub fn set_or_unset(shellenv: &mut ShellEnv, node: Node, set: bool) -> Result { - let span = node.span(); - if let NdType::Builtin { mut argv } = node.nd_type { - argv.pop_front(); // Ignore 'set' - if argv.front().is_none_or(|arg| !arg.text().starts_with('-')) { - return Err(ShellError::from_execf("Invalid 'set' invocation", 1, span)) - } - let flag_arg = argv.pop_front().unwrap(); - let set_flags = flag_arg.text(); - let set_flags = set_flags.strip_prefix('-').unwrap(); - let env_flags = flags_from_chars(set_flags); - match set { - true => shellenv.set_flags(env_flags), - false => shellenv.unset_flags(env_flags), - } - Ok(RshWaitStatus::new()) - } else { unreachable!() } -} - -pub fn pwd(shellenv: &ShellEnv, span: Span) -> Result { - if let Some(pwd) = shellenv.get_variable("PWD") { - let stdout = RustFd::from_stdout()?; - stdout.write(pwd.as_bytes())?; - Ok(RshWaitStatus::Success { span }) - } else { - Err(ShellError::from_execf("PWD environment variable is unset", 1, span)) - } -} - -pub fn export(shellenv: &mut ShellEnv, node: Node) -> Result { - let last_status = RshWaitStatus::Success { span: node.span() }; - if let NdType::Builtin { mut argv } = node.nd_type { - argv.pop_front(); // Ignore "export" - while let Some(ass) = argv.pop_front() { - if let Some((key,value)) = ass.text().split_once('=') { - shellenv.export_variable(key.to_string(), value.to_string()); - } else { - return Err(ShellError::from_execf(format!("Expected a variable assignment in export, got this: {}", ass.text()).as_str(), 1, ass.span())) - } - } - Ok(last_status) - } else { unreachable!() } -} - -pub fn jobs(shellenv: &mut ShellEnv, node: Node, mut io: ProcIO, in_pipe: bool) -> Result { - let mut argv = node.get_argv()?.into_iter().collect::>(); - argv.pop_front(); - let mut flags = JobFlags::empty(); - while let Some(arg) = argv.pop_front() { - let mut chars = arg.text().chars().collect::>(); - if chars.front().is_none_or(|ch| *ch != '-') { - return Err(ShellError::from_execf("Invalid flag in `jobs` invocation", 1, node.span())) - } - chars.pop_front(); // Ignore the leading hyphen - while let Some(ch) = chars.pop_front() { - let flag = match ch { - 'l' => JobFlags::LONG, - 'p' => JobFlags::PIDS_ONLY, - 'n' => JobFlags::NEW_ONLY, - 'r' => JobFlags::RUNNING, - 's' => JobFlags::STOPPED, - _ => return Err(ShellError::from_execf("Invalid flag in `jobs` invocation", 1, node.span())) - }; - flags |= flag; - } - } - shellenv.job_table.print_jobs(&flags); - - Ok(RshWaitStatus::Success { span: node.span() }) -} - -pub fn echo(shellenv: &mut ShellEnv, node: Node, mut io: ProcIO, in_pipe: bool) -> Result { - let mut flags = EchoFlags::empty(); - let span = node.span(); - let mut argv = node.get_argv()?.into_iter().collect::>(); - argv.pop_front(); // Remove 'echo' from argv - // Get flags - if argv.front().is_some_and(|arg| arg.text().starts_with('-')) { - let next_arg = argv.pop_front().unwrap(); - let mut options = next_arg.text().strip_prefix('-').unwrap().chars(); - loop { - match options.next() { - Some('e') => { - if flags.contains(EchoFlags::NO_ESCAPE) { - flags &= !EchoFlags::NO_ESCAPE - } - flags |= EchoFlags::USE_ESCAPE - } - Some('r') => flags |= EchoFlags::STDERR, - Some('n') => flags |= EchoFlags::NO_NEWLINE, - Some('E') => { - if flags.contains(EchoFlags::USE_ESCAPE) { - flags &= !EchoFlags::USE_ESCAPE - } - flags |= EchoFlags::NO_ESCAPE - } - _ => break - } - } - if flags.is_empty() { - argv.push_front(next_arg); - } - } - let argv = argv.into_iter().map(|tk| { - let text = tk.text(); - if flags.contains(EchoFlags::USE_ESCAPE) { - CString::new(expand::process_ansi_escapes(text)).unwrap() - } else { - CString::new(tk.text()).unwrap() - } - }).collect::>(); - - let redirs = node.get_redirs()?; - info!("Executing echo with argv: {:?}, and redirs: {:?}", argv,node.redirs); - - io.backup_fildescs()?; - let newline_opt = !flags.contains(EchoFlags::NO_NEWLINE); - let output_str = catstr(argv, newline_opt); - let mut fd_stack = vec![]; - fd_stack.extend(open_file_descriptors(redirs.into())?); - - let output_fd = if flags.contains(EchoFlags::STDERR) { - if let Some(ref err_fd) = io.stderr { - err_fd.lock().unwrap().dup().unwrap_or_else(|_| RustFd::from_stderr().unwrap()) - } else { - RustFd::from_stderr()? - } - } else if let Some(ref out_fd) = io.stdout { - out_fd.lock().unwrap().dup().unwrap_or_else(|_| RustFd::from_stdout().unwrap()) - } else { - RustFd::from_stdout()? - }; - - if let Some(ref fd) = io.stderr { - if !flags.contains(EchoFlags::STDERR) { - let fd = fd.lock().unwrap(); - output_fd.dup2(&fd.as_raw_fd())?; - } - } - - if in_pipe { - output_fd.write(output_str.as_bytes())?; - std::process::exit(0); - } - match unsafe { fork() } { - Ok(ForkResult::Child) => { - output_fd.write(output_str.as_bytes())?; - std::process::exit(0); - } - Ok(ForkResult::Parent { child }) => { - for mut fd in fd_stack { - fd.close().unwrap(); - } - io.restore_fildescs()?; - if node.flags.contains(NdFlags::BACKGROUND) { - setpgid(child, child).map_err(|_| ShellError::from_io())?; - shellenv.new_job(vec![child], vec!["echo".into()], child); - Ok(RshWaitStatus::Success { span }) - } else { - match waitpid(child, None) { - Ok(WaitStatus::Exited(_, code)) => match code { - 0 => Ok(RshWaitStatus::Success { span }), - _ => Ok(RshWaitStatus::Fail { code, cmd: Some("echo".into()), span }), - }, - Ok(WaitStatus::Signaled(_,signal,_)) => { - Ok(RshWaitStatus::Fail { code: 128 + signal as i32, cmd: Some("echo".into()), span }) - } - Ok(_) => Err(ShellError::from_execf("Unexpected waitpid result", 1, span)), - Err(err) => Err(ShellError::from_execf(&format!("Waitpid failed: {}", err), 1, span)), - } - } - } - Err(_) => Err(ShellError::from_execf("Fork failed", 1, span)), - } -} diff --git a/overlay/pkgs/rsh/rsh/src/comp.rs b/overlay/pkgs/rsh/rsh/src/comp.rs deleted file mode 100644 index 828f878..0000000 --- a/overlay/pkgs/rsh/rsh/src/comp.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crossterm::{ - cursor::{MoveTo, RestorePosition, Show}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode, size, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen} -}; -use log::info; -use rustyline::{completion::{Candidate, Completer, FilenameCompleter}, error::ReadlineError, history::{FileHistory, History}, Context, Helper, Highlighter, Hinter, Validator}; -use skim::{prelude::{SkimItemReader, SkimOptionsBuilder}, Skim}; -use std::{collections::HashSet, env, io::stdout, path::PathBuf}; - -use crate::{interp::expand, shellenv::ShellEnv}; - -#[derive(Hinter,Highlighter,Validator,Helper)] -pub struct RshHelper<'a> { - filename_comp: FilenameCompleter, - commands: Vec, // List of built-in or cached commands - shellenv: &'a ShellEnv -} - -impl<'a> RshHelper<'a> { - pub fn new(shellenv: &'a ShellEnv) -> Self { - // Prepopulate some built-in commands (could also load dynamically) - let commands = vec![ - "cd".to_string(), - "ls".to_string(), - "echo".to_string(), - "exit".to_string(), - ]; - - let mut helper = RshHelper { - filename_comp: FilenameCompleter::new(), - commands, - shellenv - }; - helper.update_commands_from_path(); - helper - } - - // Dynamically add commands (if needed, e.g., external binaries in $PATH) - fn update_commands_from_path(&mut self) { - if let Some(paths) = env::var_os("PATH") { - let mut external_commands = HashSet::new(); - for path in env::split_paths(&paths) { - if let Ok(entries) = std::fs::read_dir(path) { - for entry in entries.flatten() { - if let Ok(file_name) = entry.file_name().into_string() { - external_commands.insert(file_name); - } - } - } - } - self.commands.extend(external_commands); - } - } -} - -impl<'a> Completer for RshHelper<'a> { - type Candidate = String; - - fn complete( - &self, - line: &str, - pos: usize, - _: &Context<'_>, - ) -> Result<(usize, Vec), ReadlineError> { - let mut completions = Vec::new(); - let num_words = line.split_whitespace().count(); - - // Determine if this is a file path or a command completion - if !line.is_empty() && num_words > 1 { - let hist_path = expand::expand_var(self.shellenv, "$HIST_FILE".into()); - info!("history path in init_prompt(): {}", hist_path); - let hist_path = PathBuf::from(hist_path); - - // Delegate to FilenameCompleter for file path completion - let mut history = FileHistory::new(); - history.load(&hist_path).unwrap(); - let (start, matches) = self.filename_comp.complete(line, pos, &Context::new(&history))?; - completions.extend(matches.iter().map(|c| c.display().to_string())); - - // Invoke fuzzyfinder if there are matches - if !completions.is_empty() && completions.len() > 1 { - if let Some(selected) = skim_comp(completions.clone()) { - return Ok((start, vec![selected])); - } - } - - return Ok((start, completions)); - } - - // Command completion - let prefix = &line[..pos]; // The part of the line to match - completions.extend( - self.commands - .iter() - .filter(|cmd| cmd.starts_with(prefix)) // Match prefix - .cloned(), // Clone matched command names - ); - - // Invoke fuzzyfinder if there are matches - if completions.len() > 1 { - if let Some(selected) = skim_comp(completions.clone()) { - return Ok((0, vec![selected])); - } - } - - // Return completions, starting from the beginning of the word - Ok((0, completions)) - } -} - -pub fn skim_comp(options: Vec) -> Option { - let mut stdout = stdout(); - - // Enter the alternate screen - execute!(stdout, EnterAlternateScreen).unwrap(); - enable_raw_mode().unwrap(); - - // Get terminal dimensions - let (cols, rows) = size().unwrap(); - let width = cols.min(50); // Set floating window width - let height = rows.min(10); // Set floating window height - let start_col = 0; - let start_row = (rows - height) / 2; - - // Draw the floating window border - execute!( - stdout, - MoveTo(start_col, start_row), - Print("┌".to_string() + &"─".repeat(width as usize - 2) + "┐") - ) - .unwrap(); - - for i in 1..height - 1 { - execute!( - stdout, - MoveTo(start_col, start_row + i), - Print("│".to_string() + &" ".repeat(width as usize - 2) + "│") - ) - .unwrap(); - } - - execute!( - stdout, - MoveTo(start_col, start_row + height - 1), - Print("└".to_string() + &"─".repeat(width as usize - 2) + "┘") - ) - .unwrap(); - - // Prepare options for skim - let options_join = options.join("\n"); - let input = SkimItemReader::default().of_bufread(std::io::Cursor::new(options_join)); - - let skim_options = SkimOptionsBuilder::default() - .prompt("Select > ".to_string()) - .height("25%".to_string()) // Height in lines relative to floating window - .multi(false) - .build() - .unwrap(); - - // Run skim within the alternate screen - let selected = Skim::run_with(&skim_options, Some(input)) - .and_then(|out| out.selected_items.first().cloned()) - .map(|item| item.output().to_string()); - - // Leave the alternate screen and restore original content - execute!(stdout, Clear(ClearType::All), RestorePosition, LeaveAlternateScreen, Show).unwrap(); - disable_raw_mode().unwrap(); - - selected -} diff --git a/overlay/pkgs/rsh/rsh/src/event.rs b/overlay/pkgs/rsh/rsh/src/event.rs deleted file mode 100644 index fbe6767..0000000 --- a/overlay/pkgs/rsh/rsh/src/event.rs +++ /dev/null @@ -1,522 +0,0 @@ -use std::{fmt, io}; -use std::os::fd::BorrowedFd; - -use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; -use nix::unistd::Pid; -use tokio::sync::mpsc; -use log::{error,debug,info}; -use tokio::signal::unix::{signal, Signal, SignalKind}; - -use crate::execute::RshWaitStatus; -use crate::interp::parse::{Node, Span}; -use crate::{execute, prompt}; -use crate::shellenv::ShellEnv; - -#[derive(Debug, PartialEq)] -pub enum ShellError { - CommandNotFound(String, Span), - InvalidSyntax(String, Span), - ParsingError(String, Span), - ExecFailed(String, i32, Span), - IoError(String), - InternalError(String), -} - -impl ShellError { - pub fn from_io() -> Self { - let err = io::Error::last_os_error(); - ShellError::IoError(err.to_string()) - } - pub fn from_execf(msg: &str, code: i32, span: Span) -> Self { - ShellError::ExecFailed(msg.to_string(), code, span) - } - pub fn from_parse(msg: &str, span: Span) -> Self { - ShellError::ParsingError(msg.to_string(), span) - } - pub fn from_syntax(msg: &str, span: Span) -> Self { - ShellError::InvalidSyntax(msg.to_string(), span) - } - pub fn from_no_cmd(msg: &str, span: Span) -> Self { - ShellError::CommandNotFound(msg.to_string(), span) - } - pub fn from_internal(msg: &str) -> Self { - ShellError::InternalError(msg.to_string()) - } - // This is used in the context of functions - // To prevent the error from trying to use the span - // Of the offending command that is inside of the function - pub fn overwrite_span(&self, new_span: Span) -> Self { - match self { - ShellError::IoError(err) => ShellError::IoError(err.to_string()), - ShellError::CommandNotFound(msg,_) => ShellError::CommandNotFound(msg.to_string(),new_span), - ShellError::InvalidSyntax(msg,_) => ShellError::InvalidSyntax(msg.to_string(),new_span), - ShellError::ParsingError(msg,_) => ShellError::ParsingError(msg.to_string(),new_span), - ShellError::ExecFailed(msg,code,_) => ShellError::ExecFailed(msg.to_string(),*code,new_span), - ShellError::InternalError(msg) => ShellError::InternalError(msg.to_string()), - } - } - pub fn is_fatal(&self) -> bool { - match self { - ShellError::IoError(..) => true, - ShellError::CommandNotFound(..) => false, - ShellError::ExecFailed(..) => false, - ShellError::ParsingError(..) => false, - ShellError::InvalidSyntax(..) => false, - ShellError::InternalError(..) => false, - } - } -} - -/// A custom error type for the shell program that provides detailed error context. -/// This struct encapsulates the original input and the error, and offers methods -/// to format the error context for more informative output. -pub struct ShellErrorFull { - /// The original input string that led to the error. - input: String, - /// The shell error encountered during execution. - error: ShellError, -} - -impl ShellErrorFull { - /// Creates a new `ShellErrorFull` instance from the given input and error. - /// - /// # Arguments - /// - /// * `input` - The original input string that caused the error. - /// * `error` - The `ShellError` encountered during execution. - /// - /// # Returns - /// - /// A `ShellErrorFull` instance encapsulating the input and error. - pub fn from(input: String, error: ShellError) -> Self { - Self { input, error } - } - - /// Formats and prints the error context, including the line, column, offending input, - /// and a pointer indicating the error location. - /// - /// # Arguments - /// - /// * `span` - The span indicating the start and end positions of the error in the input. - fn format_error_context(&self, span: Option) { - if let Some(span) = span { - let (line, col) = Self::get_line_col(&self.input, span.start); - let (window, window_offset) = Self::generate_window(&self.input, line, col); - let span_diff = span.end - span.start; - let pointer = Self::get_pointer(span_diff, window_offset); - - println!(); - println!("{};{}:", line + 1, col + 1); - println!("{}", window); - println!("{}", pointer); - } - } - - /// Generates a string pointer indicating the location of the error in the input. - /// This pointer is composed of a caret (^) and tildes (~) to mark the span. - /// - /// # Arguments - /// - /// * `span_diff` - The length of the error span. - /// * `offset` - The offset position of the error within the window. - /// - /// # Returns - /// - /// A string representing the pointer to the error location. - fn get_pointer(span_diff: usize, offset: usize) -> String { - let padding = " ".repeat(offset); - let visible_span = span_diff.min(40 - offset); - - let mut pointer = String::new(); - pointer.push('^'); - if visible_span > 1 { - pointer.push_str(&"~".repeat(visible_span - 2)); - pointer.push('^'); - } - - format!("{}{}", padding, pointer) - } - - /// Determines the line and column number of a given offset within the input string. - /// - /// # Arguments - /// - /// * `input` - The original input string. - /// * `offset` - The character offset for which to find the line and column. - /// - /// # Returns - /// - /// A tuple containing the zero-based line and column numbers. - fn get_line_col(input: &str, offset: usize) -> (usize, usize) { - let mut line = 0; - let mut col = 0; - - for (i, ch) in input.chars().enumerate() { - if i == offset { - break; - } - if ch == '\n' { - line += 1; - col = 0; - } else { - col += 1; - } - } - - (line, col) - } - - /// Generates a window of text surrounding the error location for context. - /// The window is centered around the error column, with a maximum width of 40 characters. - /// - /// # Arguments - /// - /// * `input` - The original input string. - /// * `error_line` - The zero-based line number of the error. - /// * `error_col` - The zero-based column number of the error. - /// - /// # Returns - /// - /// A tuple containing a string with the window of text and the offset for the error pointer. - fn generate_window(input: &str, error_line: usize, error_col: usize) -> (String, usize) { - let window_width = 40; - let lines: Vec<&str> = input.lines().collect(); - - if lines.len() <= error_line { - return ("Error line out of range".into(), 0); - } - - let offending_line = lines[error_line]; - let line_len = offending_line.len(); - - let start = if error_col > 10 { - error_col.saturating_sub(10) - } else { - 0 - }; - let end = (start + window_width).min(line_len); - - (offending_line[start..end].to_string(), error_col - start) - } -} - -impl fmt::Display for ShellErrorFull { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.error { - ShellError::IoError(err) => { - writeln!(f, "I/O Error: {}", err)?; - self.format_error_context(None); - } - ShellError::ExecFailed(msg, code, span) => { - writeln!( - f, - "Execution failed (exit code {}): {}", - code, - msg - )?; - self.format_error_context(Some(*span)); - } - ShellError::ParsingError(msg, span) => { - writeln!(f, "Parsing error: {}", msg)?; - self.format_error_context(Some(*span)); - } - ShellError::InvalidSyntax(msg, span) => { - writeln!(f, "Syntax Error: {}", msg)?; - self.format_error_context(Some(*span)); - } - ShellError::CommandNotFound(msg, span) => { - writeln!(f, "Command not found: {}", msg)?; - self.format_error_context(Some(*span)); - } - ShellError::InternalError(msg) => { - writeln!(f, "Internal Error: {}", msg)?; - self.format_error_context(None); - } - } - writeln!(f)?; - Ok(()) - } -} - -#[derive(Debug,PartialEq)] -pub enum ShellEvent { - Prompt, - Signal(Signals), - NewAST(Node), - CatchError(ShellError), - Exit(i32) -} - -#[derive(Debug,PartialEq)] -pub enum Signals { - SIGINT, - SIGIO, - SIGPIPE, - SIGTSTP, - SIGQUIT, - SIGTERM, - SIGCHLD, - SIGHUP, - SIGWINCH, - SIGUSR1, - SIGUSR2 -} - -pub struct EventLoop<'a> { - sender: mpsc::Sender, - receiver: mpsc::Receiver, - shellenv: &'a mut ShellEnv -} - -impl<'a> EventLoop<'a> { - /// Creates a new `EventLoop` instance with a message passing channel. - /// - /// # Returns - /// A new instance of `EventLoop` with a sender and receiver for inter-task communication. - pub fn new(shellenv: &'a mut ShellEnv) -> Self { - let (sender, receiver) = mpsc::channel(100); - Self { - sender, - receiver, - shellenv - } - } - - /// Provides a clone of the `sender` channel to send events to the event loop. - /// - /// # Returns - /// A clone of the `mpsc::Sender` for sending events. - pub fn inbox(&self) -> mpsc::Sender { - self.sender.clone() - } - - /// Starts the event loop and listens for incoming events. - /// - /// This method spawns a separate task to listen for system signals using a `SignalListener`, - /// and then begins processing events from the channel. - /// - /// # Returns - /// A `Result` containing the exit code (`i32`) or a `ShellError` if an error occurs. - pub async fn listen(&mut self) -> Result { - let mut signal_listener = SignalListener::new(self.inbox()); - tokio::spawn(async move { - signal_listener.signal_listen().await - }); - self.event_listen().await - } - - /// Processes events from the event loop's receiver channel. - /// - /// This method handles different types of `ShellEvent` messages, such as prompting the user, - /// handling exit signals, processing new AST nodes, and responding to subprocess exits or errors. - /// - /// # Returns - /// A `Result` containing the exit code (`i32`) or a `ShellError` if an error occurs. - pub async fn event_listen(&mut self) -> Result { - debug!("Event loop started."); - let mut code: i32 = 0; - - // Send an initial prompt event to the loop. - self.sender.send(ShellEvent::Prompt).await.unwrap(); - while let Some(event) = self.receiver.recv().await { - match event { - ShellEvent::Prompt => { - let inbox = self.inbox(); - let mut shellenv = self.shellenv.clone(); - // Trigger the prompt logic. - tokio::spawn(async move { - prompt::prompt(inbox,&mut shellenv).await; - }); - } - ShellEvent::Exit(exit_code) => { - // Handle exit events and set the exit code. - code = exit_code; - } - ShellEvent::NewAST(tree) => { - // Log and process a new AST node. - debug!("new tree:\n {:#?}", tree); - - let mut walker = execute::NodeWalker::new(tree,self.shellenv); - match walker.start_walk() { - Ok(code) => { - info!("Last exit status: {:?}",code); - if let RshWaitStatus::Fail { code, cmd, span } = &code { - if *code == 127 { - if let Some(cmd) = cmd { - let err = ShellErrorFull::from(self.shellenv.get_last_input(),ShellError::from_no_cmd(cmd, *span)); - eprintln!("{}",err); - } - }; - }; - - self.shellenv.handle_exit_status(code); - }, - Err(e) => { - let err = ShellErrorFull::from(self.shellenv.get_last_input(),e); - eprintln!("{}",err); - } - } - } - ShellEvent::Signal(signal) => { - // Handle received signals. - if let Err(e) = self.handle_signal(signal).await { - eprintln!("{}",ShellErrorFull::from(self.shellenv.get_last_input(), e)); - } - } - ShellEvent::CatchError(err) => { - // Handle errors, exiting if fatal. - let fatal = err.is_fatal(); - let error_display = ShellErrorFull::from(self.shellenv.get_last_input(), err); - if fatal { - eprintln!("Fatal: {}", error_display); - std::process::exit(1); - } else { - println!("{}",error_display); - } - } - } - } - Ok(code) - } - async fn handle_signal(&mut self, signal: Signals) -> Result<(), ShellError> { - match signal { - Signals::SIGQUIT => { - std::process::exit(0); - } - Signals::SIGCHLD => { - let job_pid: Pid; - let status = match waitpid(None, Some(WaitPidFlag::WNOHANG)) { - Ok(WaitStatus::Exited(pid,status)) => { - job_pid = pid; - if status == 0 { - RshWaitStatus::Success { span: Span::new() } - } else { - RshWaitStatus::Fail { code: status, cmd: None, span: Span::new() } - } - } - Ok(WaitStatus::StillAlive) => return Ok(()), - Ok(WaitStatus::Signaled(pid, sig, _)) => { - job_pid = pid; - RshWaitStatus::Signaled { sig } - } - Ok(WaitStatus::Stopped(pid, sig)) => { - job_pid = pid; - RshWaitStatus::Stopped { sig } - } - Err(_) => { /* No child processes found, so */ return Ok(()) }, - _ => unimplemented!() - }; - let jobs = self.shellenv.borrow_jobs(); - for job in jobs { - let job = job.1; - if *job.pgid() == job_pid { - println!(); - job.status(status); - println!("{}",job); - break - } - } - } - _ => { }, - } - Ok(()) - } -} - -pub struct SignalListener { - outbox: mpsc::Sender, - sigint: Signal, - sigio: Signal, - sigpipe: Signal, - sigtstp: Signal, - sigquit: Signal, - sigterm: Signal, - sigchild: Signal, - sighup: Signal, - sigwinch: Signal, - sigusr1: Signal, - sigusr2: Signal, -} - -impl SignalListener { - pub fn new(outbox: mpsc::Sender) -> Self { - Self { - // Signal listeners - // TODO: figure out what to do instead of unwrapping - outbox, - sigint: signal(SignalKind::interrupt()).unwrap(), - sigio: signal(SignalKind::io()).unwrap(), - sigpipe: signal(SignalKind::pipe()).unwrap(), - sigtstp: signal(SignalKind::from_raw(20)).unwrap(), - sigquit: signal(SignalKind::quit()).unwrap(), - sigterm: signal(SignalKind::terminate()).unwrap(), - sigchild: signal(SignalKind::child()).unwrap(), - sighup: signal(SignalKind::hangup()).unwrap(), - sigwinch: signal(SignalKind::window_change()).unwrap(), - sigusr1: signal(SignalKind::user_defined1()).unwrap(), - sigusr2: signal(SignalKind::user_defined2()).unwrap(), - } - } - pub async fn signal_listen(&mut self) -> Result { - let sigint = &mut self.sigint; - let sigio = &mut self.sigio; - let sigpipe = &mut self.sigpipe; - let sigtstp = &mut self.sigtstp; - let sigquit = &mut self.sigquit; - let sigterm = &mut self.sigterm; - let sigchild = &mut self.sigchild; - let sighup = &mut self.sighup; - let sigwinch = &mut self.sigwinch; - let sigusr1 = &mut self.sigusr1; - let sigusr2 = &mut self.sigusr2; - - loop { - tokio::select! { - _ = sigint.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGINT)).await.unwrap(); - // Handle SIGINT - } - _ = sigio.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGIO)).await.unwrap(); - // Handle SIGIO - } - _ = sigpipe.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGPIPE)).await.unwrap(); - // Handle SIGPIPE - } - _ = sigtstp.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGTSTP)).await.unwrap(); - // Handle SIGPIPE - } - _ = sigquit.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGQUIT)).await.unwrap(); - // Handle SIGQUIT - } - _ = sigterm.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGTERM)).await.unwrap(); - // Handle SIGTERM - } - _ = sigchild.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGCHLD)).await.unwrap(); - // Handle SIGCHLD - } - _ = sighup.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGHUP)).await.unwrap(); - // Handle SIGHUP - } - _ = sigwinch.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGWINCH)).await.unwrap(); - // Handle SIGWINCH - } - _ = sigusr1.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGUSR1)).await.unwrap(); - // Handle SIGUSR1 - } - _ = sigusr2.recv() => { - self.outbox.send(ShellEvent::Signal(Signals::SIGUSR2)).await.unwrap(); - // Handle SIGUSR2 - } - } - } - } -} diff --git a/overlay/pkgs/rsh/rsh/src/execute.rs b/overlay/pkgs/rsh/rsh/src/execute.rs deleted file mode 100644 index 03b79be..0000000 --- a/overlay/pkgs/rsh/rsh/src/execute.rs +++ /dev/null @@ -1,1115 +0,0 @@ -use libc::{memfd_create, tcsetpgrp, MFD_CLOEXEC, STDIN_FILENO}; -use nix::sys::signal::Signal; -use nix::unistd::{close, dup, dup2, execve, execvpe, fork, getpgid, getpid, pipe, setpgid, ForkResult, Pid}; -use nix::fcntl::{open,OFlag}; -use nix::sys::stat::Mode; -use nix::sys::wait::{waitpid, WaitStatus}; -use std::fmt::{self, Display}; -use std::io; -use std::os::fd::{AsFd, AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use std::ffi::CString; -use std::collections::{HashMap, VecDeque}; -use std::path::Path; -use std::sync::{Arc, Mutex}; -use log::{info,trace}; -use glob::MatchOptions; - -use crate::builtin::{alias, cd, echo, export, jobs, pwd, set_or_unset, source, test}; -use crate::event::ShellError; -use crate::interp::helper::{self, VecDequeExtension}; -use crate::interp::{expand, parse}; -use crate::interp::token::{Redir, RedirType, Tk, TkType, WdFlags}; -use crate::interp::parse::{NdFlags, NdType, Node, Span}; -use crate::shellenv::ShellEnv; - -pub const GLOB_OPTS: MatchOptions = MatchOptions { - case_sensitive: false, - require_literal_separator: true, - require_literal_leading_dot: false -}; - -/// A wrapper struct for idiomatic and safe handling of file descriptors in Rust. -/// -/// `RustFd` provides a safer and more ergonomic interface for working with file descriptors (`RawFd`), -/// addressing common pitfalls associated with direct handling of file descriptors in systems programming. -/// -/// ### Key Features: -/// - **Automatic Resource Management**: Implements the `Drop` trait, ensuring the file descriptor is -/// automatically closed when the `RustFd` instance goes out of scope, preventing resource leaks. -/// - **Prevention of Double-Closing**: Once closed, the internal file descriptor is set to `-1`, -/// denoting a "dead" file descriptor and safeguarding against double-close errors. -/// - **Uniqueness**: `RustFd` does not implement the `Copy` or `Clone` traits, ensuring each instance -/// uniquely owns its file descriptor. This design eliminates the risk of unintended file descriptor -/// duplication and the associated lifecycle ambiguities. -/// - **Proof of Openness**: The existence of a `RustFd` instance guarantees that the file descriptor it wraps -/// is valid and open. This makes reasoning about resource states easier and more reliable. -/// -/// ### Advantages: -/// Compared to raw file descriptors from `libc` or `nix::unistd`, `RustFd` reduces the likelihood of: -/// - Resource leaks caused by missing `close` calls. -/// - Use-after-close bugs from accessing invalid file descriptors. -/// - Double-close errors. -/// -/// ### Example: -/// ```rust -/// use std::os::unix::io::RawFd; -/// -/// let fd: RawFd = open_some_file(); // Hypothetical function that opens a file and returns a RawFd. -/// let rust_fd = RustFd::new(fd); -/// // `rust_fd` ensures the file descriptor is properly closed when it goes out of scope. -/// ``` -#[derive(Hash, Eq, PartialEq, Debug)] -pub struct RustFd { - fd: RawFd, -} - -impl RustFd { - pub fn new(fd: RawFd) -> Result { - if fd < 0 { - return Err(ShellError::from_internal("Attempted to create a new RustFd from a negative FD")); - } - Ok(RustFd { fd }) - } - - /// Create a `RustFd` from a duplicate of `stdin` (FD 0) - pub fn from_stdin() -> Result { - let fd = dup(0).map_err(|_| ShellError::from_io())?; - Ok(Self { fd }) - } - - /// Create a `RustFd` from a duplicate of `stdout` (FD 1) - pub fn from_stdout() -> Result { - let fd = dup(1).map_err(|_| ShellError::from_io())?; - Ok(Self { fd }) - } - - /// Create a `RustFd` from a duplicate of `stderr` (FD 2) - pub fn from_stderr() -> Result { - let fd = dup(2).map_err(|_| ShellError::from_io())?; - Ok(Self { fd }) - } - - /// Create a `RustFd` from a type that provides an owned or borrowed FD - pub fn from_fd(fd: T) -> Result { - let raw_fd = fd.as_fd().as_raw_fd(); - if raw_fd < 0 { - return Err(ShellError::from_internal("Attempted to convert to RustFd from a negative FD")); - } - Ok(RustFd { fd: raw_fd }) - } - - /// Create a `RustFd` by consuming ownership of an FD - pub fn from_owned_fd(fd: T) -> Result { - let raw_fd = fd.into_raw_fd(); // Consumes ownership - if raw_fd < 0 { - return Err(ShellError::from_internal("Attempted to convert to RustFd from a negative FD")); - } - Ok(RustFd { fd: raw_fd }) - } - - /// Create a new `RustFd` that points to an in-memory file descriptor. In-memory file descriptors can be interacted with as though they were normal files. - pub fn new_memfd(name: &str, executable: bool) -> Result { - let c_name = CString::new(name).map_err(|_| ShellError::from_internal("Invalid name for memfd"))?; - let flags = if executable { - 0 - } else { - MFD_CLOEXEC - }; - let fd = unsafe { memfd_create(c_name.as_ptr(), flags) }; - Ok(RustFd { fd }) - } - - /// Write some bytes to the contained file descriptor - pub fn write(&self, buffer: &[u8]) -> Result<(),ShellError> { - if !self.is_valid() { - return Err(ShellError::from_internal("Attempted to write to an invalid RustFd")); - } - let result = unsafe { libc::write(self.fd, buffer.as_ptr() as *const libc::c_void, buffer.len()) }; - if result < 0 { - Err(ShellError::from_io()) - } else { - Ok(()) - } - } - - /// Wrapper for nix::unistd::pipe(), simply produces two `RustFds` that point to a read and write pipe respectfully - pub fn pipe() -> Result<(Self,Self),ShellError> { - let (r_pipe,w_pipe) = pipe().map_err(|_| ShellError::from_io())?; - let r_fd = RustFd::from_owned_fd(r_pipe)?; - let w_fd = RustFd::from_owned_fd(w_pipe)?; - Ok((r_fd,w_fd)) - } - - /// Produce a `RustFd` that points to the same resource as the 'self' `RustFd` - pub fn dup(&self) -> Result { - if !self.is_valid() { - return Err(ShellError::from_internal("Attempted to dup an invalid fd")); - } - let new_fd = dup(self.fd).map_err(|_| ShellError::from_io())?; - Ok(RustFd { fd: new_fd }) - } - - /// A wrapper for nix::unistd::dup2(), 'self' is duplicated to the given target file descriptor. - pub fn dup2(&self, target: &T) -> Result<(),ShellError> { - let target_fd = target.as_raw_fd(); - if self.fd == target_fd { - // Nothing to do here - return Ok(()) - } - if !self.is_valid() || target_fd < 0 { - return Err(ShellError::from_io()); - } - - dup2(self.fd, target_fd).map_err(|_| ShellError::from_io())?; - Ok(()) - } - - /// Open a file using a file descriptor, with the given OFlags and Mode bits - pub fn open(path: &Path, flags: OFlag, mode: Mode) -> Result { - let file_fd: RawFd = open(path, flags, mode).map_err(|_| ShellError::from_io())?; - Ok(Self { fd: file_fd }) - } - - pub fn close(&mut self) -> Result<(),ShellError> { - if !self.is_valid() { - return Err(ShellError::from_internal("Attempted to close an invalid RustFd")); - } - close(self.fd).map_err(|_| ShellError::from_io())?; - self.fd = -1; - Ok(()) - } - - pub fn mk_shared(self) -> Arc> { - Arc::new(Mutex::new(self)) - } - - pub fn is_valid(&self) -> bool { - self.fd > 0 - } -} - -impl Display for RustFd { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.fd) - } -} - -impl Drop for RustFd { - fn drop(&mut self) { - if self.fd >= 0 { - self.close().ok(); - } - } -} - -impl AsRawFd for RustFd { - fn as_raw_fd(&self) -> RawFd { - self.fd - } -} - -impl IntoRawFd for RustFd { - fn into_raw_fd(self) -> RawFd { - let fd = self.fd; - std::mem::forget(self); - fd - } -} - -impl FromRawFd for RustFd { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - RustFd { fd } - } -} - -#[derive(Debug)] -pub struct ProcIO { - pub stdin: Option>>, - pub stdout: Option>>, - pub stderr: Option>>, - pub backup: HashMap -} - -impl ProcIO { - pub fn new() -> Self { - Self { stdin: None, stdout: None, stderr: None, backup: HashMap::new() } - } - pub fn from(stdin: Option>>, stdout: Option>>, stderr: Option>>) -> Self { - Self { - stdin, - stdout, - stderr, - backup: HashMap::new(), - } - } - pub fn close_all(&mut self) -> Result<(),ShellError> { - if let Some(fd) = &self.stdin { - fd.lock().unwrap().close()?; - } - if let Some(fd) = &self.stdout { - fd.lock().unwrap().close()?; - } - if let Some(fd) = &self.stderr { - fd.lock().unwrap().close()?; - } - Ok(()) - } - pub fn backup_fildescs(&mut self) -> Result<(), ShellError> { - let mut backup = HashMap::new(); - // Get duped file descriptors - let dup_in = RustFd::from_stdin()?; - let dup_out = RustFd::from_stdout()?; - let dup_err = RustFd::from_stderr()?; - // Store them in a hashmap - backup.insert(0,dup_in); - backup.insert(1,dup_out); - backup.insert(2,dup_err); - self.backup = backup; - Ok(()) - } - pub fn restore_fildescs(&mut self) -> Result<(), ShellError> { - // Get duped file descriptors from hashmap - if !self.backup.is_empty() { - // Dup2 to restore file descriptors - if let Some(mut saved_in) = self.backup.remove(&0) { - saved_in.dup2(&0)?; - saved_in.close()?; - } - if let Some(mut saved_out) = self.backup.remove(&1) { - saved_out.dup2(&1)?; - saved_out.close()?; - } - if let Some(mut saved_err) = self.backup.remove(&2) { - saved_err.dup2(&2)?; - saved_err.close()?; - } - } - Ok(()) - } - pub fn do_plumbing(&mut self) -> Result<(), ShellError> { - if let Some(ref mut err_pipe) = self.stderr { - let mut pipe = err_pipe.lock().unwrap(); - pipe.dup2(&2)?; - pipe.close()?; - } - // Redirect stdout - if let Some(ref mut w_pipe) = self.stdout { - let mut pipe = w_pipe.lock().unwrap(); - pipe.dup2(&1)?; - pipe.close()?; - } - - // Redirect stdin - if let Some(ref mut r_pipe) = self.stdin { - let mut pipe = r_pipe.lock().unwrap(); - pipe.dup2(&0)?; - pipe.close()?; - } - Ok(()) - } - - pub fn try_clone(&self) -> Self { - ProcIO::from(self.stdin.clone(),self.stdout.clone(),self.stderr.clone()) - } -} - -impl Default for ProcIO { - fn default() -> Self { - Self::new() - } -} - -#[derive(PartialEq,Debug,Clone)] -pub enum RshWaitStatus { - Success { span: Span }, - Fail { code: i32, cmd: Option, span: Span }, - Signaled { sig: Signal }, - Stopped { sig: Signal }, - Terminated { signal: i32 }, - Continued, - Running, - Killed { signal: i32 }, - TimeOut, - - // These wait statuses are returned by builtins like `return` and `break` - SIGRETURN, // Return from a function - SIGCONT, // Restart a loop from the beginning - SIGBREAK, // Break a loop - SIGRSHEXIT // Internal call to exit early -} - -impl Display for RshWaitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RshWaitStatus::Success { .. } => write!(f, "done"), - RshWaitStatus::Fail { code, .. } => write!(f, "exit {}", code), - RshWaitStatus::Signaled { sig } => write!(f, "exit {}", sig), - RshWaitStatus::Stopped { sig } => write!(f, "stopped {}", sig), - RshWaitStatus::Terminated { signal } => write!(f, "terminated {}", signal), - RshWaitStatus::Continued => write!(f, "continued"), - RshWaitStatus::Running => write!(f, "running"), - RshWaitStatus::Killed { signal } => write!(f, "killed {}", signal), - RshWaitStatus::TimeOut => write!(f, "time out"), - _ => write!(f, "{:?}",self) - } - } -} - -impl RshWaitStatus { - pub fn new() -> Self { - RshWaitStatus::Success { span: Span::new() } - } - pub fn s(span: Span) -> Self { - RshWaitStatus::Success { span } - } - pub fn f(code: i32, cmd: Option, span: Span) -> Self { - RshWaitStatus::Fail { code, cmd, span } - } -} - -impl Default for RshWaitStatus { - fn default() -> Self { - RshWaitStatus::new() - } -} - - -pub struct NodeWalker<'a> { - ast: Node, - shellenv: &'a mut ShellEnv, -} - -impl<'a> NodeWalker<'a> { - pub fn new(ast: Node, shellenv: &'a mut ShellEnv) -> Self { - Self { - ast, - shellenv - } - } - pub fn start_walk(&mut self) -> Result { - info!("Going on a walk..."); - // Save file descs just in case - let saved_in = RustFd::from_stdin()?; - let saved_out = RustFd::from_stdout()?; - let saved_err = RustFd::from_stderr()?; - let mut exit_status = RshWaitStatus::new(); - let mut nodes; - if let NdType::Root { ref mut deck } = self.ast.nd_type { - nodes = std::mem::take(deck); - } else { unreachable!() } - while let Some(node) = nodes.pop_front() { - exit_status = self.walk(node, ProcIO::new(), false)?; - } - // Restore file descs just in case - saved_in.dup2(&0)?; - saved_out.dup2(&1)?; - saved_err.dup2(&2)?; - Ok(exit_status) - } - - fn walk(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let last_status; - match node.nd_type { - NdType::Command { ref argv } | NdType::Builtin { ref argv } => { - let mut node = node.clone(); - let command_name = argv.front().unwrap(); - let not_from_alias = !command_name.flags().contains(WdFlags::FROM_ALIAS); - let is_not_command_builtin = command_name.text() != "command"; - if not_from_alias && is_not_command_builtin { - node = expand::expand_alias(self.shellenv, node.clone())?; - } - if let Some(_func) = self.shellenv.get_function(command_name.text()) { - last_status = self.handle_function(node, io, in_pipe)?; - } else if !matches!(node.nd_type, NdType::Command {..} | NdType::Builtin {..}) { - // If the resulting alias expansion returns a root node - // then walk the resulting sub-tree - return self.walk_root(node, None, io,in_pipe) - } else { - match node.nd_type { - NdType::Command {..} => { - trace!("Found command: {:?}",node); - last_status = self.handle_command(node, io, in_pipe)?; - } - NdType::Builtin {..} => { - last_status = self.handle_builtin(node, io, in_pipe)?; - } - _ => unreachable!() - } - } - } - NdType::Pipeline {..} => { - last_status = self.handle_pipeline(node, io)?; - } - NdType::Chain {..} => { - last_status = self.handle_chain(node)?; - } - NdType::If {..} => { - last_status = self.handle_if(node,io,in_pipe)?; - } - NdType::For {..} => { - last_status = self.handle_for(node,io,in_pipe)?; - } - NdType::Loop {..} => { - last_status = self.handle_loop(node,io,in_pipe)?; - } - NdType::Case {..} => { - last_status = self.handle_case(node,io,in_pipe)?; - } - NdType::Select {..} => { - todo!("handle select") - } - NdType::Subshell {..} => { - last_status = self.handle_subshell(node,io,in_pipe)?; - } - NdType::FuncDef {..} => { - last_status = self.handle_func_def(node)?; - } - NdType::Assignment {..} => { - last_status = self.handle_assignment(node)?; - } - NdType::Cmdsep => { - last_status = RshWaitStatus::new(); - } - _ => unimplemented!("Support for node type `{:?}` is not yet implemented",node.nd_type) - } - Ok(last_status) - } - - fn walk_root(&mut self, mut node: Node, break_condition: Option, io: ProcIO, in_pipe: bool) -> Result { - let mut last_status = RshWaitStatus::new(); - if !node.redirs.is_empty() { - node = parse::propagate_redirections(node)?; - } - if let NdType::Root { deck } = node.nd_type { - for node in &deck { - last_status = self.walk(node.clone(), io.try_clone(), in_pipe)?; - if let Some(condition) = break_condition { - match condition { - true => { - if let RshWaitStatus::Fail {..} = last_status { - break - } - } - false => { - if let RshWaitStatus::Success {..} = last_status { - break - } - } - } - } - } - } - Ok(last_status) - } - - fn handle_func_def(&mut self, node: Node) -> Result { - let last_status = RshWaitStatus::new(); - if let NdType::FuncDef { name, body } = node.nd_type { - self.shellenv.set_function(name, body)?; - Ok(last_status) - } else { unreachable!() } - } - - fn handle_case(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let span = node.span(); - if let NdType::Case { input_var, cases } = node.nd_type { - for case in cases { - let (pat,body) = case; - if pat == input_var.text() { - return self.walk_root(body, None, io, in_pipe) - } - } - Ok(RshWaitStatus::Fail { code: 1, cmd: None, span }) - } else { unreachable!() } - } - - /// For loops in bash can have multiple loop variables, e.g. `for a b c in 1 2 3 4 5 6` - /// In this case, loop_vars are also iterated through, e.g. a = 1, b = 2, c = 3, etc - /// Here, we use the modulo operator to figure out which variable to set on each iteration. - fn handle_for(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let mut last_status = RshWaitStatus::new(); - let body_io = ProcIO::from(None, io.stdout, io.stderr); - let redirs = node.get_redirs()?; - self.handle_redirs(redirs.into())?; - - if let NdType::For { loop_vars, mut loop_arr, loop_body } = node.nd_type { - let var_count = loop_vars.len(); - let mut var_index = 0; - let mut iteration_count = 0; - - let mut arr_buffer = VecDeque::new(); - loop_arr.map_rotate(|elem| { - for token in expand::expand_token(self.shellenv, elem) { - arr_buffer.push_back(token); - } - }); - loop_arr.extend(arr_buffer.drain(..)); - - - while !loop_arr.is_empty() { - - // Get the current value from the array - let current_value = loop_arr.pop_front().unwrap().text().to_string(); - - // Set the current variable to the current value - let current_var = loop_vars[var_index].text().to_string(); - self.shellenv.set_variable(current_var, current_value); - - // Update the variable index for the next iteration - iteration_count += 1; - var_index = iteration_count % var_count; - - // Execute the body of the loop - last_status = self.walk_root(*loop_body.clone(), None, body_io.try_clone(),in_pipe)?; - } - } - - Ok(last_status) - } - - fn handle_loop(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let mut last_status = RshWaitStatus::new(); - let cond_io = ProcIO::from(io.stdin, None, None); - let body_io = ProcIO::from(None, io.stdout, io.stderr); - - if let NdType::Loop { condition, logic } = node.nd_type { - let cond = *logic.condition; - let body = *logic.body; - // TODO: keep an eye on this; cloning in the loop body may be too expensive - loop { - // Evaluate the loop condition - let condition_status = self.walk_root(cond.clone(),Some(condition),cond_io.try_clone(),in_pipe)?; - - match condition { - true => { - if !matches!(condition_status,RshWaitStatus::Success {..}) { - break; // Exit for a `while` loop when condition is false - } - } - false => { - if matches!(condition_status,RshWaitStatus::Success {..}) { - break; // Exit for an `until` loop when condition is true - } - } - } - - // Execute the body of the loop - last_status = self.walk_root(body.clone(),None,body_io.try_clone(),in_pipe)?; - - // Check for break or continue signals - // match last_status { - // RshWaitStatus::Break => return Ok(RshWaitStatus::Break), - // RshWaitStatus::Continue => continue, - // _ => {} - // } - } - Ok(last_status) - } else { - Err(ShellError::from_syntax( - "Expected a loop node in handle_loop", - node.span() - )) - } - } - - fn handle_if(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let mut last_result = RshWaitStatus::new(); - let cond_io = ProcIO::from(io.stdin, None, None); - let body_io = ProcIO::from(None, io.stdout, io.stderr); - - if let NdType::If { mut cond_blocks, else_block } = node.nd_type { - while let Some(block) = cond_blocks.pop_front() { - let cond = *block.condition; - let body = *block.body; - last_result = self.walk_root(cond,Some(false),cond_io.try_clone(),in_pipe)?; - if let RshWaitStatus::Success {..} = last_result { - return self.walk_root(body,None,body_io,in_pipe) - } - } - if let Some(block) = else_block { - return self.walk_root(*block,None,body_io,in_pipe) - } - } - Ok(last_result) - } - - fn handle_chain(&mut self, node: Node) -> Result { - let mut last_status = RshWaitStatus::new(); - - if let NdType::Chain { left, right, op } = node.nd_type { - match self.walk(*left, ProcIO::new(), false)? { - RshWaitStatus::Success {..} => { - if let NdType::And = op.nd_type { - last_status = self.walk(*right, ProcIO::new(), false)?; - } - } - _ => { - if let NdType::Or = op.nd_type { - last_status = self.walk(*right, ProcIO::new(), false)?; - } - } - } - - } - Ok(last_status) - } - - - fn handle_assignment(&mut self, node: Node) -> Result { - let span = node.span(); - if let NdType::Assignment { name, value } = node.nd_type { - let value = value.unwrap_or_default(); - self.shellenv.set_variable(name, value); - } - Ok(RshWaitStatus::s(span)) - } - - fn handle_builtin(&mut self, mut node: Node, io: ProcIO, in_pipe: bool) -> Result { - let argv = expand::expand_arguments(self.shellenv, &mut node)?; - match argv.first().unwrap().text() { - "echo" => echo(self.shellenv, node, io,in_pipe), - "set" => set_or_unset(self.shellenv, node, true), - "jobs" => jobs(self.shellenv, node, io, in_pipe), - "unset" => set_or_unset(self.shellenv, node, false), - "source" => source(self.shellenv, node), - "cd" => cd(self.shellenv, node), - "pwd" => pwd(self.shellenv, node.span()), - "alias" => alias(self.shellenv, node), - "export" => export(self.shellenv, node), - "[" | "test" => test(node.get_argv()?.into()), - "builtin" => { - // This one allows you to safely wrap builtins in aliases/functions - if let NdType::Builtin { mut argv } = node.nd_type { - argv.pop_front(); - node.nd_type = NdType::Builtin { argv }; - self.handle_builtin(node, io, in_pipe) - } else { unreachable!() } - } - _ => unimplemented!("found this builtin: {}",argv[0].text()) - } - } - - - - /// Extracts arguments and redirections from an AST node. - /// - /// This function processes an AST node of type `Command`, `Builtin`, or `Function` - /// and extracts the arguments and redirections from it. The arguments are converted - /// to `CString` and collected into a vector, while the redirections are collected - /// into a `VecDeque`. - /// - /// # Arguments - /// - /// * `node` - The AST node to extract arguments and redirections from. - /// - /// # Returns - /// - /// A tuple containing: - /// * A vector of `CString` representing the extracted arguments. - /// * A `VecDeque` of `Tk` representing the extracted redirections. - /// - /// # Panics - /// - /// This function will panic if the provided `node` is not of type `Command`, `Builtin`, - /// or `Function`. - /// - /// # Example - /// - /// let node = Node::Command { argv: vec![...].into_iter().collect() }; - /// let (args, redirs) = shell.extract_args(node); - /// - /// # Notes - /// - /// - The function uses `trace!` and `debug!` macros for logging purposes. - /// - Variable substitutions (`$var`) are resolved using the `shellenv`'s `get_variable` method. - /// - If a variable substitution is not found, an empty `CString` is added to the arguments. - fn handle_redirs(&self, mut redirs: VecDeque) -> Result, ShellError> { - let mut fd_queue: VecDeque = VecDeque::new(); - let mut fd_dupes: VecDeque = VecDeque::new(); - - while let Some(redir_tk) = redirs.pop_front() { - if let NdType::Redirection { ref redir } = redir_tk.nd_type { - let Redir { fd_source, op, fd_target, file_target } = &redir; - if fd_target.is_some() { - fd_dupes.push_back(redir.clone()); - } else if let Some(file_path) = file_target { - let source_fd = RustFd::new(*fd_source)?; - let flags = match op { - RedirType::Input => OFlag::O_RDONLY, - RedirType::Output => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC, - RedirType::Append => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_APPEND, - _ => unimplemented!("Heredocs and herestrings are not implemented yet."), - }; - let mut file_fd: RustFd = RustFd::open(Path::new(file_path.text()), flags, Mode::from_bits(0o644).unwrap())?; - info!("Duping file FD {} to FD {}", file_fd, fd_source); - file_fd.dup2(&source_fd)?; - file_fd.close()?; - fd_queue.push_back(source_fd); - } - } - } - - while let Some(dupe_redir) = fd_dupes.pop_front() { - let Redir { fd_source, op: _, fd_target, file_target: _ } = dupe_redir; - let mut target_fd = RustFd::new(fd_target.unwrap())?; - let source_fd = RustFd::new(fd_source)?; - target_fd.dup2(&source_fd)?; - target_fd.close()?; - fd_queue.push_back(source_fd); - } - - Ok(fd_queue) - } - - fn handle_subshell(&mut self, mut node: Node, mut io: ProcIO, in_pipe: bool) -> Result { - expand::expand_arguments(self.shellenv, &mut node)?; - let redirs = node.redirs; - let mut shellenv = self.shellenv.clone(); - shellenv.clear_pos_parameters(); - if let NdType::Subshell { mut body, mut argv } = node.nd_type { - let mut c_argv = vec![CString::new("subshell").unwrap()]; - while let Some(tk) = argv.pop_front() { - let c_arg = CString::new(tk.text()).unwrap(); - c_argv.push(c_arg); - } - if !body.starts_with("#!") { - let interpreter = std::env::current_exe().unwrap(); - let mut shebang = "#!".to_string(); - shebang.push_str(interpreter.to_str().unwrap()); - shebang.push('\n'); - shebang.push_str(&body); - body = shebang; - } else if body.starts_with("#!") && !body.contains('/') { - let mut command = String::new(); - let mut body_chars = body.chars().collect::>(); - body_chars.pop_front(); body_chars.pop_front(); // Skip the '#!' - while let Some(ch) = body_chars.pop_front() { - if matches!(ch, ' ' | '\t' | '\n' | ';') { - while body_chars.front().is_some_and(|ch| matches!(ch, ' ' | '\t' | '\n' | ';')) { - body_chars.pop_front(); - } - body = body_chars.iter().collect::(); - break - } else { - command.push(ch); - } - } - if let Some(path) = helper::which(self.shellenv,&command) { - let path = format!("{}{}{}","#!",path,'\n'); - body = format!("{}{}",path,body); - } - } - // Step 1: Create a pipe - let memfd = RustFd::new_memfd("subshell", true)?; - memfd.write(body.as_bytes())?; - io.backup_fildescs()?; - io.do_plumbing()?; - - if in_pipe { - let mut open_fds: VecDeque = VecDeque::new(); - if !redirs.is_empty() { - open_fds.extend(self.handle_redirs(redirs)?); - } - let fd_path = format!("/proc/self/fd/{}", memfd); - let fd_path = CString::new(fd_path).unwrap(); - let env = shellenv.get_cvars(); - execve(&fd_path, &c_argv, &env).unwrap(); - } else { - match unsafe { fork() } { - Ok(ForkResult::Child) => { - let mut open_fds: VecDeque = VecDeque::new(); - if !redirs.is_empty() { - open_fds.extend(self.handle_redirs(redirs)?); - } - let fd_path = format!("/proc/self/fd/{}", memfd); - let fd_path = CString::new(fd_path).unwrap(); - let env = shellenv.get_cvars(); - execve(&fd_path, &c_argv, &env).unwrap(); - unreachable!(); - } - Ok(ForkResult::Parent { child }) => { - io.restore_fildescs()?; - nix::sys::wait::waitpid(child, None).unwrap(); - } - Err(_) => {} - } - } - } else { unreachable!() } - Ok(RshWaitStatus::new()) - } - - - fn handle_pipeline(&mut self, node: Node, io: ProcIO) -> Result { - // Step 1: Flatten the pipeline tree into a sequential list - let mut flattened_pipeline; - if let NdType::Pipeline { ref left, ref right, .. } = node.nd_type { - flattened_pipeline = helper::flatten_pipeline(*left.clone(), *right.clone(), VecDeque::new()); - } else { - unreachable!(); // Only call handle_pipeline with pipeline nodes - } - - // Here we are checking to see if the final token of the final command is a background '&' operator - let background = flattened_pipeline.back() - .is_some_and(|node| node.flags.contains(NdFlags::BACKGROUND)); - - let span = node.span(); - - // Step 2: Iterate through the flattened pipeline - let mut prev_read_pipe: Option = None; // Previous read pipe - let mut last_status = Ok(RshWaitStatus::new()); - let mut pgid: Option = None; - let mut cmds = vec![]; - let mut pids = vec![]; - - while let Some(command) = flattened_pipeline.pop_front() { - // Create a new pipe for this command, except for the last one - let (r_pipe, mut w_pipe) = if !flattened_pipeline.is_empty() { - let (r_pipe, w_pipe) = RustFd::pipe()?; - (Some(r_pipe),Some(w_pipe)) - } else { - (None, None) // Last command doesn't need a pipe - }; - - - let argv = command.get_argv()?; - if let NdType::Subshell {..} = command.nd_type { - // We are in a subshell - cmds.push("subshell".into()); - } else { - let cmd_name = argv.first().unwrap().text().to_string(); - cmds.push(cmd_name); - } - - - // Set up IO for the current command - let current_io = ProcIO::from( - prev_read_pipe.take().map(|pipe| pipe.mk_shared()), // stdin from the previous pipe - w_pipe.take().map(|pipe| pipe.mk_shared()), // stdout to the write pipe - io.stderr.clone(), // Shared stderr - ); - - // Fork the process for this command - match unsafe { fork() } { - Ok(ForkResult::Child) => { - // In the child process - if let Some(mut read_pipe) = prev_read_pipe { - read_pipe.close()?; // Close the previous read pipe - } - if let Some(ref mut write_pipe) = w_pipe { - write_pipe.close()?; // Close the current write pipe - } - self.walk(command, current_io, true)?; - std::process::exit(0); - } - Ok(ForkResult::Parent { child }) => { - // In the parent process - - pids.push(child); - if let Some(mut read_pipe) = prev_read_pipe { - read_pipe.close()?; // Close the previous read pipe in the parent - } - if let Some(mut write_pipe) = w_pipe { - write_pipe.close()?; // Close the current write pipe in the parent - } - - if pgid.is_none() { - pgid = Some(child) - } - setpgid(child, pgid.unwrap()).map_err(|_| ShellError::from_internal("Failed to set pgid in pipeline"))?; - - // Update the read pipe for the next command - prev_read_pipe = r_pipe; - - // Wait for the child process if it's the last command - if flattened_pipeline.is_empty() { - if background { - self.shellenv.new_job(pids,cmds,pgid.unwrap()); - return last_status; - } - last_status = match waitpid(child, None) { - Ok(WaitStatus::Exited(_, code)) => match code { - 0 => Ok(RshWaitStatus::Success { span }), - _ => Ok(RshWaitStatus::Fail { code, cmd: None, span }), - }, - Ok(WaitStatus::Signaled(_, signal, _)) => { - Ok(RshWaitStatus::Fail { code: 128 + signal as i32, cmd: None, span }) - } - Ok(_) => Err(ShellError::from_execf("Unexpected waitpid result in pipeline", 1, span)), - Err(err) => Err(ShellError::from_execf(&format!("Pipeline waitpid failed: {}", err), 1, span)), - }; - } - } - Err(e) => { - return Err(ShellError::from_execf( - "Execution failed in pipeline", - e.to_string().parse::().unwrap_or(1), - span, - )); - } - } - } - - // Step 3: Return the status of the last command - last_status - } - - fn handle_function(&mut self, node: Node, io: ProcIO, in_pipe: bool) -> Result { - let node_span = node.span(); - if let NdType::Command { mut argv } | NdType::Builtin { mut argv } = node.nd_type { - let func_name = argv.pop_front().unwrap(); - let mut pos_params = vec![]; - while let Some(token) = argv.pop_front() { - pos_params.push(token.text().to_string()) - } - // Unwrap is safe here because we checked for Some() in self.walk() - let mut func = self.shellenv.get_function(func_name.text()).unwrap(); - for redir in node.redirs { - func.redirs.push_back(redir.clone()); - } - let saved_parameters = self.shellenv.parameters.clone(); - self.shellenv.clear_pos_parameters(); - for (index,param) in pos_params.into_iter().enumerate() { - self.shellenv.set_parameter((index + 1).to_string(), param); - } - let mut sub_walker = NodeWalker::new(*func.clone(), self.shellenv); - - // Returns exit status or shell error - let mut result = sub_walker.walk_root(*func, None, io,in_pipe); - if let Err(ref mut e) = result { - // Use the span of the function call rather than the stuff inside the function - *e = e.overwrite_span(node_span) - } - self.shellenv.parameters = saved_parameters; - result - } else { unreachable!() } - } - - - fn handle_command(&mut self, mut node: Node, mut io: ProcIO, in_pipe: bool) -> Result { - let argv = expand::expand_arguments(self.shellenv, &mut node)?; - let argv = argv - .iter() - .map(|arg| CString::new(arg.text()).unwrap()) - .collect::>(); - let redirs = node.get_redirs()?; - let span = node.span(); - // Let's expand aliases here - if let NdType::Command { ref argv } = node.nd_type { - // If the shellenv is allowing aliases, and the token is not from an expanded alias - if !argv.front().unwrap().flags().contains(WdFlags::FROM_ALIAS) { - let node = expand::expand_alias(self.shellenv, node.clone())?; - - if !matches!(node.nd_type, NdType::Command {..}) { - // If the resulting alias expansion does not return this node - // then walk the resulting sub-tree - return self.walk_root(node, None, io,in_pipe) - } - } - // Check if autocd is enabled - if self.shellenv.shopts.get("autocd").is_some_and(|opt| *opt > 0) && argv.len() == 1 { - // Check if argv[0] looks like a path, and exists at a path - let path_candidate = argv.front().unwrap(); - let is_relative_path = path_candidate.text().starts_with('.'); - let contains_slash = path_candidate.text().contains('/'); - let path_exists = Path::new(path_candidate.text()).is_dir(); - - if (is_relative_path || contains_slash) && path_exists { - // Produce a new Builtin {..} node with cd as the command, and call self.walk() on it - let cd_token = Tk::new("cd".into(),span,path_candidate.flags()); - let mut autocd_argv = argv.clone(); - autocd_argv.push_front(cd_token); - let autocd = Node { - nd_type: NdType::Builtin { argv: autocd_argv }, - span, - flags: node.flags, - redirs: node.redirs - }; - return self.walk(autocd, io,in_pipe) - - } - } - } - io.backup_fildescs()?; // Save original stdin, stdout, and stderr - io.do_plumbing()?; // Route pipe logic using fildescs - - // Prepare the execvpe arguments - let cmd = argv[0].clone().into_string().unwrap(); - let command = &argv[0]; - let envp = self.shellenv.get_cvars(); - - // Now let's tell the child process what to do - let child_instruction = || -> Result<(),ShellError> { - // Handle redirections - let mut open_fds: VecDeque = VecDeque::new(); - if !redirs.is_empty() { - open_fds.extend(self.handle_redirs(redirs.clone().into())?); - } - - // Execute the command - let Err(_) = execvpe(command, &argv, &envp); - std::process::exit(127); - }; - if in_pipe { - // We are currently in a child process, - // So we will execute the instructions here - child_instruction()?; - unreachable!() // If the thread returns, something has gone horribly wrong - } else { - match unsafe { fork() } { - Ok(ForkResult::Child) => { - child_instruction()?; - unreachable!() - } - Ok(ForkResult::Parent { child }) => { - if node.flags.contains(NdFlags::BACKGROUND) { - // Background job logic - // Set process group id here - setpgid(child, child).map_err(|_| ShellError::from_io())?; - // Add job to the job table - self.shellenv.new_job(vec![child], vec![cmd], child); - // Restore saved file descriptors - io.restore_fildescs()?; - Ok(RshWaitStatus::Success { span }) - } else { - // Foreground job logic - // Here we get a lock on the current stdin file descriptor - let stdin_fd = &io.try_clone().stdin.unwrap_or(RustFd::from_stdin().unwrap().mk_shared()); - let stdin_fd = stdin_fd.lock().unwrap(); - - //And then get the pgid of rsh - let self_pid = getpid(); - let self_pgid = getpgid(Some(self_pid)).map_err(|_| ShellError::from_io())?; - - // Now surrender control of the terminal to the child process - // Required for interactive programs, i.e. running another shell program - unsafe { tcsetpgrp(stdin_fd.as_raw_fd(), child.into()) }; - let status = match waitpid(child, None) { - Ok(WaitStatus::Exited(_, code)) => match code { - 0 => Ok(RshWaitStatus::Success { span }), - _ => Ok(RshWaitStatus::Fail { code, cmd: Some(cmd), span }), - }, - Ok(WaitStatus::Signaled(_,signal,_)) => { - Ok(RshWaitStatus::Signaled { sig: signal }) - } - Ok(_) => Err(ShellError::from_execf("Unexpected waitpid result", 1, span)), - Err(err) => Err(ShellError::from_execf(&format!("Waitpid failed: {}", err), 1, span)), - }; - - // Regain control of the terminal after child process ends - unsafe { tcsetpgrp(stdin_fd.as_raw_fd(), self_pgid.into()) }; - - // Drop the lock on the stdin file descriptor - drop(stdin_fd); - - // Restore saved file descriptors - io.restore_fildescs()?; - - // Return wait status - status - } - } - Err(_) => Err(ShellError::from_io()) - } - } - } -} diff --git a/overlay/pkgs/rsh/rsh/src/file.output b/overlay/pkgs/rsh/rsh/src/file.output deleted file mode 100644 index 963cf0f..0000000 --- a/overlay/pkgs/rsh/rsh/src/file.output +++ /dev/null @@ -1,7 +0,0 @@ -[?2004h projects/rust/rsh/src/ -$>   projects/rust/rsh/src/ -$> export PS1=">> " [?2004l -[?2004h projects/rust/rsh/src/ -$> [?2004l -[?2004h projects/rust/rsh/src/ -$> [?2004l diff --git a/overlay/pkgs/rsh/rsh/src/file.rsh b/overlay/pkgs/rsh/rsh/src/file.rsh deleted file mode 100755 index 6be1226..0000000 --- a/overlay/pkgs/rsh/rsh/src/file.rsh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo hellothere$@ diff --git a/overlay/pkgs/rsh/rsh/src/file.txt b/overlay/pkgs/rsh/rsh/src/file.txt deleted file mode 100644 index 252d377..0000000 --- a/overlay/pkgs/rsh/rsh/src/file.txt +++ /dev/null @@ -1,3 +0,0 @@ -PROCESSED: DATA 0 -PROCESSED: DATA 1 -PROCESSED: DATA 2 diff --git a/overlay/pkgs/rsh/rsh/src/interp/debug.rs b/overlay/pkgs/rsh/rsh/src/interp/debug.rs deleted file mode 100644 index 130c774..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/debug.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::fmt; - -use crate::interp; -use interp::parse::{NdType,Node}; - -impl fmt::Display for Node { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn write_tree(node: &Node, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { - let prefix = "| ".repeat(indent); - writeln!(f, "{}Node (span: {:?})", prefix, node.span)?; - - match &node.nd_type { - NdType::Root { deck } => { - writeln!(f, "{}Root", prefix)?; - for node in deck { - write_tree(node, f, indent + 1)?; - } - } - NdType::If { cond_blocks, else_block } => { - writeln!(f, "{}If", prefix)?; - for block in cond_blocks { - writeln!(f, "{}|--- Condition", prefix)?; - write_tree(&block.condition, f, indent + 2)?; - writeln!(f, "{}|--- Body", prefix)?; - write_tree(&block.body, f, indent + 2)?; - } - if let Some(else_block) = else_block { - writeln!(f, "{}|--- Else", prefix)?; - write_tree(else_block, f, indent + 1)?; - } - } - NdType::For { loop_vars, loop_arr, loop_body } => { - let var_texts: Vec<_> = loop_vars.iter().map(|var| var.text()).collect(); - let arr_texts: Vec<_> = loop_arr.iter().map(|arr| arr.text()).collect(); - writeln!(f, "{}For", prefix)?; - writeln!(f, "{}|--- Loop Vars: {:?}", prefix, var_texts)?; - writeln!(f, "{}|--- Loop Array: {:?}", prefix, arr_texts)?; - writeln!(f, "{}|--- Body", prefix)?; - write_tree(loop_body, f, indent + 1)?; - } - NdType::Loop { condition, logic } => { - writeln!(f, "{}Loop (condition: {})", prefix, condition)?; - writeln!(f, "{}|--- Condition", prefix)?; - write_tree(&logic.condition, f, indent + 1)?; - writeln!(f, "{}|--- Body", prefix)?; - write_tree(&logic.body, f, indent + 1)?; - } - NdType::Case { input_var, cases } => { - writeln!(f, "{}Case (input: {:?})", prefix, input_var.text())?; - for case in cases { - writeln!(f, "{}|--- Pattern: {:?}", prefix, case.0)?; - write_tree(case.1, f, indent + 1)?; - } - } - NdType::Select { select_var, opts, body } => { - let opts_texts: Vec<_> = opts.iter().map(|opt| opt.text()).collect(); - writeln!( - f, - "{}Select (input: {:?}, options: {:?})", - prefix, select_var.text(), opts_texts - )?; - write_tree(body, f, indent + 1)?; - } - NdType::Pipeline { left, right, both: _ } => { - writeln!(f, "{}Pipeline", prefix)?; - writeln!(f, "{}|--- Left", prefix)?; - write_tree(left, f, indent + 1)?; - writeln!(f, "{}|--- Right", prefix)?; - write_tree(right, f, indent + 1)?; - } - NdType::Chain { left, right, op } => { - writeln!(f, "{}Chain (operator span: {:?})", prefix, op.span)?; - writeln!(f, "{}|--- Left", prefix)?; - write_tree(left, f, indent + 1)?; - writeln!(f, "{}|--- Right", prefix)?; - write_tree(right, f, indent + 1)?; - } - NdType::BraceGroup { body } => { - writeln!(f, "{}BraceGroup", prefix)?; - write_tree(body, f, indent + 1)?; - } - NdType::Subshell { body, argv: _ } => { - writeln!(f, "{}Subshell (body: {:?})", prefix, body)?; - } - NdType::FuncDef { name, body } => { - writeln!(f, "{}Function Definition (name: {})", prefix, name)?; - write_tree(body, f, indent + 1)?; - } - NdType::Assignment { name, value } => { - writeln!(f, "{}Assignment (name: {}, value: {:?})", prefix, name, value)?; - } - NdType::Command { argv } | NdType::Builtin { argv } => { - let argv_texts: Vec<_> = argv.iter().map(|arg| arg.text()).collect(); - writeln!( - f, - "{}Command (args: {:?})", - prefix, argv_texts - )?; - } - NdType::And => { - writeln!(f, "{}And", prefix)?; - } - NdType::Or => { - writeln!(f, "{}Or", prefix)?; - } - NdType::Redirection { .. } => { - writeln!(f, "{}Redir", prefix)?; - } - NdType::Pipe => { - writeln!(f, "{}Pipe", prefix)?; - } - NdType::PipeBoth => { - writeln!(f, "{}PipeBoth", prefix)?; - } - NdType::Cmdsep => { - writeln!(f, "{}Cmdsep", prefix)?; - } - NdType::NullNode => panic!() - } - Ok(()) - } - - write_tree(self, f, 0) - } -} diff --git a/overlay/pkgs/rsh/rsh/src/interp/expand.rs b/overlay/pkgs/rsh/rsh/src/interp/expand.rs deleted file mode 100644 index 930655e..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/expand.rs +++ /dev/null @@ -1,639 +0,0 @@ -use chrono::Local; -use glob::glob; -use log::{debug, info, trace}; -use std::collections::VecDeque; -use std::mem::take; -use std::path::PathBuf; -use crate::event::ShellError; -use crate::interp::token::{Tk, TkType, WdFlags, WordDesc}; -use crate::interp::helper::{self,StrExtension, VecDequeExtension}; -use crate::shellenv::{EnvFlags, ShellEnv}; - -use super::parse::{self, NdType, Node, ParseState}; -use super::token::RshTokenizer; - -pub fn check_globs(string: String) -> bool { - string.has_unescaped("?") || - string.has_unescaped("*") -} - -pub fn expand_arguments(shellenv: &ShellEnv, node: &mut Node) -> Result,ShellError> { - let argv = node.get_argv()?; - let mut expand_buffer = Vec::new(); - for arg in &argv { - let mut expanded = expand_token(shellenv, arg.clone()); - while let Some(token) = expanded.pop_front() { - if !token.text().is_empty() { - // Do not return empty tokens - expand_buffer.push(token); - } - } - } - match &node.nd_type { - NdType::Builtin {..} => { - node.nd_type = NdType::Builtin { argv: expand_buffer.clone().into() }; - Ok(expand_buffer) - } - NdType::Command {..} => { - node.nd_type = NdType::Command { argv: expand_buffer.clone().into() }; - Ok(expand_buffer) - } - NdType::Subshell { body, argv: _ } => { - node.nd_type = NdType::Subshell { body: body.to_string(), argv: expand_buffer.clone().into() }; - Ok(expand_buffer) - } - _ => Err(ShellError::from_internal("Called expand arguments on a non-command node")) - } -} - -pub fn esc_seq(c: char) -> Option { - //TODO: - match c { - 'a' => Some('\x07'), - 'n' => Some('\n'), - 't' => Some('\t'), - '\\' => Some('\\'), - '"' => Some('"'), - '\'' => Some('\''), - _ => panic!() - } -} - -pub fn expand_time(fmt: &str) -> String { - let right_here_right_now = Local::now(); - right_here_right_now.format(fmt).to_string() -} - -pub fn expand_prompt(shellenv: &ShellEnv) -> String { - // TODO: - //\j - number of managed jobs - //\l - shell's terminal device name - //\v - rsh version - //\V - rsh release; version + patch level - //\! - history number of this command - //\# - command number of this command - let default_color = if shellenv.env_vars.get("UID").is_some_and(|uid| uid == "0") { - "31" // Red if uid is 0, aka root user - } else { - "32" // Green if anyone else - }; - let cwd: String = shellenv.env_vars.get("PWD").map_or("", |cwd| cwd).to_string(); - let default_path = if cwd.as_str() == "/" { - "\\e[36m\\w\\e[0m".to_string() - } else { - format!("\\e[1;{}m\\w\\e[1;36m/\\e[0m",default_color) - }; - let ps1: String = shellenv.env_vars.get("PS1").map_or(format!("\\n{}\\n\\e[{}mdebug \\$\\e[36m>\\e[0m ",default_path,default_color), |ps1| ps1.clone()); - let mut result = String::new(); - let mut chars = ps1.chars().collect::>(); - while let Some(c) = chars.pop_front() { - match c { - '\\' => { - if let Some(esc_c) = chars.pop_front() { - match esc_c { - 'a' => result.push('\x07'), - 'n' => result.push('\n'), - 'r' => result.push('\r'), - '\\' => result.push('\\'), - '\'' => result.push('\''), - '"' => result.push('"'), - 'd' => result.push_str(expand_time("%a %b %d").as_str()), - 't' => result.push_str(expand_time("%H:%M:%S").as_str()), - 'T' => result.push_str(expand_time("%I:%M:%S").as_str()), - 'A' => result.push_str(expand_time("%H:%M").as_str()), - '@' => result.push_str(expand_time("%I:%M %p").as_str()), - _ if esc_c.is_digit(8) => { - let mut octal_digits = String::new(); - octal_digits.push(esc_c); // Add the first digit - - for _ in 0..2 { - if let Some(next_c) = chars.front() { - if next_c.is_digit(8) { - octal_digits.push(chars.pop_front().unwrap()); - } else { - break; - } - } - } - - if let Ok(value) = u8::from_str_radix(&octal_digits, 8) { - result.push(value as char); - } else { - // Invalid sequence, treat as literal - result.push_str(&format!("\\{}", octal_digits)); - } - } - 'e' => { - result.push('\x1B'); - if chars.front().is_some_and(|&ch| ch == '[') { - result.push(chars.pop_front().unwrap()); // Consume '[' - while let Some(ch) = chars.pop_front() { - result.push(ch); - if ch == 'm' { - break; // End of ANSI sequence - } - } - } - } - '[' => { - // Handle \[ (start of non-printing sequence) - while let Some(ch) = chars.pop_front() { - if ch == ']' { - break; // Stop at the closing \] - } - result.push(ch); // Add non-printing content - } - } - ']' => { - // Handle \] (end of non-printing sequence) - // Do nothing, it's just a marker - } - 'w' => { - let mut cwd = shellenv.env_vars.get("PWD").map_or(String::new(), |pwd| pwd.to_string()); - let home = shellenv.env_vars.get("HOME").map_or("", |home| home); - if cwd.starts_with(home) { - cwd = cwd.replacen(home, "~", 1); // Use `replacen` to replace only the first occurrence - } - // TODO: unwrap is probably safe here since this option is initialized with the environment but it might still cause issues later if this is left unhandled - let trunc_len = shellenv.shopts.get("trunc_prompt_path").unwrap_or(&0); - if *trunc_len > 0 { - let mut path = PathBuf::from(cwd); - let mut cwd_components: Vec<_> = path.components().collect(); - if cwd_components.len() > *trunc_len { - cwd_components = cwd_components.split_off(cwd_components.len() - *trunc_len); - path = cwd_components.iter().collect(); // Rebuild the PathBuf - } - cwd = path.to_string_lossy().to_string(); - } - result.push_str(&cwd); - } - 'W' => { - let cwd = PathBuf::from(shellenv.env_vars.get("PWD").map_or("", |pwd| pwd)); - let mut cwd = cwd.components().last().map(|comp| comp.as_os_str().to_string_lossy().to_string()).unwrap_or_default(); - let home = shellenv.env_vars.get("HOME").map_or("", |home| home); - if cwd.starts_with(home) { - cwd = cwd.replacen(home, "~", 1); // Replace HOME with '~' - } - result.push_str(&cwd); - } - 'H' => { - let hostname = shellenv.env_vars.get("HOSTNAME").map_or("unknown host", |host| host); - result.push_str(hostname); - } - 'h' => { - let hostname = shellenv.env_vars.get("HOSTNAME").map_or("unknown host", |host| host); - if let Some((hostname, _)) = hostname.split_once('.') { - result.push_str(hostname); - } else { - result.push_str(hostname); // No '.' found, use the full hostname - } - } - 's' => { - let sh_name = shellenv.env_vars.get("SHELL").map_or("rsh", |sh| sh); - result.push_str(sh_name); - } - 'u' => { - let user = shellenv.env_vars.get("USER").map_or("unknown", |user| user); - result.push_str(user); - } - '$' => { - let uid = shellenv.env_vars.get("UID").map_or("0", |uid| uid); - match uid { - "0" => result.push('#'), - _ => result.push('$'), - } - } - _ => { - result.push('\\'); - result.push(esc_c); - } - } - } else { - result.push('\\'); - } - } - _ => result.push(c) - } - } - result -} - -pub fn process_ansi_escapes(input: &str) -> String { - let mut result = String::new(); - let mut chars = input.chars().collect::>(); - - while let Some(c) = chars.pop_front() { - if c == '\\' { - if let Some(next) = chars.pop_front() { - match next { - 'a' => result.push('\x07'), // Bell - 'b' => result.push('\x08'), // Backspace - 't' => result.push('\t'), // Tab - 'n' => result.push('\n'), // Newline - 'r' => result.push('\r'), // Carriage return - 'e' | 'E' => result.push('\x1B'), // Escape (\033 in octal) - '0' => { - // Octal escape: \0 followed by up to 3 octal digits - let mut octal_digits = String::new(); - while octal_digits.len() < 3 && chars.front().is_some_and(|ch| ch.is_digit(8)) { - octal_digits.push(chars.pop_front().unwrap()); - } - if let Ok(value) = u8::from_str_radix(&octal_digits, 8) { - let character = value as char; - result.push(character); - // Check for ANSI sequence if the result is ESC (\033 or \x1B) - if character == '\x1B' && chars.front().is_some_and(|&ch| ch == '[') { - result.push(chars.pop_front().unwrap()); // Consume '[' - while let Some(ch) = chars.pop_front() { - result.push(ch); - if ch == 'm' { - break; // Stop at the end of the ANSI sequence - } - } - } - } - } - _ => { - // Unknown escape, treat literally - result.push('\\'); - result.push(next); - } - } - } else { - // Trailing backslash, treat literally - result.push('\\'); - } - } else if c == '\x1B' { - // Handle raw ESC characters (e.g., \033 in octal or actual ESC char) - result.push(c); - if chars.front().is_some_and(|&ch| ch == '[') { - result.push(chars.pop_front().unwrap()); // Consume '[' - while let Some(ch) = chars.pop_front() { - result.push(ch); - if ch == 'm' { - break; // Stop at the end of the ANSI sequence - } - } - } - } else { - result.push(c); - } - } - - result -} - -pub fn expand_alias(shellenv: &ShellEnv, mut node: Node) -> Result { - match node.nd_type { - NdType::Command { ref mut argv } | NdType::Builtin { ref mut argv } => { - if let Some(cmd_tk) = argv.pop_front() { - if let Some(alias_content) = shellenv.get_alias(cmd_tk.text()) { - let new_argv = take(argv); - let mut child_shellenv = shellenv.clone(); - child_shellenv.mod_flags(|f| *f |= EnvFlags::NO_ALIAS); - let mut state = ParseState { - input: alias_content, - shellenv: &child_shellenv, - tokens: VecDeque::new(), - ast: Node::new(), - }; - - let mut tokenizer = RshTokenizer::new(alias_content); - tokenizer.tokenize(); - state.tokens = tokenizer.tokens.into(); - let mut alias_tokens = state.tokens; - // Trim `SOI` and `EOI` tokens - alias_tokens.pop_back(); - alias_tokens.pop_front(); - alias_tokens.extend(new_argv); - - // Guard against recursive aliasing - // i.e. "alias grep="grep --color-auto" - if alias_tokens.front().is_some_and(|tk| tk.text() == cmd_tk.text()) { - for token in &mut alias_tokens { - if token.text() == cmd_tk.text() { - token.wd.flags |= WdFlags::FROM_ALIAS - } - } - } - - state.tokens = alias_tokens; - state = parse::parse(state)?; - for redir in node.redirs { - state.ast.redirs.push_back(redir.clone()) - } - Ok(state.ast) - } else { - argv.push_front(cmd_tk); - Ok(node) - } - } else { - Ok(node) - } - } - _ => unreachable!() - } -} - -pub fn expand_token(shellenv: &ShellEnv, token: Tk) -> VecDeque { - trace!("expand(): Starting expansion with token: {:?}", token); - let mut working_buffer: VecDeque = VecDeque::new(); - let mut product_buffer: VecDeque = VecDeque::new(); - - //TODO: find some way to clean up this surprisingly functional mess - // Escaping breaks this right now I think - - working_buffer.push_back(token.clone()); - while let Some(mut token) = working_buffer.pop_front() { - let is_glob = check_globs(token.text().into()); - let is_brace_expansion = helper::is_brace_expansion(token.text()); - - let expand_home = token.text().has_unescaped("~"); - if expand_home { - // If this unwrap fails, god help you - let home = shellenv.get_variable("HOME").unwrap(); - token.wd.text = token.wd.text.replace("~",&home); - } - - if !is_glob && !is_brace_expansion { - debug!("expanding var for {}",token.text()); - if token.text().has_unescaped("$") && !token.wd.flags.intersects(WdFlags::FROM_VAR | WdFlags::SNG_QUOTED) { - info!("found unescaped dollar in: {}",token.text()); - if token.text().has_unescaped("$@") { - let mut param_tokens = expand_params(shellenv,token); - while let Some(param) = param_tokens.pop_back() { - working_buffer.push_front(param); - } - continue - } - token.wd.text = expand_var(shellenv, token.text().into()); - } - if helper::is_brace_expansion(token.text()) || token.text().has_unescaped("$") { - working_buffer.push_front(token); - } else { - let expand_home = token.text().has_unescaped("~"); - if expand_home { - // If this unwrap fails, god help you - let home = shellenv.get_variable("HOME").unwrap(); - token.wd.text = token.wd.text.replace("~",&home); - } - product_buffer.push_back(token) - } - - } else if is_brace_expansion && token.text().has_unescaped("{") && token.tk_type != TkType::String { - // Perform brace expansion - let expanded = expand_braces(token.text().to_string(), VecDeque::new()); - for mut expanded_token in expanded { - expanded_token = expand_var(shellenv, expanded_token); - working_buffer.push_back( - Tk { - tk_type: TkType::Expanded, - wd: WordDesc { - text: expanded_token, - span: token.span(), - flags: token.flags() - } - } - ); - }; - } else if is_glob { - // Expand glob patterns - for path in glob(token.text()).unwrap().flatten() { - working_buffer.push_back( - Tk { - tk_type: TkType::Expanded, - wd: WordDesc { - text: path.to_str().unwrap().to_string(), - span: token.span(), - flags: token.flags() - } - } - ); - } - } else if shellenv.get_alias(token.text()).is_some() { - let alias_content = shellenv.get_alias(token.text()).unwrap().split(' '); - for word in alias_content { - working_buffer.push_back( - Tk { - tk_type: TkType::Expanded, - wd: WordDesc { - text: word.into(), - span: token.span(), - flags: token.flags() - } - } - ); - } - } else { - let expand_home = token.text().has_unescaped("~"); - if expand_home { - // If this unwrap fails, god help you - let home = shellenv.get_variable("HOME").unwrap(); - token.wd.text = token.wd.text.replace("~",&home); - } - product_buffer.push_back(token); - } - } - - let mut temp_buffer = VecDeque::new(); - product_buffer.map_rotate(|mut elem| { - elem.wd.text = elem.wd.text.consume_escapes(); - temp_buffer.push_back(elem); - }); - product_buffer.extend(temp_buffer.drain(..)); - product_buffer -} - -pub fn clean_escape_chars(token_buffer: &mut VecDeque) { - for token in token_buffer { - let mut text = std::mem::take(&mut token.wd.text); - text = text.replace('\\',""); - token.wd.text = text; - } -} - -pub fn expand_braces(word: String, mut results: VecDeque) -> VecDeque { - if let Some((preamble, rest)) = word.split_once("{") { - if let Some((amble, postamble)) = rest.split_last("}") { - // the current logic makes adjacent expansions look like this: `left}{right` - // let's take advantage of that, shall we - if let Some((left,right)) = amble.split_once("}{") { - // Reconstruct the left side into a new brace expansion: left -> {left} - let left = format!("{}{}{}","{",left,"}"); - // Same with the right side: right -> {right} - // This also has the side effect of rebuilding any subsequent adjacent expansions - // e.g. "right1}{right2}{right3" -> {right1}{right2}{right3} - let right = format!("{}{}{}","{",right,"}"); - // Recurse - let left_expanded = expand_braces(left.to_string(), VecDeque::new()); - let right_expanded = expand_braces(right.to_string(), VecDeque::new()); - // Combine them - for left_part in left_expanded { - for right_part in &right_expanded { - results.push_back(format!("{}{}",left_part,right_part)); - } - } - } else { - let mut expanded = expand_amble(amble); - while let Some(string) = expanded.pop_front() { - let expanded_word = format!("{}{}{}", preamble, string, postamble); - results = expand_braces(expanded_word, results); // Recurse for nested braces - } - } - } else { - // Malformed brace: No closing `}` found - results.push_back(word); - } -} else { - // Base case: No more braces to expand - results.push_back(word); -} -results -} - -pub fn expand_amble(amble: String) -> VecDeque { - let mut result = VecDeque::new(); - if amble.contains("..") && amble.len() >= 4 { - let num_range = amble.chars().next().is_some_and(|ch| ch.is_ascii_digit()) - && amble.chars().last().is_some_and(|ch| ch.is_ascii_digit()); - - let lower_alpha_range = amble.chars().next().is_some_and(|ch| ch.is_ascii_lowercase()) - && amble.chars().last().is_some_and(|ch| ch.is_ascii_lowercase()) - && amble.chars().next() <= amble.chars().last(); // Ensure valid range - - let upper_alpha_range = amble.chars().next().is_some_and(|ch| ch.is_ascii_uppercase()) - && amble.chars().last().is_some_and(|ch| ch.is_ascii_uppercase()) - && amble.chars().next() <= amble.chars().last(); // Ensure valid range - - if lower_alpha_range || upper_alpha_range { - let left = amble.chars().next().unwrap(); - let right = amble.chars().last().unwrap(); - for i in left..=right { - result.push_back(i.to_string()); - } - } - if num_range { - let (left,right) = amble.split_once("..").unwrap(); - for i in left.parse::().unwrap()..=right.parse::().unwrap() { - result.push_back(i.to_string()); - } - } - } else { - let mut cur_string = String::new(); - let mut chars = amble.chars(); - let mut brace_stack = vec![]; - while let Some(ch) = chars.next() { - match ch { - '{' => { - cur_string.push(ch); - brace_stack.push(ch); - } - '}' => { - cur_string.push(ch); - brace_stack.pop(); - } - ',' => { - if brace_stack.is_empty() { - result.push_back(cur_string); - cur_string = String::new(); - } else { - cur_string.push(ch) - } - } - '\\' => { - let next = chars.next(); - if !matches!(next, Some('}') | Some('{')) { - cur_string.push(ch) - } - if let Some(next) = next { - cur_string.push(next) - } - } - _ => cur_string.push(ch) - } - } - result.push_back(cur_string); - } - result -} - -pub fn expand_var(shellenv: &ShellEnv, string: String) -> String { - let mut left = String::new(); - let mut right = String::new(); - let mut chars = string.chars().collect::>(); - while let Some(ch) = chars.pop_front() { - match ch { - '\\' => { - left.push(ch); - left.push(if let Some(ch) = chars.pop_front() { ch } else { break }) - } - '$' => { - right.extend(chars.drain(..)); - break - }, - _ => left.push(ch) - } - } - if right.is_empty() { - return string.to_string() - } - let mut right_chars = right.chars().collect::>(); - let mut var_name = String::new(); - while let Some(ch) = right_chars.pop_front() { - match ch { - _ if ch.is_alphanumeric() => { - var_name.push(ch); - } - '_' => { - var_name.push(ch); - } - '-' | '*' | '?' | '$' | '#' => { - var_name.push(ch); - break - } - '{' => continue, - '}' => break, - _ => { - right_chars.push_front(ch); - break - } - } - } - let right = right_chars.iter().collect::(); - - let value = shellenv.get_variable(&var_name).unwrap_or_default(); - let expanded = format!("{}{}{}",left,value,right); - if expanded.has_unescaped("$") { - expand_var(shellenv,expanded) - } else { - expanded - } -} - -fn expand_params(shellenv: &ShellEnv, token: Tk) -> VecDeque { - let mut expanded_tokens = VecDeque::new(); - // Get the positional parameter string from shellenv and split it - let arg_string = shellenv.get_variable("@").unwrap_or_default(); - let arg_split = arg_string.split(' '); - - // Split the token's text at the first instance of '$@' and make two new tokens - // Subsequent instances will be handled in later iterations of expand() - let (left,right) = token.text().split_once("$@").unwrap(); - let left_token = Tk::new(left.to_string(), token.span(), token.flags()); - let right_token = Tk::new(right.to_string(), token.span(), token.flags()); - - // Push the left token into the deque - if !left_token.text().is_empty() { - expanded_tokens.push_back(left_token); - } - for arg in arg_split { - // For each arg, make a new token and push it into the deque - let new_token = Tk::new(arg.to_string(),token.span(), token.flags() | WdFlags::FROM_VAR); - expanded_tokens.push_back(new_token); - } - // Now push the right token into the deque - if !right_token.text().is_empty() { - expanded_tokens.push_back(right_token); - } - expanded_tokens -} diff --git a/overlay/pkgs/rsh/rsh/src/interp/helper.rs b/overlay/pkgs/rsh/rsh/src/interp/helper.rs deleted file mode 100644 index 2b4d33e..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/helper.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::{interp::token::REGEX, shellenv::ShellEnv}; -use nix::unistd::dup2; -use std::{collections::VecDeque, env, fs::{self, metadata}, io, os::{fd::AsRawFd, unix::fs::PermissionsExt}, path::Path}; - -use super::parse::{NdType, Node}; - -pub trait VecExtension { - fn extended(self, vec: Vec) -> Vec; -} - -impl VecExtension for Vec { - fn extended(self, vec: Vec) -> Vec { - let mut new_vec = self; - new_vec.extend(vec); - new_vec - } -} - -pub trait VecDequeExtension { - fn map_rotate(&mut self, transform: F) where F: FnMut(T); -} - -impl VecDequeExtension for VecDeque { - /// Applies a transformation function to each element of the `VecDeque` - /// while preserving the original order of elements. - /// - /// This method "rotates" the `VecDeque` by repeatedly removing the element - /// at the front, applying the given transformation function to it, and - /// appending it to the back. The process ensures that all elements are - /// visited exactly once, and the final order remains unchanged. - /// - /// # Type Parameters - /// - `T`: The type of elements in the `VecDeque`. - /// - `F`: A closure or function that takes a mutable reference to an element - /// and applies the desired transformation. - /// - /// # Parameters - /// - `transform`: A closure or function of type `FnMut(&mut T)` that modifies - /// each element in place. - /// - /// # Examples - /// - /// ```rust - /// use std::collections::VecDeque; - /// - /// trait VecDequeExtension { - /// fn map_rotate(&mut self, transform: F) - /// where - /// F: FnMut(&mut T); - /// } - /// - /// impl VecDequeExtension for VecDeque { - /// fn map_rotate(&mut self, mut transform: F) - /// where - /// F: FnMut(&mut T), - /// { - /// let len = self.len(); - /// for _ in 0..len { - /// if let Some(mut element) = self.pop_front() { - /// transform(&mut element); - /// self.push_back(element); - /// } - /// } - /// } - /// } - /// - /// let mut deque: VecDeque = VecDeque::from(vec![ - /// String::from("hello"), - /// String::from("world"), - /// String::from("rust"), - /// ]); - /// - /// // Capitalize all elements - /// deque.map_rotate(|text| *text = text.to_uppercase()); - /// - /// assert_eq!(deque, VecDeque::from(vec![ - /// String::from("HELLO"), - /// String::from("WORLD"), - /// String::from("RUST"), - /// ])); - /// ``` - fn map_rotate(&mut self, mut transform: F) - where F: FnMut(T) { - let len = self.len(); - for _ in 0..len { - if let Some(element) = self.pop_front() { - transform(element); - } - } - } -} - -pub trait StrExtension { - fn split_last(&self, pat: &str) -> Option<(String,String)>; - fn has_unescaped(&self, pat: &str) -> bool; - fn consume_escapes(&self) -> String; -} - -impl StrExtension for str { - fn consume_escapes(&self) -> String { - let mut result = String::new(); - let mut chars = self.chars().peekable(); - - while let Some(ch) = chars.next() { - if ch == '\\' { - if let Some(&next_ch) = chars.peek() { - chars.next(); // Consume the escaped character - result.push(next_ch); // Add the unescaped pattern character - } - } else { - result.push(ch); // Add non-escaped characters as-is - } - } - - result - } - - fn split_last(&self, pat: &str) -> Option<(String, String)> { - let mut last_index = None; - let pat_len = pat.len(); - - // Iterate through the string and find the last occurrence of `pat` - for i in 0..=self.len().saturating_sub(pat_len) { - if &self[i..i + pat_len] == pat { - last_index = Some(i); - } - } - - // If no occurrence is found, return None - last_index.map(|index| ( - self[..index].to_string(), // Everything before `pat` - self[index + pat_len..].to_string(), // Everything after `pat` - )) - } - - /// Checks to see if a string slice contains a specified unescaped text pattern. This method assumes that '\' is the escape character. - /// - fn has_unescaped(&self, pat: &str) -> bool { - let mut chars = self.chars().collect::>(); - let mut working_pat = String::new(); - let mut escaped = false; - - while let Some(ch) = chars.pop_front() { - if !escaped && working_pat.contains(pat) { - return true; - } - match ch { - ' ' | '\t' => { - // Check for unescaped match when encountering a space/tab - // Reset for next segment - escaped = false; - working_pat.clear(); - } - '\\' => { - escaped = true; - } - _ => { - working_pat.push(ch); - } - } - } - - // Check for unescaped match at the end of the string - !escaped && working_pat.contains(pat) - } - -} - -// This is used when pesky system calls want to emit their own errors -// Which ends up being redundant, since rsh has it's own error reporting system -// Kind of hacky but fuck it -// It works by taking the function as an argument and then executing it in -// an isolated context where stderr is briefly redirected to /dev/null. -pub fn suppress_err T, T>(f: F) -> T { - let stderr = io::stderr(); - let stderr_fd = stderr.as_raw_fd(); - let devnull = fs::OpenOptions::new().write(true).open("/dev/null").unwrap(); - let devnull_fd = devnull.as_raw_fd(); - - dup2(devnull_fd, stderr_fd).unwrap(); - let result = f(); - dup2(stderr_fd,stderr_fd).unwrap(); - result -} - -pub fn which(shellenv: &ShellEnv, command: &str) -> Option { - if let Some(env_path) = shellenv.get_variable("PATH") { - for path in env::split_paths(&env_path) { - let full_path = path.join(command); - if full_path.is_file() && is_exec(&full_path) { - return Some(full_path.to_string_lossy().to_string()) - } - } - } - None -} - -pub fn is_exec(path: &Path) -> bool { - fs::metadata(path) - .map(|meta| meta.is_file() && (meta.permissions().mode() & 0o111) != 0) - .unwrap_or(false) -} - -pub fn flatten_pipeline(left: Node, right: Node, mut flattened: VecDeque) -> VecDeque { - flattened.push_front(right); - if let NdType::Pipeline { left, right, both: _ } = left.nd_type { - flattened = flatten_pipeline(*left, *right, flattened); - } else { - flattened.push_front(left); - } - flattened -} - -pub fn is_brace_expansion(text: &str) -> bool { - if REGEX["brace_expansion"].is_match(text) && - REGEX["brace_expansion"].captures(text).unwrap()[1].is_empty() { - let mut brace_count: i32 = 0; - let mut chars = text.chars(); - while let Some(ch) = chars.next() { - match ch { - '{' => brace_count += 1, - '}' => { - brace_count -= 1; - if brace_count < 0 { - // found a closing brace before an open brace - return false - } - }, - '\\' => { chars.next(); }, - _ => { /* Do nothing */ } - } - } - brace_count == 0 - } else { - false - } -} - -#[cfg(test)] -mod test { - use super::StrExtension; - - #[test] - fn split_last_test() { - let string = "hello there here is a pattern '&&&' and another occurence of it '&&&' and another! '&&&' a lot of patterns today"; - if let Some((left,right)) = string.split_last("&&&") { - assert_eq!((left,right),("hello there here is a pattern '&&&' and another occurence of it '&&&' and another! '".into(),"' a lot of patterns today".into())) - } else { - panic!() - } - } -} diff --git a/overlay/pkgs/rsh/rsh/src/interp/mod.rs b/overlay/pkgs/rsh/rsh/src/interp/mod.rs deleted file mode 100644 index eb80c7e..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod parse; -pub mod helper; -pub mod token; -pub mod expand; -pub mod debug; diff --git a/overlay/pkgs/rsh/rsh/src/interp/parse.rs b/overlay/pkgs/rsh/rsh/src/interp/parse.rs deleted file mode 100644 index a5d1217..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/parse.rs +++ /dev/null @@ -1,1567 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use bitflags::bitflags; -use once_cell::sync::Lazy; -use log::{error,debug,info,trace}; -use std::mem::take; - -use crate::event::ShellError; -use crate::shellenv::{EnvFlags, ShellEnv}; -use crate::interp::token::{RedirType, RshTokenizer, Tk, TkType}; - -use super::token::{Redir, WdFlags}; - -bitflags! { - #[derive(Debug,Clone,PartialEq)] - pub struct NdFlags: u32 { - // General Contexts - const VALID_OPERAND = 0b00000000000000000000000000000001; // Can be a target for redirection - const IS_OP = 0b00000000000000000000000000000010; // Is an operator - const COMBINE_OUT = 0b00000000000000000000000000000100; // Combine stderr and stdout - const BACKGROUND = 0b00000000000000000000000000001000; // Combine stderr and stdout - } -} - -pub static EXPECT: Lazy>> = Lazy::new(|| { - let mut m = HashMap::new(); - m.insert(TkType::If, vec![TkType::Then]); - m.insert(TkType::Elif, vec![TkType::Then]); - m.insert(TkType::Else, vec![TkType::Fi]); - m.insert(TkType::Then, vec![TkType::Fi, TkType::Elif, TkType::Else]); - m.insert(TkType::Do, vec![TkType::Done]); // `Do` expects `Done` - m.insert(TkType::Case, vec![TkType::Esac]); // `Case` expects `Esac` - m.insert(TkType::Select, vec![TkType::Do]); // `Select` expects `Do` - m.insert(TkType::While, vec![TkType::Do]); // `While` expects `Do` - m.insert(TkType::Until, vec![TkType::Do]); // `Until` expects `Do` - m.insert(TkType::For, vec![TkType::Do]); // `Until` expects `Do` - m -}); - -pub const OPENERS: [TkType;6] = [ - TkType::If, - TkType::For, - TkType::Until, - TkType::While, - TkType::Case, - TkType::Select, -]; - -#[derive(PartialEq,Debug,Clone)] -enum Phase { - Condition, - Body, - Vars, - Array, -} - -enum CmdType { - Builtin, - Subshell, - Command -} - -#[derive(PartialEq,Clone,Copy,Debug,Eq,Hash)] -pub struct Span { - pub start: usize, - pub end: usize -} - -impl Default for Span { - fn default() -> Self { - Span::new() - } -} - -impl Span { - pub fn new() -> Self { - Self { start: 0, end: 0 } - } - pub fn from(start: usize, end: usize) -> Self { - Self { start, end } - } - - pub fn set_start(&mut self, start: usize) { - self.start = start; - } - - pub fn set_end(&mut self, end: usize) { - self.end = end; - } -} - -#[derive(Debug,Clone,PartialEq)] -pub struct Conditional { - pub condition: Box, - pub body: Box -} - -#[derive(Debug,Clone,PartialEq)] -pub struct Node { - pub nd_type: NdType, - pub span: Span, - pub flags: NdFlags, - pub redirs: VecDeque -} - -impl Node { - pub fn new() -> Self { - Self { - nd_type: NdType::NullNode, - span: Span::new(), - flags: NdFlags::empty(), - redirs: VecDeque::new() - } - } - pub fn from(deck: VecDeque,span: Span) -> Self { - Self { - nd_type: NdType::Root { deck }, - span, - flags: NdFlags::empty(), - redirs: VecDeque::new() - } - } - - fn boxed(self) -> Box { - Box::new(self) - } - fn with_flags(&mut self,flags: NdFlags) -> Self { - Self { - nd_type: self.nd_type.clone(), - span: self.span, - flags, - redirs: take(&mut self.redirs) - } - } - pub fn span(&self) -> Span { - self.span - } - pub fn node_type(&self) -> &NdType { - &self.nd_type - } - pub fn set_span(&mut self,span: Span) { - self.span = span - } - pub fn get_argv(&self) -> Result,ShellError> { - let mut arg_vec = vec![]; - match &self.nd_type { - NdType::Command { argv } | NdType::Builtin { argv } | NdType::Subshell { body: _, argv } => { - for arg in argv { - arg_vec.push(arg.clone()); - } - Ok(arg_vec) - } - _ => Err(ShellError::from_internal("Attempt to call `get_argv()` on a non-command node")), - } - } - pub fn get_redirs(&self) -> Result,ShellError> { - if !self.flags.contains(NdFlags::VALID_OPERAND) { - return Err(ShellError::from_internal("Called get_redirs with an invalid operand")) - } - let mut redir_vec = vec![]; - for redir in &self.redirs { - redir_vec.push(redir.clone()); - } - Ok(redir_vec) - } -} - -impl Default for Node { - fn default() -> Self { - Self::new() - } -} -#[derive(Debug,Clone,PartialEq)] -pub enum NdType { - Root { deck: VecDeque }, - If { cond_blocks: VecDeque, else_block: Option> }, - For { loop_vars: VecDeque, loop_arr: VecDeque, loop_body: Box }, - Loop { condition: bool, logic: Conditional }, - Case { input_var: Tk, cases: HashMap }, - Select { select_var: Tk, opts: VecDeque, body: Box }, - Pipeline { left: Box, right: Box, both: bool }, - Chain { left: Box, right: Box, op: Box }, - BraceGroup { body: Box }, - Subshell { body: String, argv: VecDeque }, // It's a string because we're going to parse it in a subshell later - FuncDef { name: String, body: Box }, - Assignment {name: String, value: Option }, - Command { argv: VecDeque }, - Builtin { argv: VecDeque }, - Redirection { redir: Redir }, - And, - Or, - Pipe, - PipeBoth, - Cmdsep, - NullNode -} - -#[derive(Debug,PartialEq,Clone)] -pub struct ParseState<'a> { - pub input: &'a str, - pub shellenv: &'a ShellEnv, - pub tokens: VecDeque, - pub ast: Node -} - -#[derive(Debug,Clone)] -pub struct DescentContext { - tokens: VecDeque, - root: VecDeque, - start: usize, - end: usize, -} - -impl DescentContext { - pub fn new(tokens: VecDeque) -> Self { - Self { - tokens, - root: VecDeque::new(), - start: 0, - end: 0 - } - } - - pub fn mark_start(&self) -> usize { - self.start - } - - pub fn mark_end(&self) -> usize { - self.end - } - - pub fn next_tk(&mut self) -> Option { - let tk = self.tokens.pop_front(); - if let Some(ref tk) = tk { - self.start = tk.span().start; - self.end = tk.span().end - } - tk - } - - pub fn last_tk(&mut self) -> Option { - self.tokens.pop_back() - } - - pub fn front_tk(&mut self) -> Option<&Tk> { - self.tokens.front() - } - - pub fn back_tk(&mut self) -> Option<&Tk> { - self.tokens.back() - } - - pub fn next_node(&mut self) -> Option { - self.root.pop_front() - } - - pub fn last_node(&mut self) -> Option { - self.root.pop_back() - } - - pub fn front_node(&mut self) -> Option<&Node> { - self.root.front() - } - - pub fn back_node(&mut self) -> Option<&Node> { - self.root.back() - } - - pub fn attach_node(&mut self, node: Node) { - self.root.push_back(node); - } - - pub fn get_tk_texts(&self) -> Vec { - let mut texts = vec![]; - for tk in &self.tokens { - texts.push(tk.text().into()) - } - texts - } -} - -pub fn descend<'a>(input: &'a str, shellenv: &'a ShellEnv) -> Result,ShellError> { - info!("Starting descent into parsing with input: {:?}", input); - let mut tokenizer = RshTokenizer::new(input); - let mut state = ParseState { - input, - shellenv, - tokens: VecDeque::new(), - ast: Node { - nd_type: NdType::Root { deck: VecDeque::new() }, - span: Span::from(0,input.len()), - flags: NdFlags::empty(), - redirs: VecDeque::new() - } - }; - - tokenizer.tokenize(); - state.tokens = tokenizer.tokens.into(); - - state = parse(state)?; - - Ok(state) -} - -/// The purpose of this function is mainly just to be an entry point for the parsing logic -/// It is the only part of this logic that has access to the full input context. ShellError's are -/// propagated up here and then converted to a complete ShellErrorFull using the context of -/// ParseState. This is done because propagating errors upwards is probably -/// cheaper (and definitely easier) than propagating the raw input text downwards. -pub fn parse(state: ParseState) -> Result { - let ctx = DescentContext::new(state.tokens.clone()); - - get_tree(ctx).map(|ast| { - debug!("Generated AST: {:#?}", ast); - ParseState { - input: state.input, - shellenv: state.shellenv, - tokens: state.tokens, - ast - } - }) -} - -pub fn get_tree(ctx: DescentContext) -> Result { - trace!("Building AST from tokens: {:?}",ctx.tokens); - let span = compute_span(&ctx.tokens.clone()); - let ctx = parse_linear(ctx,false)?; - let tree = Node { - nd_type: NdType::Root { deck: ctx.root }, - span, - flags: NdFlags::empty(), - redirs: VecDeque::new() - }; - let tree = propagate_redirections(tree)?; - - Ok(tree) -} - -pub fn parse_linear(mut ctx: DescentContext, once: bool) -> Result { - // First pass just makes nodes without joining at operators - info!("Starting linear parsing of tokens..."); - while let Some(tk) = ctx.next_tk() { - trace!("Current tokens: {:?}", ctx.tokens); - use crate::interp::token::TkType::*; - match tk.class() { - If => { - info!("Found 'if' token, processing..."); - info!("tokens: {:?}",ctx.get_tk_texts()); - ctx = build_if(ctx)?; - if once { - break - } else { - continue - } - } - While => { - info!("Found 'while' token, processing..."); - ctx = build_loop(true,ctx)?; - if once { - break - } else { - continue - } - } - Until => { - info!("Found 'until' token, processing..."); - ctx = build_loop(false,ctx)?; - if once { - break - } else { - continue - } - } - For => { - info!("Found 'for' token, processing..."); - ctx = build_for(ctx)?; - if once { - break - } else { - continue - } - } - Case => { - info!("Found 'case' token, processing..."); - ctx = build_case(ctx)?; - if once { - break - } else { - continue - } - } - Select => { - info!("Found 'select' token, processing..."); - ctx = build_select(ctx)?; - if once { - break - } else { - continue - } - } - Ident | String => { - info!("Found command or string token, processing..."); - ctx.tokens.push_front(tk); - ctx = build_command(ctx)?; - // Fall through - } - Subshell => { - info!("Found subshell"); - ctx.tokens.push_front(tk); - ctx = build_command(ctx)?; - } - FuncDef {..} => { - ctx.tokens.push_front(tk); - ctx = build_func_def(ctx)?; - } - Assignment => { - ctx.tokens.push_front(tk); - ctx = build_assignment(ctx)?; - } - SOI => { - trace!("Skipping Start of Input token"); - continue - } - EOI => { - info!("End of Input token encountered, stopping parsing"); - break; - } - Do | Done => { - return Err(ShellError::from_parse(format!("Found `{}` outside of loop context",tk.text()).as_str(), tk.span())) - } - Else | Elif | Then | Fi => { - return Err(ShellError::from_parse(format!("Found `{}` outside of `if` context",tk.text()).as_str(), tk.span())) - } - Esac => { - return Err(ShellError::from_parse("Found `esac` outside of `case` context", tk.span())) - } - Redirection { .. } => { - ctx.tokens.push_front(tk); - ctx = build_redirection(ctx)?; - } - Cmdsep => ctx.attach_node( - Node { - nd_type: NdType::Cmdsep, - span: tk.span(), - flags: NdFlags::empty(), - redirs: VecDeque::new() - } - ), - LogicAnd => ctx.attach_node( - Node { - nd_type: NdType::And, - span: tk.span(), - flags: NdFlags::IS_OP, - redirs: VecDeque::new() - } - ), - LogicOr => ctx.attach_node( - Node { - nd_type: NdType::Or, - span: tk.span(), - flags: NdFlags::IS_OP, - redirs: VecDeque::new() - } - ), - Pipe => ctx.attach_node( - Node { - nd_type: NdType::Pipe, - span: tk.span(), - flags: NdFlags::IS_OP, - redirs: VecDeque::new() - } - ), - PipeBoth => ctx.attach_node( - Node { - nd_type: NdType::PipeBoth, - span: tk.span(), - flags: NdFlags::IS_OP, - redirs: VecDeque::new() - }.with_flags(NdFlags::COMBINE_OUT)), - _ => { - unimplemented!( - "Support for token type `{:?}` is not implemented yet", - tk.class() - ); - } - } - trace!("Current nodes: {:?}", &ctx.root); - } - - ctx = join_at_operators(ctx)?; - trace!("Completed linear parsing, nodes: {:?}", &ctx.root); - Ok(ctx) -} - -pub fn check_valid_operand(node: &Node) -> bool { - use crate::interp::parse::NdType::*; - matches!(node.nd_type, Pipeline {..} | Subshell {..} | Chain {..} | If {..} | For {..} | Loop {..} | Case {..} | Select {..} | Command {..} | Builtin {..}) -} - -pub fn join_at_operators(mut ctx: DescentContext) -> Result { - let mut buffer: VecDeque = VecDeque::new(); - - // First pass: Redirection operators - while let Some(node) = ctx.next_node() { - match node.nd_type { - NdType::Redirection { .. } => { - if let Some(mut target_node) = buffer.pop_back() { - target_node.redirs.push_back(node); - buffer.push_back(target_node); - } else { - return Err(ShellError::from_parse("Found this orphaned redirection operator", node.span())) - } - } - _ => buffer.push_back(node), - } - } - ctx.root.extend(buffer.drain(..)); - - // Second pass: Pipeline operators - let mut found_one = false; - while let Some(node) = ctx.next_node() { - match node.nd_type { - NdType::Pipe | NdType::PipeBoth => { - found_one = true; - let both = match node.nd_type { - NdType::PipeBoth => true, - NdType::Pipe => false, - _ => unreachable!() - }; - if let Some(left) = buffer.pop_back() { - if let Some(right) = ctx.next_node() { - if !check_valid_operand(&left) { - return Err(ShellError::from_parse("The left side of this pipeline is invalid", node.span)) - } - if !check_valid_operand(&right) { - return Err(ShellError::from_parse("The right side of this pipeline is invalid", node.span)) - } - let left = left.boxed(); - let right = right.boxed(); - let pipeline = Node { - nd_type: NdType::Pipeline { left, right, both }, - span: Span::from(0,0), - flags: NdFlags::empty(), - redirs: VecDeque::new() - }; - buffer.push_back(pipeline); - } else { - return Err(ShellError::from_parse("This pipeline is missing a right operand", node.span)) - } - } else { - return Err(ShellError::from_parse("This pipeline is missing a left operand", node.span)) - } - } - NdType::Cmdsep => { - if found_one { - while !buffer.is_empty() { - ctx.root.push_front(buffer.pop_back().unwrap()); - } - break - } - } - _ => buffer.push_back(node) - } - } - ctx.root.extend(buffer.drain(..)); - - // Third pass: Chain operators - found_one = false; - while let Some(node) = ctx.next_node() { - match node.nd_type { - NdType::And | NdType::Or => { - found_one = true; - if let Some(left) = buffer.pop_back() { - if let Some(right) = ctx.next_node() { - if !check_valid_operand(&left) { - return Err(ShellError::from_parse("The left side of this chain is invalid", node.span)) - } - if !check_valid_operand(&right) { - return Err(ShellError::from_parse("The right side of this chain is invalid", node.span)) - } - let left = left.boxed(); - let right = right.boxed(); - let op = node.boxed(); - let chain = Node { - nd_type: NdType::Chain { left, right, op }, - span: Span::from(0,0), - flags: NdFlags::empty(), - redirs: VecDeque::new() - }; - buffer.push_back(chain); - } else { - return Err(ShellError::from_parse("This chain is missing a right operand", node.span)) - } - } else { - return Err(ShellError::from_parse("This chain is missing a left operand", node.span)) - } - } - NdType::Cmdsep => { - if found_one { - while !buffer.is_empty() { - ctx.root.push_front(buffer.pop_back().unwrap()); - } - break - } - } - _ => buffer.push_back(node) - } - } - - ctx.root.extend(buffer.drain(..)); - Ok(ctx) -} -pub fn propagate_redirections(mut node: Node) -> Result { - // This function allows for redirections for higher order control flow structures - // e.g. `while true; do echo hello world; done > file.txt` - // The entire AST is rebuilt in-place, while carrying redirections out to the leaf nodes - let mut nd_type = node.nd_type.clone(); - match nd_type { - NdType::Root { ref mut deck } => { - // Iterate through the deck and map all root node redirections to children - let mut new_deck = VecDeque::new(); - while let Some(redir) = node.redirs.pop_back() { - while let Some(mut deck_node) = deck.pop_front() { - deck_node.redirs.push_front(redir.clone()); - new_deck.push_back(deck_node); - } - deck.extend(take(&mut new_deck)); - } - while let Some(mut deck_node) = deck.pop_front() { - deck_node = propagate_redirections(deck_node)?; - new_deck.push_back(deck_node); - } - node = Node::from(new_deck, node.span) - } - NdType::If { cond_blocks, mut else_block } => { - // Iterate through cond_blocks and map redirections accordingly - // Input redirections go to cond, output redirections go to body - let (cond_redirs,body_redirs) = get_flow_ctl_redirections(&node)?; - let mut new_cond_blocks = VecDeque::new(); - for block in cond_blocks { - let mut cond = *block.condition; - let mut body = *block.body; - - for redir in &cond_redirs { - cond.redirs.push_back(redir.clone()); - } - let cond = Box::new(propagate_redirections(cond)?); - - for redir in &body_redirs { - body.redirs.push_back(redir.clone()); - } - let body = Box::new(propagate_redirections(body)?); - new_cond_blocks.push_back(Conditional { condition: cond, body }); - } - if let Some(mut else_body) = else_block { - for redir in &body_redirs { - else_body.redirs.push_back(redir.clone()); - } - else_block = Some(Box::new(propagate_redirections(*else_body)?)); - } - node = Node { - nd_type: NdType::If { cond_blocks: new_cond_blocks, else_block }, - flags: node.flags, - redirs: VecDeque::new(), - span: node.span - } - } - NdType::Loop { condition, logic } => { - // Same as the logic for propagating in If blocks, just performed once - let mut cond = logic.condition; - let mut body = logic.body; - let (cond_redirs,body_redirs) = get_flow_ctl_redirections(&node)?; - - for redir in &cond_redirs { - cond.redirs.push_back(redir.clone()); - } - cond = Box::new(propagate_redirections(*cond)?); - - for redir in &body_redirs { - body.redirs.push_back(redir.clone()); - } - body = Box::new(propagate_redirections(*body)?); - let logic = Conditional { condition: cond, body }; - node = Node { - nd_type: NdType::Loop { condition, logic }, - flags: node.flags, - redirs: VecDeque::new(), - span: node.span - } - } - NdType::For { loop_vars, loop_arr, mut loop_body } => { - // Simple, loop_body is just a Root node so we just need to map redirs to it - // and then call propagate_redirections() - for redir in &node.redirs { - loop_body.redirs.push_back(redir.clone()); - } - - let loop_body = Box::new(propagate_redirections(*loop_body)?); - node = Node { - nd_type: NdType::For { loop_vars, loop_arr, loop_body }, - flags: node.flags, - redirs: VecDeque::new(), - span: node.span - } - } - NdType::Case { input_var, mut cases } => { - // This one gets a little bit messy - // Iterate through keys and map redirections to each case body - // And then iterate through the keys again and call propagate_redirections() on each - let keys = cases.keys().cloned().collect::>(); - let mut new_cases = HashMap::new(); - for redir in &node.redirs { - for key in keys.iter() { - cases.get_mut(key).unwrap().redirs.push_back(redir.clone()) - } - } - for key in keys.iter() { - if let Some(mut case_node) = cases.remove(key) { - case_node = propagate_redirections(case_node)?; - new_cases.insert(key.clone(),case_node); - } - } - let cases = new_cases; - node = Node { - nd_type: NdType::Case { input_var, cases }, - flags: node.flags, - redirs: VecDeque::new(), - span: node.span - } - - } - NdType::Select { select_var, opts, mut body } => { - // Same as For node logic - for redir in &node.redirs { - body.redirs.push_back(redir.clone()); - } - - body = Box::new(propagate_redirections(*body)?); - node = Node { - nd_type: NdType::Select { select_var, opts, body }, - flags: node.flags, - redirs: VecDeque::new(), - span: node.span - } - } - _ => { - // Fall-through - // This is for bottom-level nodes like commands and subshells - // If we have reached one of these, propagation is complete - // so we can just return the node now - } - } - Ok(node) -} - -fn get_flow_ctl_redirections(node: &Node) -> Result<(Vec, Vec),ShellError> { - // Separates redirections into two baskets; one for conditions and one for bodies - // Input redirections like `while read -r line; do echo $line; done < lines.txt` go to the condition - // Output redirections like `while true; do echo hello world; done >> hello.txt` go to the body - let redirs = node.get_redirs()?; - let (cond_redirs, body_redirs): (Vec, Vec) = redirs.into_iter().partition(|redir_nd| { - if let NdType::Redirection { ref redir } = redir_nd.nd_type { - matches!(redir.op, RedirType::Input) - } else { - false - } - }); - Ok((cond_redirs,body_redirs)) -} - -fn compute_span(tokens: &VecDeque) -> Span { - if tokens.is_empty() { - Span::from(0, 0) // Default span for empty tokens - } else { - Span::from(tokens.front().unwrap().span().start, tokens.back().unwrap().span().end) - } -} - -fn parse_and_attach(mut tokens: VecDeque, mut root: VecDeque) -> Result,ShellError> { - let mut sub_ctx = DescentContext::new(take(&mut tokens)); - sub_ctx = parse_linear(sub_ctx,true)?; - while let Some(node) = sub_ctx.root.pop_back() { - root.push_front(node); - } - Ok(root) -} - -fn get_conditional(cond_root: VecDeque, cond_span: Span, body_root: VecDeque, body_span: Span) -> Conditional { - let condition = Node { nd_type: NdType::Root { deck: cond_root }, span: cond_span, flags: NdFlags::empty(), redirs: VecDeque::new() }.boxed(); - let body = Node { nd_type: NdType::Root { deck: body_root }, span: body_span, flags: NdFlags::empty(), redirs: VecDeque::new() }.boxed(); - Conditional { condition, body } -} - -pub fn build_redirection(mut ctx: DescentContext) -> Result { - let span_start = ctx.mark_start(); - let redir_tk = ctx.next_tk() - .ok_or_else(|| ShellError::from_internal("Called build_redirection with an empty token queue"))?; - - let span = redir_tk.span(); - - let mut redir = if let TkType::Redirection { redir } = redir_tk.class() { - redir - } else { - return Err(ShellError::from_internal(format!("Called build_redirection() with a non-redirection token: {:?}",redir_tk).as_str())) - }; - - if redir.fd_target.is_none() && redir.file_target.is_none() { - let target = ctx.next_tk() - .ok_or_else(|| ShellError::from_parse("Did not find an output for this redirection operator", span))?; - - if !matches!(target.class(), TkType::Ident | TkType::String) { - return Err(ShellError::from_parse(format!("Expected identifier after redirection operator, found this: {}",target.text()).as_str(), span)) - } - - redir.file_target = Some(Box::new(target)); - } - - let node = Node { - nd_type: NdType::Redirection { redir }, - span, - flags: NdFlags::IS_OP, - redirs: VecDeque::new() - }; - ctx.attach_node(node); - - Ok(ctx) -} - -pub fn build_if(mut ctx: DescentContext) -> Result { - let mut cond_tokens = VecDeque::new(); - let mut cond_root = VecDeque::new(); - let mut body_tokens = VecDeque::new(); - let mut body_root = VecDeque::new(); - - let mut if_context = TkType::If; - let mut logic_blocks = VecDeque::new(); - let mut else_block = None; - let mut phase = Phase::Condition; - let mut closed = false; - - let span_start = ctx.mark_start(); - debug!("Starting build_if, initial tokens: {:?}", ctx.get_tk_texts()); - - while let Some(tk) = ctx.next_tk() { - let err_span = ctx.mark_start(); - debug!( - "build_if: processing token {:?}, current phase: {:?}, context: {:?}", - tk.text(), - phase, - if_context - ); - - match tk.class() { - _ if OPENERS.contains(&tk.class()) => { - ctx.tokens.push_front(tk); - match phase { - Phase::Condition => { - if !cond_tokens.is_empty() { - cond_root = parse_and_attach(take(&mut cond_tokens), cond_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - cond_root.push_back(node); - } - }, - Phase::Body => { - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - body_root.push_back(node); - } - }, - _ => unreachable!() - } - } - TkType::Elif if if_context != TkType::Else => { - debug!("build_if: processing 'elif', switching context..."); - if_context = TkType::Elif; - let cond_span = compute_span(&cond_tokens); - cond_root = parse_and_attach(take(&mut cond_tokens), cond_root)?; - - let body_span = compute_span(&body_tokens); - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - - let logic = get_conditional(take(&mut cond_root), cond_span, take(&mut body_root), body_span); - logic_blocks.push_back(logic); - phase = Phase::Condition; - debug!( - "build_if: added logic block, logic_blocks: {:?}, remaining tokens: {:?}", - logic_blocks.len(), - ctx.get_tk_texts() - ); - } - TkType::Then => { - if if_context == TkType::Then { - return Err(ShellError::from_parse( - "Did not find a condition for this `then` block", - Span::from(err_span,ctx.mark_end())) - ) - } - if if_context == TkType::Else { - return Err(ShellError::from_parse( - "Else blocks do not get a `then` statement; give the body directly after the else keyword", - Span::from(err_span,ctx.mark_end())) - ) - } - if_context = TkType::Then; - debug!("build_if: processing 'then', switching to Body phase"); - phase = Phase::Body; - } - TkType::Else => { - debug!("build_if: processing 'else', switching context..."); - if if_context != TkType::Then { - return Err(ShellError::from_parse("Was expecting a `then` block, get an else block instead", Span::from(err_span,ctx.mark_end()))) - } - if_context = TkType::Else; - let cond_span = compute_span(&cond_tokens); - cond_root = parse_and_attach(take(&mut cond_tokens), cond_root)?; - - let body_span = compute_span(&body_tokens); - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - - let logic = get_conditional(take(&mut cond_root), cond_span, take(&mut body_root), body_span); - logic_blocks.push_back(logic); - phase = Phase::Body; - debug!( - "build_if: added logic block, logic_blocks: {:?}, remaining tokens: {:?}", - logic_blocks.len(), - ctx.get_tk_texts() - ); - } - TkType::Fi => { - closed = true; - if !matches!(if_context,TkType::Then | TkType::Else) { - return Err(ShellError::from_parse("Was expecting a `then` block, get an else block instead", Span::from(err_span,ctx.mark_end()))) - } - debug!("build_if: processing 'fi', finalizing if block"); - debug!("cond tokens: {:?}",cond_tokens); - debug!("body tokens: {:?}",body_tokens); - if if_context == TkType::Else { - debug!("build_if: processing else block..."); - let else_ctx = DescentContext::new(take(&mut body_tokens)); - let else_node = get_tree(else_ctx)?.boxed(); - else_block = Some(else_node); - debug!("build_if: else block node created"); - } - if !body_tokens.is_empty() && !cond_tokens.is_empty() { - let cond_span = compute_span(&cond_tokens); - cond_root = parse_and_attach(take(&mut cond_tokens), cond_root)?; - - let body_span = compute_span(&body_tokens); - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - - let logic = get_conditional(take(&mut cond_root), cond_span, take(&mut body_root), body_span); - logic_blocks.push_back(logic); - } - break; - } - _ if phase == Phase::Condition => { - debug!("build_if: adding token {:?} to cond_tokens", tk.text()); - cond_tokens.push_back(tk); - } - _ if phase == Phase::Body => { - debug!("build_if: adding token {:?} to body_tokens", tk.text()); - body_tokens.push_back(tk); - } - _ => unreachable!("Unexpected token in build_if: {:?}", tk), - } - } - - let span_end = ctx.mark_end(); - let span = Span::from(span_start, span_end); - - if !closed { - return Err(ShellError::from_parse("This if statement didn't get an `fi`", span)) - } - - debug!("build_if: constructing final node..."); - let node = Node { - nd_type: NdType::If { cond_blocks: logic_blocks, else_block }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - }; - debug!("created node: {:#?}",node); - ctx.attach_node(node); - debug!("build_if: node attached, final remaining tokens: {:?}", ctx.get_tk_texts()); - - Ok(ctx) -} - -pub fn build_for(mut ctx: DescentContext) -> Result { - let mut phase = Phase::Vars; - - let mut loop_vars: VecDeque = VecDeque::new(); - let mut loop_arr: VecDeque = VecDeque::new(); - let mut body_tokens: VecDeque = VecDeque::new(); - let mut body_root: VecDeque = VecDeque::new(); - let span_start = ctx.mark_start(); - let mut body_start = 0; - let mut closed = false; - - while let Some(tk) = ctx.next_tk() { - match tk.class() { - TkType::In => { - if loop_vars.is_empty() { - return Err(ShellError::from_parse( - "This for loop didn't get any loop variables", - Span::from(span_start,ctx.mark_end())) - ) - } - phase = Phase::Array - } - TkType::Do => { - if loop_arr.back().is_some_and(|tk| tk.class() == TkType::Cmdsep) { - loop_arr.pop_back(); - } - if loop_arr.is_empty() { - return Err(ShellError::from_parse( - "This for loop got an empty array", - Span::from(span_start,ctx.mark_end())) - ) - } - body_start = ctx.mark_start(); - phase = Phase::Body - } - TkType::Done => { - if phase == Phase::Vars { - return Err(ShellError::from_parse( - "This for loop has an unterminated variable definition", - Span::from(span_start,ctx.mark_end())) - ) - } - if phase == Phase::Array { - return Err(ShellError::from_parse( - "This for loop has an unterminated array definition", - Span::from(span_start,ctx.mark_end())) - ) - } - closed = true; - break; - } - _ => match phase { - Phase::Vars => { - loop_vars.push_back(tk); - } - Phase::Array => { - loop_arr.push_back(tk); - } - Phase::Body => { - match tk.class() { - _ if OPENERS.contains(&tk.class()) => { - ctx.tokens.push_front(tk); - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - body_root.push_back(node); - } - }, - _ => body_tokens.push_back(tk), - } - } - _ => unreachable!() - } - } - } - - let span_end = ctx.mark_end(); - let span = Span::from(span_start,span_end); - - if !closed { - return Err(ShellError::from_parse( - "This loop is missing a `done`.", - span) - ) - } - - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - let body_end = ctx.mark_end(); - let body_span = Span::from(body_start,body_end); - let loop_body = Node::from(body_root,body_span).boxed(); - let node = Node { - nd_type: NdType::For { loop_vars, loop_arr, loop_body }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - }; - ctx.attach_node(node); - Ok(ctx) -} - -pub fn build_loop(condition: bool, mut ctx: DescentContext) -> Result { - let loop_condition = condition; - - let mut phase = Phase::Condition; - let mut cond_tokens = VecDeque::new(); - let mut cond_root = VecDeque::new(); - let mut body_tokens = VecDeque::new(); - let mut body_root = VecDeque::new(); - let mut closed = false; - let span_start = ctx.mark_start(); - - while let Some(tk) = ctx.next_tk() { - match tk.class() { - TkType::Do => { - if cond_tokens.is_empty() { - return Err(ShellError::from_parse("Did not find a condition for this loop", tk.span())) - } - phase = Phase::Body - } - TkType::Done => { - if body_tokens.is_empty() { - return Err(ShellError::from_parse("Did not find a body for this loop", tk.span())) - } - closed = true; - break - } - _ if OPENERS.contains(&tk.class()) => { - ctx.tokens.push_front(tk); - match phase { - Phase::Condition => { - if !cond_tokens.is_empty() { - cond_root = parse_and_attach(take(&mut cond_tokens), cond_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - cond_root.push_back(node); - } - }, - Phase::Body => { - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - body_root.push_back(node); - } - }, - _ => unreachable!() - } - } - _ if phase == Phase::Condition => { - cond_tokens.push_back(tk); - } - _ if phase == Phase::Body => { - body_tokens.push_back(tk); - } - _ => unreachable!() - } - } - - - let span_end = ctx.mark_end(); - let span = Span::from(span_start,span_end); - - if !closed { - return Err(ShellError::from_parse( - "This loop is missing a `done`", - span) - ) - } - - let mut cond_span = Span::from(0,0); - let mut body_span = Span::from(0,0); - if !cond_tokens.is_empty() && !body_tokens.is_empty() { - cond_span = compute_span(&cond_tokens); - body_span = compute_span(&body_tokens); - cond_root = parse_and_attach(cond_tokens,cond_root)?; - body_root = parse_and_attach(body_tokens,body_root)?; - } - let logic = get_conditional(cond_root, cond_span, body_root, body_span); - - let node = Node { - nd_type: NdType::Loop { condition: loop_condition, logic }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - }; - ctx.attach_node(node); - Ok(ctx) -} - -pub fn build_case(mut ctx: DescentContext) -> Result { - let mut cases = HashMap::new(); - let mut block_string = String::new(); - let mut block_tokens = VecDeque::new(); - let mut block_root = VecDeque::new(); - let mut input_var: Option = None; - let mut phase = Phase::Vars; - let mut closed = false; - if ctx.front_tk().is_some_and(|tk| tk.tk_type == TkType::Ident) { - input_var = Some(ctx.next_tk().unwrap()); - } - - let span_start = ctx.mark_start(); - - while let Some(tk) = ctx.next_tk() { - debug!("found token in build_case: {:?}",tk); - match tk.class() { - TkType::In => { - if input_var.is_some() { - if ctx.front_tk().is_some_and(|tk| tk.class() == TkType::Cmdsep) { - ctx.next_tk(); - } - phase = Phase::Condition; - } else { - return Err(ShellError::from_parse( - "Did not find a variable for this case statement", - tk.span(), - )); - } - } - TkType::Esac => { - // Final block handling - if !block_string.is_empty() { - let block_span = compute_span(&block_tokens); - block_root = parse_and_attach(take(&mut block_tokens), block_root)?; - let block_node = Node::from(take(&mut block_root), block_span); - cases.insert(block_string.clone(), block_node); - } - if cases.is_empty() { - return Err(ShellError::from_parse( - "Did not find any cases for this case statement", - tk.span(), - )); - } - closed = true; - break; - } - TkType::CasePat if phase == Phase::Condition => { - phase = Phase::Body; - if block_string.is_empty() { - block_string = tk.text().trim().to_string(); - } else { - return Err(ShellError::from_parse( - "Expected only one variable in case statement", - tk.span(), - )); - } - } - _ if phase == Phase::Body && ctx.front_tk().is_some_and(|f_tk| f_tk.tk_type == TkType::CasePat) => { - let block_span = compute_span(&block_tokens); - block_root = parse_and_attach(take(&mut block_tokens), block_root)?; - let block_node = Node::from(take(&mut block_root), block_span); - cases.insert(take(&mut block_string), block_node); - phase = Phase::Condition; - } - _ if phase == Phase::Body => { - if block_string.is_empty() { - return Err(ShellError::from_parse( - format!("Did not find a pattern for this case block: {}",tk.text()).as_str(), - tk.span(), - )); - } - match tk.class() { - _ if OPENERS.contains(&tk.class()) => { - ctx.tokens.push_front(tk); - if !block_tokens.is_empty() { - block_root = parse_and_attach(take(&mut block_tokens), block_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - block_root.push_back(node); - } - }, - _ => block_tokens.push_back(tk), - } - } - _ => { - return Err(ShellError::from_parse("Something weird happened in this case statement", tk.span())) - } - } - } - - let span_end = ctx.mark_end(); - let span = Span::from(span_start, span_end); - - if !closed { - return Err(ShellError::from_parse( - "This case statement is missing an `esac`", - span) - ) - } - - if input_var.is_none() { - return Err(ShellError::from_parse( - "Did not find a variable for this case statement", - span, - )); - } - - let input_var = input_var.unwrap(); - let node = Node { - nd_type: NdType::Case { input_var, cases }, - span, - flags: NdFlags::empty(), - redirs: VecDeque::new() - }; - ctx.attach_node(node); - Ok(ctx) -} - -pub fn build_select(mut ctx: DescentContext) -> Result { - // TODO: figure out a way to get 'in' to actually be a keyword - // Fix the logic in general so this code doesn't have to use awkward work arounds - let mut phase = Phase::Condition; - trace!("entered build_select with these tokens: {:?}",ctx.tokens); - - let mut select_var: Option = None; - let mut opts: VecDeque = VecDeque::new(); - let mut body_tokens: VecDeque = VecDeque::new(); - let mut body_root: VecDeque = VecDeque::new(); - let mut closed = false; - let span_start = ctx.mark_start(); - let body_start = 0; - - while let Some(tk) = ctx.next_tk() { - match tk.class() { - TkType::In => { - phase = Phase::Vars - } - TkType::Do => { - if opts.back().is_some_and(|tk| tk.class() == TkType::Cmdsep) { - opts.pop_back(); - } - phase = Phase::Body - } - TkType::Done => { - if select_var.is_none() { - return Err(ShellError::from_parse("Did not find a variable for this select statement", tk.span())) - } - if opts.is_empty() { - return Err(ShellError::from_parse("Did not find any options for this select statement", tk.span())) - } - if body_tokens.is_empty() { - return Err(ShellError::from_parse("This select statement has an empty body", tk.span())) - } - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - closed = true; - break - } - _ => { - match phase { - Phase::Condition => { - select_var = Some(tk); - } - Phase::Vars => { - opts.push_back(tk); - } - Phase::Body => { - match tk.class() { - _ if OPENERS.contains(&tk.class()) => { - ctx.tokens.push_front(tk); - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - ctx = parse_linear(ctx, true)?; - if let Some(node) = ctx.root.pop_back() { - body_root.push_back(node); - } - }, - _ => body_tokens.push_back(tk), - } - } - _ => unreachable!() - } - } - } - } - let span_end = ctx.mark_end(); - let span = Span::from(span_start,span_end); - - if !closed { - return Err(ShellError::from_parse( - "This select statement is missing a `done`", - span) - ) - } - - if !body_tokens.is_empty() { - body_root = parse_and_attach(take(&mut body_tokens), body_root)?; - } - if select_var.is_none() { - return Err(ShellError::from_parse("Did not find a variable for this select statement", span)) - } - let select_var = select_var.unwrap(); - let body_end = ctx.mark_end(); - let body_span = Span::from(body_start,body_end); - let body = Node::from(body_root,body_span).boxed(); - let node = Node { - nd_type: NdType::Select { select_var, opts, body }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - }; - ctx.attach_node(node); - Ok(ctx) -} - -pub fn build_func_def(mut ctx: DescentContext) -> Result { - let def = ctx.next_tk().unwrap(); - if let TkType::FuncDef = def.tk_type { - //TODO: initializing a new shellenv instead of cloning the current one here - //could cause issues later, keep an eye on this - //Might be fine to just build the AST since nothing is being executed or expanded - let name = def.text(); - let body_tk = ctx.next_tk().unwrap(); // We can be reasonably sure that this exists - let body = body_tk.text(); - let mut tokenizer = RshTokenizer::new(body); - let mut state = ParseState { - input: body, - shellenv: &ShellEnv::new(EnvFlags::NO_RC), - tokens: VecDeque::new(), - ast: Node::new() - }; - tokenizer.tokenize(); - state.tokens = tokenizer.tokens.into(); - let state = parse(state)?; - let func_tree = state.ast.boxed(); - let node = Node { - nd_type: NdType::FuncDef { name: name.to_string(), body: func_tree }, - span: def.span(), - flags: NdFlags::empty(), - redirs: VecDeque::new() - }; - ctx.attach_node(node); - - Ok(ctx) - } else { unreachable!() } -} - -pub fn build_assignment(mut ctx: DescentContext) -> Result { - let ass = ctx.next_tk().unwrap(); - if let TkType::Assignment = ass.tk_type { - let (var, val) = ass.text().split_once('=').unwrap(); - let span = ass.span(); - let node = Node { - nd_type: NdType::Assignment { - name: var.to_string(), - value: Some(val.to_string()), - }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - }; - ctx.attach_node(node); - Ok(ctx) - } else { unreachable!() } -} - -pub fn build_brace_group(tokens: VecDeque) -> Result<(Node, VecDeque), ShellError> { - todo!("Implement build_brace_group") -} - -pub fn build_command(mut ctx: DescentContext) -> Result { - let mut argv = VecDeque::new(); - // We handle redirections in join_at_operators(), so hold them here and push them back onto the queue afterward - let mut held_redirs = VecDeque::new(); - let mut background = false; - - let cmd = ctx.front_tk().unwrap().clone(); - let cmd_type = if cmd.flags().contains(WdFlags::BUILTIN) { - CmdType::Builtin - } else if cmd.tk_type == TkType::Subshell { - CmdType::Subshell - } else { - CmdType::Command - }; - - while let Some(mut tk) = ctx.next_tk() { - info!("found potential arg: {}",tk.text()); - - match tk.class() { - TkType:: PipeBoth | TkType::Cmdsep | TkType::LogicAnd | TkType::LogicOr | TkType::Pipe => { - info!("build_command breaking on: {:?}", tk); - ctx.tokens.push_front(tk); - while let Some(redir) = held_redirs.pop_back() { - // Push redirections back onto the queue, at the front - // This has the effect of moving all redirections to the right of the command node - // Which will be useful in join_at_operators() - ctx.tokens.push_front(redir); - } - break; - } - TkType::Background => { - background = true; - break // Background operator '&' is always the last argument - } - TkType::Subshell => continue, // Don't include the subshell token in the args - TkType::Ident | TkType::String | TkType::VariableSub | TkType::Assignment => { - // Add to argv - argv.push_back(tk); - } - TkType::Redirection { ref mut redir } => { - // Handle redirection - if redir.fd_target.is_none() { - if let Some(target_tk) = ctx.next_tk() { - if matches!(target_tk.class(), TkType::Ident | TkType::String) { - redir.file_target = Some(Box::new(target_tk)); - } - } - } - tk.tk_type = TkType::Redirection { redir: redir.clone() }; - held_redirs.push_back(tk) - } - TkType::SOI => continue, - TkType::EOI => { - while let Some(redir) = held_redirs.pop_back() { - // Push redirections back onto the queue, at the front - // This has the effect of moving all redirections to the right of the command node - // Which will be useful in join_at_operators() - ctx.tokens.push_front(redir); - } - break - } - _ => { - error!("ran into EOI while building command, with these tokens: {:?}",ctx.get_tk_texts()); - return Err(ShellError::from_parse( - format!("Unexpected token: {:?}", tk).as_str(), - tk.span(), - )); - } - } - } - - debug!("returning from build_command with tokens: {:?}", ctx.tokens); - - let span = compute_span(&argv); - let mut node = match cmd_type { - CmdType::Command => { - Node { - nd_type: NdType::Command { argv }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - } - } - CmdType::Builtin => { - Node { - nd_type: NdType::Builtin { argv }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - } - } - CmdType::Subshell => { - Node { - nd_type: NdType::Subshell { body: cmd.text().into(), argv }, - span, - flags: NdFlags::VALID_OPERAND, - redirs: VecDeque::new() - } - } - }; - if background { - node.flags |= NdFlags::BACKGROUND - } - debug!("attaching node: {:?}",node); - ctx.attach_node(node); - debug!("ast state: {:?}",ctx.root); - Ok(ctx) -} diff --git a/overlay/pkgs/rsh/rsh/src/interp/token.rs b/overlay/pkgs/rsh/rsh/src/interp/token.rs deleted file mode 100644 index a453068..0000000 --- a/overlay/pkgs/rsh/rsh/src/interp/token.rs +++ /dev/null @@ -1,985 +0,0 @@ -use bitflags::bitflags; -use once_cell::sync::Lazy; -use log::trace; -use regex::Regex; -use std::collections::HashMap; -use std::collections::VecDeque; -use std::io; -use std::mem::take; - -use crate::interp::parse::Span; -use crate::event::ShellError; - -use super::helper::StrExtension; - -pub static REGEX: Lazy> = Lazy::new(|| { - let mut regex = HashMap::new(); - regex.insert("redirection",Regex::new(r"^(?P[0-9]+)?(?P>{1,2}|<{1,3})(?:[&]?(?P[0-9]+))?$").unwrap()); - regex.insert("rsh_shebang",Regex::new(r"^#!((?:/[^\s]+)+)((?:\s+arg:[a-zA-Z][a-zA-Z0-9_\-]*)*)$").unwrap()); - regex.insert("brace_expansion",Regex::new(r"(\$?)\{(?:[\x20-\x7e,]+|[0-9]+(?:\.\.[0-9+]){1,2}|[a-z]\.\.[a-z]|[A-Z]\.\.[A-Z])\}").unwrap()); - regex.insert("process_sub",Regex::new(r"^>\(.*\)$").unwrap()); - regex.insert("command_sub",Regex::new(r"^\$\([^\)]+\)$").unwrap()); - regex.insert("arithmetic",Regex::new(r"^\$\(\([^\)]+\)\)$").unwrap()); - regex.insert("subshell",Regex::new(r"^\([^\)]+\)$").unwrap()); - regex.insert("test",Regex::new(r"^\[\s*(.*?)\s*\]$").unwrap()); - regex.insert("sng_string",Regex::new(r#"^\'([^\']*)\'$"#).unwrap()); - regex.insert("dub_string",Regex::new(r#"^\"([^\"]*)\"$"#).unwrap()); - regex.insert("var_sub",Regex::new(r"\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})").unwrap()); - regex.insert("assignment",Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*=.*$").unwrap()); - regex.insert("funcdef",Regex::new(r"^[\x20-\x7E]*\(\)\s+\{[\s\S]*?\}").unwrap()); - regex.insert("operator",Regex::new(r"(?:&&|\|\||[><]=?|[|&])").unwrap()); - regex.insert("cmdsep",Regex::new(r"^(?:\n|;)$").unwrap()); - regex.insert("ident",Regex::new(r"^[\x20-\x7E]*$").unwrap()); - regex -}); - -pub const KEYWORDS: [&str;14] = [ - "if", "while", "until", "for", "case", "select", - "then", "elif", "else", "in", - "do", "done", "fi", "esac" -]; -pub const OPENERS: [&str;6] = [ - "if", "while", "until", "for", "case", "select", -]; -pub const BUILTINS: [&str; 22] = [ - "echo", "jobs", "unset", "fg", "bg", "set", "builtin", "test", "[", "shift", "alias", "export", "cd", "readonly", "declare", "local", "unset", "trap", "node", "exec", "source", "wait", -]; -pub const CMDSEP: [char;2] = [ - ';', '\n' -]; -pub const WHITESPACE: [char;2] = [ - ' ', '\t' -]; - -bitflags! { - #[derive(Debug,Clone,Copy,PartialEq,Eq)] - pub struct FnFlags: u32 { - const RECURSE = 0b0001; - } - #[derive(Debug,Hash,Clone,Copy,PartialEq,Eq)] - pub struct WdFlags: u32 { - const KEYWORD = 0b000000000000000001; - const BUILTIN = 0b000000000000000010; - const FUNCTION = 0b000000000000000100; - const ALIAS = 0b000000000000001000; - const IS_ARG = 0b000000000000010000; - const DUB_QUOTED = 0b000000000000100000; - const SNG_QUOTED = 0b000000000001000000; - const IN_BRACE = 0b000000000010000000; - const IN_PAREN = 0b000000000100000000; - const IS_SUB = 0b000000001000000000; - const IS_OP = 0b000000010000000000; - const EXPECT_IN = 0b000000100000000000; - const IS_PATH = 0b000001000000000000; - const FROM_ALIAS = 0b000010000000000000; - const FROM_FUNC = 0b000100000000000000; - const FROM_VAR = 0b001000000000000000; - } - } - -macro_rules! define_expectations { - ($($name:expr => $pattern:expr),* $(,)?) => {{ - use crate::interp::token::TkState::*; - let mut m = HashMap::new(); - $(m.insert($name, $pattern);)* - m - }}; -} - -#[derive(Debug,Hash,Clone,PartialEq,Eq)] -pub struct WordDesc { - pub text: String, - pub span: Span, - pub flags: WdFlags -} - -impl WordDesc { - pub fn empty() -> Self { - Self { - text: String::new(), - span: Span::new(), - flags: WdFlags::empty() - } - } - pub fn set_span(self, span: Span) -> Self { - Self { - text: self.text, - span, - flags: self.flags - } - } - pub fn add_char(&mut self, ch: char) -> Self { - trace!("cloning text: {}",self.text); - let mut text = std::mem::take(&mut self.text); - trace!("cloned text: {}",text); - trace!("pushing char: {}",ch); - text.push(ch); - trace!("after pushing: {}",text); - - Self { - text, - span: Span::from(self.span.start, self.span.end), - flags: self.flags, - } - } - pub fn cat_string(mut self, s: &str) -> Self { - self.text.push_str(s); - Self { - text: self.text, - span: Span::from(self.span.start, self.span.end + 1), - flags: self.flags - } - } - pub fn contains_flag(&self, flag: WdFlags) -> bool { - self.flags.contains(flag) - } - - pub fn add_flag(self, flag: WdFlags) -> Self { - let mut flags = self.flags; - flags |= flag; - let new = Self { - text: self.text, - span: self.span, - flags - }; - trace!("added flag: {:?}, new flags {:?}",flag,new.flags); - new - } - - pub fn remove_flag(self, flag: WdFlags) -> Self { - let mut flags = self.flags; - flags &= !flag; - Self { - text: self.text, - span: self.span, - flags - } - } - - pub fn toggle_flag(self, flag: WdFlags) -> Self { - let mut flags = self.flags; - flags ^= flag; - Self { - text: self.text, - span: self.span, - flags - } - } - pub fn reset_flags(self) -> Self { - Self { - text: self.text, - span: self.span, - flags: WdFlags::empty() - } - } - pub fn push_span(&mut self, count: usize) { - self.span = Span::from(self.span.start,self.span.end + count); - } - pub fn push_span_start(&mut self, count: usize) { - let mut span_start = self.span.start; - let mut span_end = self.span.end; - span_start += count; - if span_end < span_start { - span_end = span_start - } - self.span = Span::from(span_start,span_end); - } - pub fn delimit(self, delim: char) -> Self { - let flag = match delim { - '{' => WdFlags::IN_BRACE, - '(' => WdFlags::IN_PAREN, - _ => unreachable!() - }; - self.add_flag(flag) - } -} - -impl Default for WordDesc { - fn default() -> Self { - Self::empty() - } -} - -#[derive(Debug,Hash,Clone,PartialEq,Eq)] -pub struct Tk { - pub tk_type: TkType, - pub wd: WordDesc - } - -#[derive(Debug,Hash,Clone,PartialEq,Eq)] -pub enum TkType { - // Control Flow Keywords - If, - Then, - Else, - Elif, - Fi, - For, - While, - Until, - Do, - Done, - Case, - Esac, - Select, - In, - FuncBody, - - Redirection { redir: Redir }, - FuncDef, - Assignment, // `=` - LogicAnd, // `&&` - LogicOr, // `||` - Pipe, // `|` - PipeBoth, // '|&' - Background, // `&` - - // Grouping and Subshells - ProcessSub, - Subshell, // `(` - Array { elements: Vec }, - Vars { vars: Vec }, - - // Strings and Identifiers - String, // Generic string literal - Ident, // Identifier for variables, functions, etc. - Expanded, // Token from an expansion - - // Expansions - BraceExpansion, - VariableSub, // `$var`, `${var}` - CommandSub, // `$(command)` - - // Comments - Comment, // `#` - - // Special Characters - Cmdsep, // ';' or '\n' - CasePat, - Whitespace, // Space or tab - SOI, - EOI, - -} - -impl Tk { - pub fn new(text: String, span: Span, flags: WdFlags) -> Self { - Self { - tk_type: TkType::String, - wd: WordDesc { - text, - span, - flags - } - } - } - pub fn from(mut wd: WordDesc, context: TkState) -> Result { - use crate::interp::token::TkState::*; - use crate::interp::token::TkType as TkT; - match context { - Root => panic!("not supposed to be here"), - Ident => Ok(Tk { tk_type: TkT::Ident, wd }), - Arg => Ok(Tk { tk_type: TkT::Ident, wd: wd.add_flag(WdFlags::IS_ARG) }), - Command => Ok(Tk { tk_type: TkT::Ident, wd }), - Array => Ok(Tk { tk_type: TkT::Array { elements: vec![] }, wd }), - If => Ok(Tk { tk_type: TkT::If, wd: wd.add_flag(WdFlags::KEYWORD) }), - For => Ok(Tk { tk_type: TkT::For, wd: wd.add_flag(WdFlags::KEYWORD) }), - Loop => Ok({ - if wd.text == "while" { - Tk { tk_type: TkT::While, wd: wd.add_flag(WdFlags::KEYWORD) } - } else if wd.text == "until" { - Tk { tk_type: TkT::Until, wd: wd.add_flag(WdFlags::KEYWORD) } - } else { unreachable!("reached loop context with this: {}",wd.text) } - }), - Case => Ok(Tk { tk_type: TkT::Case, wd: wd.add_flag(WdFlags::KEYWORD) }), - Select => Ok(Tk { tk_type: TkT::Select, wd: wd.add_flag(WdFlags::KEYWORD) }), - In | CaseIn => Ok(Tk { tk_type: TkT::In, wd: wd.add_flag(WdFlags::KEYWORD) }), - CasePat => Ok(Tk { tk_type: TkT::Ident, wd }), - Elif => Ok(Tk { tk_type: TkT::Elif, wd: wd.add_flag(WdFlags::KEYWORD) }), - Else => Ok(Tk { tk_type: TkT::Else, wd: wd.add_flag(WdFlags::KEYWORD) }), - Do => Ok(Tk { tk_type: TkT::Do, wd: wd.add_flag(WdFlags::KEYWORD) }), - Then => Ok(Tk { tk_type: TkT::Then, wd: wd.add_flag(WdFlags::KEYWORD) }), - Done => Ok(Tk { tk_type: TkT::Done, wd: wd.add_flag(WdFlags::KEYWORD) }), - Fi => Ok(Tk { tk_type: TkT::Fi, wd: wd.add_flag(WdFlags::KEYWORD) }), - Esac => Ok(Tk { tk_type: TkT::Esac, wd: wd.add_flag(WdFlags::KEYWORD) }), - Subshell => Ok(Tk { tk_type: TkT::Subshell, wd }), - SQuote => Ok(Tk { tk_type: TkT::String, wd }), - DQuote => Ok(Tk { tk_type: TkT::String, wd }), - Redirect => todo!(), - CommandSub => Ok(Tk { tk_type: TkT::CommandSub, wd }), - Separator => Ok(Tk { tk_type: TkT::Cmdsep, wd }), - _ => return Err(ShellError::from_parse(format!("Parse error: {}", wd.text).as_str(), wd.span)) - } - } - pub fn start_of_input() -> Self { - Tk { - tk_type: TkType::SOI, - wd: WordDesc { text: "".into(), span: Span::from(0,0), flags: WdFlags::empty() } - } - } - pub fn end_of_input(end: usize) -> Self { - Tk { - tk_type: TkType::EOI, - wd: WordDesc { text: "".into(), span: Span::from(end,end), flags: WdFlags::empty() } - } - } - pub fn cmdsep(wd: &WordDesc, pos: usize) -> Self { - Tk { - tk_type: TkType::Cmdsep, - wd: WordDesc { text: wd.text.clone(), span: Span::from(pos + 1,pos + 1), flags: WdFlags::empty() } - } - } - fn get_keyword_token(wd: &WordDesc) -> Result { - let text = wd.text.clone(); - match text.as_str() { - "if" => Ok(TkType::If), - "elif" => Ok(TkType::Elif), - "else" => Ok(TkType::Else), - "then" => Ok(TkType::Then), - "fi" => Ok(TkType::Fi), - "for" => Ok(TkType::For), - "while" => Ok(TkType::While), - "until" => Ok(TkType::Until), - "do" => Ok(TkType::Do), - "done" => Ok(TkType::Done), - "case" => Ok(TkType::Case), - "esac" => Ok(TkType::Esac), - "select" => Ok(TkType::Select), - "in" => Ok(TkType::In), - _ => Err(ShellError::from_parse(format!("Unrecognized keyword: {}",wd.text).as_str(), wd.span)) - } - } - fn split_func(wd: &WordDesc) -> Result { - let func_raw = &wd.text; - let (mut name,mut body) = func_raw.split_once(' ').unwrap(); - name = name.trim(); - body = body.trim(); - if name.ends_with("()") { - name = name.strip_suffix("()").unwrap(); - name = name.trim(); - } - if body.starts_with('{') && body.ends_with('}') { - body = body.strip_prefix('{').unwrap(); - body = body.strip_suffix('}').unwrap(); - body = body.trim(); - } else { - return Err(ShellError::from_internal(format!("This body made it to split_func with no surrounding braces: {}",body).as_str())); - } - - Ok(TkType::FuncDef) - } - fn get_operator_type(word_desc: &WordDesc) -> TkType { - match word_desc.text.as_str() { - "|&" => TkType::PipeBoth, - "&" => TkType::Background, - "&&" => TkType::LogicAnd, - "||" => TkType::LogicOr, - "|" => TkType::Pipe, - _ => unreachable!() - } - } - pub fn text(&self) -> &str { - self.wd.text.as_str() - } - pub fn span(&self) -> Span { - self.wd.span - } - pub fn class(&self) -> TkType { - self.tk_type.clone() - } - pub fn flags(&self) -> WdFlags { - self.wd.flags - } -} - - -#[derive(Debug,Hash,Clone,PartialEq,Eq)] -pub struct Redir { - pub fd_source: i32, - pub op: RedirType, - pub fd_target: Option, - pub file_target: Option> -} - -#[derive(Debug,Hash,Clone,PartialEq,Eq)] -pub enum RedirType { - Output, - Append, - Input, - Heredoc, - Herestring -} - - -#[derive(Eq,Hash,PartialEq)] -pub enum TkState { - Root, // Outer contex, allows for anything and everything. Closed by the EOI token - ArrDec, // Used in arrays like for i in ArrDec1 ArrDec2 - VarDec, // Used in for loop vars like for VarDec1 VarDec2 in array - SingleVarDec, // Used in case and select - Ident, // Generic words used for var and arr declarations - Arg, // Command arguments; only appear after commands - Command, // Starting point for the tokenizer - FuncDef, // defining a function like() { this } - FuncBody, // The stuff { inside braces } - Array, // Used in for loops and select statements - If, // If statement opener - For, // For loop opener - Loop, // While/Until opener - Case, // Case opener - Select, // Select opener - In, // Used in for, case, and select statements - CaseBlock, // this)kind of thing;; - CaseIn, // 'In' context used for case statements, signaling the tokenizer to look for stuff 'like)this;;esac' - CasePat, // the left side of this)kind of thing - CaseBody, // the right side of this)kind of thing - Elif, // Secondary if/then blocks - Else, // Else statements - Do, // Select, for, and while/until condition/body separator - Then, // If statement condition/body separator - Done, // Select, for, and while/until closer - Fi, // If statement closer - Esac, // Case statement closer - Subshell, // Subshells, look (like this) - SQuote, // 'Single quoted strings' - DQuote, // "Double quoted strings" - Escaped, // Used to denote an escaped character like \a - Redirect, // >, <, <<, <<<, 1>&2, 2>, etc. - Comment, // #Comments like this - Whitespace, // Space or tabs - CommandSub, // $(Command substitution) - Operator, // operators - Separator, // Semicolon or newline to end an invocation - DeadEnd, // Used for closing keywords like 'fi' and 'done' that demand a separator immediately after - Invalid // Used when an unexpected state is discovered -} - -impl TkState { - pub fn check_str(slice: &str, context: TkState) -> Self { - use crate::interp::token::TkState::*; - match slice { - // Loop and conditional openers - "for" if matches!(context, Command) => For, - "if" if matches!(context, Command) => If, - "while" if matches!(context, Command) => Loop, - "until" if matches!(context, Command) => Loop, - - // Conditional separators and terminators - "then" if matches!(context, If | Elif) => Then, - "elif" if matches!(context, Then | Elif) => Elif, - "else" if matches!(context, If | Then | Elif) => Else, - "fi" if matches!(context, If | Then | Elif | Else) => Fi, - - // Loop terminators - "do" if matches!(context, For | Loop) => Do, - "done" if matches!(context, Do) => Done, - - // Case-specific keywords - "case" if matches!(context, Command) => Case, - "in" if matches!(context, Case | For | Select) => In, - "esac" if matches!(context, CaseIn | CaseBlock | CaseBody) => Esac, - - // Select-specific keywords - "select" if matches!(context, Command) => Select, - - // General flow control - "in" if matches!(context, For | Case | Select) => In, - "|" if Self::executable().contains(&context) => Operator, - "&" if Self::executable().contains(&context) => Operator, - - // Defaults to Ident for non-keyword or unmatched cases - _ => Ident, - } - } - pub fn executable() -> Vec { - use crate::interp::token::TkState::*; - let mut execs = vec![ - Command, - Subshell - ]; - execs.extend(Self::openers()); - execs - } - pub fn body() -> Vec { - use crate::interp::token::TkState::*; - vec![ - Command, - Arg, - Separator, - Redirect, - SQuote, - DQuote, - CommandSub, - Subshell - ] - } - pub fn openers() -> Vec { - use crate::interp::token::TkState::*; - vec![ - For, - Loop, - If, - Case, - Select - ] - } -} - -pub struct RshTokenizer { - input: String, - char_stream: VecDeque, - context: TkState, - pub tokens: Vec, - pub span: Span, -} - -impl RshTokenizer { - pub fn new(input: &str) -> Self { - let input = input.trim().to_string(); - let char_stream = input.chars().collect::>(); - let tokens = vec![Tk { tk_type: TkType::SOI, wd: WordDesc::empty() }]; - Self { input, char_stream, context: TkState::Command, tokens, span: Span::new() } - } - fn advance(&mut self) -> Option { - self.span.end += 1; - self.char_stream.pop_front() - } - pub fn tokenize(&mut self) -> Result<(),ShellError> { - use crate::interp::token::TkState::*; - let mut wd = WordDesc::empty(); - while !self.char_stream.is_empty() { - self.span.start = self.span.end; - wd = wd.set_span(self.span); - if self.context == DeadEnd && !matches!(self.char_stream.front().unwrap(),';' | '\n') { - return Err(ShellError::from_parse("Expected a semicolon or newline here", self.span)) - } - match self.char_stream.front().unwrap() { - '\\' => { - let escape = self.advance().unwrap(); - wd = wd.add_char(escape); - let escaped_char = self.advance(); - if let Some(ch) = escaped_char { - wd = wd.add_char(ch) - } - } - ';' | '\n' => { - self.advance(); - self.tokens.push(Tk::cmdsep(&wd,self.span.end)); - self.context = Command; - continue - } - '#' => self.context = Comment, - '(' if self.context == Command => self.context = Subshell, - '{' if self.context == FuncDef => self.context = FuncBody, - '\'' if matches!(self.context, Command | Arg) => self.context = SQuote, - '"' if matches!(self.context, Command | Arg) => self.context = DQuote, - '$' if matches!(self.context, Command | Arg) => { - let dollar = self.advance().unwrap(); - if self.char_stream.front().is_some_and(|ch| *ch == '(') { - self.context = CommandSub - } - self.char_stream.push_front(dollar); - self.span.end -= 1; - } - ' ' | '\t' => { - self.advance(); - continue - } - _ => { /* Do nothing */ } - } - match self.context { - Command => self.command_context(take(&mut wd)), - Arg => self.arg_context(take(&mut wd)), - DQuote | SQuote => self.string_context(take(&mut wd)), - VarDec => self.vardec_context(take(&mut wd))?, - SingleVarDec => self.vardec_context(take(&mut wd))?, - ArrDec => self.arrdec_context(take(&mut wd))?, - Subshell | CommandSub => self.subshell_context(take(&mut wd)), - FuncBody => self.func_context(take(&mut wd)), - Case => self.case_context(take(&mut wd))?, - Comment => { - while self.char_stream.front().is_some_and(|ch| *ch != '\n') { - self.advance(); - } - self.advance(); // Consume the newline too - self.context = Command - } - _ => unreachable!() - } - } - self.tokens.push(Tk::end_of_input(self.input.len())); - self.clean_tokens(); - Ok(()) - } - fn command_context(&mut self, mut wd: WordDesc) { - use crate::interp::token::TkState::*; - wd = self.complete_word(wd); - if wd.text.ends_with("()") { - wd.text = wd.text.strip_suffix("()").unwrap().to_string(); - self.tokens.push(Tk { tk_type: TkType::FuncDef, wd }); - self.context = FuncDef; - } else if KEYWORDS.contains(&wd.text.as_str()) { - match wd.text.as_str() { - "then" | "if" | "elif" | "else" | "do" | "while" | "until" => self.context = Command, - "for" => self.context = VarDec, - "case" => self.context = Case, - "select" => self.context = Select, - _ => self.context = DeadEnd - } - match wd.text.as_str() { - "if" => self.tokens.push(Tk { tk_type: TkType::If, wd }), - "then" => self.tokens.push(Tk { tk_type: TkType::Then, wd }), - "elif" => self.tokens.push(Tk { tk_type: TkType::Elif, wd }), - "else" => self.tokens.push(Tk { tk_type: TkType::Else, wd }), - "fi" => self.tokens.push(Tk { tk_type: TkType::Fi, wd }), - "for" => self.tokens.push(Tk { tk_type: TkType::For, wd }), - "do" => self.tokens.push(Tk { tk_type: TkType::Do, wd }), - "done" => self.tokens.push(Tk { tk_type: TkType::Done, wd }), - "while" => self.tokens.push(Tk { tk_type: TkType::While, wd }), - "until" => self.tokens.push(Tk { tk_type: TkType::Until, wd }), - "case" => self.tokens.push(Tk { tk_type: TkType::Case, wd }), - "select" => self.tokens.push(Tk { tk_type: TkType::Select, wd }), - _ => unreachable!("text: {}", wd.text) - } - } else if matches!(wd.text.as_str(), ";" | "\n") || self.char_stream.is_empty() { - let flags = match self.context { - Arg => WdFlags::IS_ARG, - Command => { - if BUILTINS.contains(&wd.text.as_str()) { - WdFlags::BUILTIN - } else { - WdFlags::empty() - } - } - _ => unreachable!() - }; - if wd.text.has_unescaped("=") { - self.tokens.push(Tk { tk_type: TkType::Assignment, wd }); - self.context = Arg - } else { - self.tokens.push(Tk { tk_type: TkType::Ident, wd: wd.reset_flags().add_flag(flags) }); - self.context = Arg - } - self.context = Command; - } else { - let flags = match self.context { - Arg => WdFlags::IS_ARG, - Command => { - if BUILTINS.contains(&wd.text.as_str()) { - WdFlags::BUILTIN - } else { - WdFlags::empty() - } - } - _ => unreachable!() - }; - if wd.text.has_unescaped("=") { - self.tokens.push(Tk { tk_type: TkType::Assignment, wd }); - self.context = Arg - } else { - self.tokens.push(Tk { tk_type: TkType::Ident, wd: wd.reset_flags().add_flag(flags) }); - self.context = Arg - } - } - } - fn arg_context(&mut self, mut wd: WordDesc) { - wd = self.complete_word(wd); - match wd.text.as_str() { - "||" | "&&" | "|" | "|&" => { - while self.char_stream.front().is_some_and(|ch| *ch == '\n') { - self.advance(); // Allow line continuation - } - // Push the token - } - _ => { /* Do nothing */ } - } - match wd.text.as_str() { - "||" => { - self.tokens.push(Tk { tk_type: TkType::LogicOr, wd: wd.add_flag(WdFlags::IS_OP) }); - self.context = TkState::Command - } - "&&" => { - self.tokens.push(Tk { tk_type: TkType::LogicAnd, wd: wd.add_flag(WdFlags::IS_OP) }); - self.context = TkState::Command - } - "|" => { - self.tokens.push(Tk { tk_type: TkType::Pipe, wd: wd.add_flag(WdFlags::IS_OP) }); - self.context = TkState::Command - } - "|&" => { - self.tokens.push(Tk { tk_type: TkType::PipeBoth, wd: wd.add_flag(WdFlags::IS_OP) }); - self.context = TkState::Command - } - "&" => { - self.tokens.push(Tk { tk_type: TkType::Background, wd: wd.add_flag(WdFlags::IS_OP) }); - self.context = TkState::Command - } - ";" | "\n" => { - self.tokens.push(Tk::cmdsep(&wd,wd.span.start)); - self.context = TkState::Command - } - _ if REGEX["redirection"].is_match(&wd.text) => { - let mut fd_out; - let operator; - let fd_target; - if let Some(caps) = REGEX["redirection"].captures(&wd.text) { - fd_out = caps.name("fd_out").and_then(|fd| fd.as_str().parse::().ok()).unwrap_or(1); - operator = caps.name("operator").unwrap().as_str(); - fd_target = caps.name("fd_target").and_then(|fd| fd.as_str().parse::().ok()) - } else { unreachable!() } - let op = match operator { - ">" => RedirType::Output, - ">>" => RedirType::Append, - "<" => RedirType::Input, - "<<<" => RedirType::Herestring, - _ => unimplemented!() - }; - if matches!(op, RedirType::Input | RedirType::Herestring) { - fd_out = 0 - } - let redir = Redir { - fd_source: fd_out, - op, - fd_target, - file_target: None - }; - self.tokens.push(Tk { tk_type: TkType::Redirection { redir }, wd }) - } - _ => { - self.tokens.push(Tk { tk_type: TkType::Ident, wd: wd.add_flag(WdFlags::IS_ARG) }); - } - } - } - fn string_context(&mut self, mut wd: WordDesc) { - // Pop the opening quote - self.advance(); - while let Some(ch) = self.advance() { - match ch { - '\\' => { - wd = wd.add_char(ch); - let next_char = self.advance(); - if let Some(ch) = next_char { - wd = wd.add_char(ch) - } - } - '"' if self.context == TkState::DQuote => { - self.context = TkState::Arg; - break - } - '\'' if self.context == TkState::SQuote => { - self.context = TkState::Arg; - break - } - _ => { - wd = wd.add_char(ch) - } - } - } - wd.span = self.span; - self.tokens.push(Tk { tk_type: TkType::String, wd }) - } - fn vardec_context(&mut self, mut wd: WordDesc) -> Result<(),ShellError> { - let mut found = false; - loop { - let span = wd.span; - if self.char_stream.is_empty() { - return Err(ShellError::from_parse("Did not find an `in` keyword for this statement", span)) - } - wd = self.complete_word(wd); - match wd.text.as_str() { - "in" => { - if !found { - return Err(ShellError::from_parse("Did not find a variable for this statement", wd.span)) - } - self.tokens.push(Tk { tk_type: TkType::In, wd }); - break - } - _ => { - if self.context == TkState::SingleVarDec && found { - return Err(ShellError::from_parse(format!("Only expected one variable in this statement, found: {}",wd.text).as_str(), wd.span)) - } - found = true; - self.tokens.push(Tk { tk_type: TkType::Ident, wd: take(&mut wd) }); - wd = wd.set_span(span); - } - } - } - self.context = TkState::ArrDec; - Ok(()) - } - fn arrdec_context(&mut self, mut wd: WordDesc) -> Result<(),ShellError> { - let mut found = false; - while self.char_stream.front().is_some_and(|ch| !matches!(ch, ';' | '\n')) { - found = true; - wd = self.complete_word(wd); - wd.span = self.span; - self.tokens.push(Tk { tk_type: TkType::Ident, wd: take(&mut wd) }); - wd = wd.set_span(self.span) - } - if self.char_stream.front().is_some_and(|ch| matches!(ch, ';' | '\n')) { - if !found { - return Err(ShellError::from_parse("Did not find any array elements for this statement", wd.span)) - } - self.advance(); - self.tokens.push(Tk::cmdsep(&wd,self.span.end + 1)) - } - self.context = TkState::Command; - Ok(()) - } - fn subshell_context(&mut self, mut wd: WordDesc) { - self.advance(); - if self.context == TkState::CommandSub { self.advance(); } - let mut paren_stack = vec!['(']; - while let Some(ch) = self.advance() { - match ch { - '(' => { - paren_stack.push(ch); - wd = wd.add_char(ch) - } - ')' => { - paren_stack.pop(); - if paren_stack.is_empty() { - break - } - wd = wd.add_char(ch); - } - _ => { - wd = wd.add_char(ch) - } - } - } - wd.span = self.span; - let tk = match self.context { - TkState::Subshell => { - Tk { - tk_type: TkType::Subshell, - wd - } - } - TkState::CommandSub => { - Tk { - tk_type: TkType::CommandSub, - wd - } - } - _ => unreachable!() - }; - self.tokens.push(tk); - self.context = TkState::Arg; - } - fn func_context(&mut self, mut wd: WordDesc) { - self.advance(); - if self.context == TkState::CommandSub { self.advance(); } - let mut brace_stack = vec!['{']; - while let Some(ch) = self.advance() { - match ch { - '{' => { - brace_stack.push(ch); - wd = wd.add_char(ch) - } - '}' => { - brace_stack.pop(); - if brace_stack.is_empty() { - break - } - wd = wd.add_char(ch); - } - _ => { - wd = wd.add_char(ch) - } - } - } - wd.span = self.span; - wd.text = wd.text.trim().to_string(); - self.tokens.push(Tk { tk_type: TkType::FuncBody, wd }) - } - fn case_context(&mut self, mut wd: WordDesc) -> Result<(), ShellError> { - use crate::interp::token::TkState::*; - let span = wd.span; - self.context = SingleVarDec; - self.vardec_context(take(&mut wd))?; - if self.char_stream.front().is_some_and(|ch| matches!(*ch, ';' | '\n')) { - self.advance(); - self.tokens.push(Tk::cmdsep(&wd,span.end + 1)); - } - while !self.char_stream.is_empty() { - // Get pattern - let mut pat = String::new(); - while let Some(ch) = self.advance() { - if ch == ')' { break } - pat.push(ch); - } - wd.text = pat; - if wd.text == "esac" { - self.tokens.push(Tk { tk_type: TkType::Esac, wd: take(&mut wd) }); - break - } - let span = wd.span; - self.tokens.push(Tk { tk_type: TkType::CasePat, wd: take(&mut wd) }); - - // Get body - let mut body = String::new(); - let mut prev_char = None; - while let Some(ch) = self.advance() { - if ch == ';' && prev_char == Some(';') { - break - } - prev_char = Some(ch); - body.push(ch); - } - let mut body_tokenizer = RshTokenizer::new(&body); - body_tokenizer.tokenize()?; - let len = body_tokenizer.tokens.len(); - let body_tokens = Vec::from(&body_tokenizer.tokens[1..len-1]); // Strip SOI/EOI tokens - self.tokens.extend(body_tokens); - wd = wd.set_span(span); - } - self.context = DeadEnd; - Ok(()) - } - fn complete_word(&mut self, mut wd: WordDesc) -> WordDesc { - let mut dub_quote = false; - let mut sng_quote = false; - while let Some(ch) = self.advance() { - if matches!(ch, '\'') && !dub_quote { - // Single quote handling - sng_quote = !sng_quote; - wd = wd.add_char(ch); - } else if matches!(ch, '"') && !sng_quote { - // Double quote handling - dub_quote = !dub_quote; - wd = wd.add_char(ch); - } else if dub_quote || sng_quote { - // Inside a quoted string - wd = wd.add_char(ch); - } else if !matches!(ch, ' ' | '\t' | '\n' | ';') { - // Regular character - wd = wd.add_char(ch); - } else if matches!(ch, '\n' | ';') { - // Preserve cmdsep for tokenizing - self.char_stream.push_front(ch); - self.span.end -= 1; - wd.span = self.span; - break; - } else { - // Whitespace handling - self.char_stream.push_front(ch); - while self.char_stream.front().is_some_and(|c| matches!(c, ' ' | '\t')) { - self.advance(); - } - break; - } - } - wd - } - fn clean_tokens(&mut self) { - let mut buffer = VecDeque::new(); - let mut tokens = VecDeque::from(take(&mut self.tokens)); - while let Some(token) = tokens.pop_front() { - if matches!(token.tk_type, TkType::SOI | TkType::EOI | TkType::Cmdsep) || !token.text().is_empty() { - buffer.push_back(token); - } - } - self.tokens.extend(buffer.drain(..)); - } -} diff --git a/overlay/pkgs/rsh/rsh/src/main.rs b/overlay/pkgs/rsh/rsh/src/main.rs deleted file mode 100644 index 1ac589c..0000000 --- a/overlay/pkgs/rsh/rsh/src/main.rs +++ /dev/null @@ -1,168 +0,0 @@ -//pub mod event; -//pub mod prompt; -//pub mod parser; -//mod rsh; -// -//use log::debug; -// -//use tokio::io::{self, AsyncBufReadExt, BufReader}; -//use tokio_stream::{wrappers::LinesStream, StreamExt}; -// -//use crate::event::EventLoop; -// -//#[tokio::main] -//async fn main() { -//env_logger::init(); -//let mut event_loop = EventLoop::new(); -// -//debug!("Starting event loop"); -//TODO: use the value returned by this for something -//let _ = event_loop.listen().await; -//} - - -pub mod prompt; -pub mod event; -pub mod execute; -pub mod shellenv; -pub mod interp; -pub mod builtin; -pub mod comp; - -use std::{env, fs::File, io::Read, os::fd::{AsFd, BorrowedFd}}; - -use event::{EventLoop, ShellError, ShellErrorFull}; -use execute::{NodeWalker, RshWaitStatus}; -use interp::parse::descend; -use libc::STDERR_FILENO; -use log::info; -use nix::unistd::write; -use shellenv::EnvFlags; - -//use crate::event::EventLoop; -use crate::shellenv::ShellEnv; - - -#[tokio::main] -async fn main() { - let args = env::args().collect::>(); - - let mut shellenv = ShellEnv::new(EnvFlags::empty()); - if args[0].starts_with('-') { - shellenv.source_profile().ok(); - } - match args.len() { - 1 => { - shellenv.set_flags(EnvFlags::INTERACTIVE); - main_interactive(shellenv).await; - }, - _ => { - main_noninteractive(args,shellenv).await; - } - } - -} - - - -async fn main_noninteractive(args: Vec, mut shellenv: ShellEnv) { - let stderr = unsafe { BorrowedFd::borrow_raw(STDERR_FILENO) }; - let mut pos_params: Vec = vec![]; - let input; - - // Input Handling - if args[1] == "-c" { - if args.len() < 3 { - write(stderr.as_fd(), b"Expected a command after '-c' flag\n").unwrap(); - return; - } - input = args[2].clone(); // Store the command string - } else { - let script_name = &args[1]; - if args.len() > 2 { - pos_params = args[2..].to_vec(); - } - let file = File::open(script_name); - match file { - Ok(mut script) => { - let mut buffer = vec![]; - if let Err(e) = script.read_to_end(&mut buffer) { - write(stderr.as_fd(), format!("Error reading file: {}\n", e).as_bytes()).unwrap(); - return; - } - input = String::from_utf8_lossy(&buffer).to_string(); // Convert file contents to String - } - Err(e) => { - write(stderr.as_fd(), format!("Error opening file: {}\n", e).as_bytes()).unwrap(); - return; - } - } - } - - // Code Execution Logic - shellenv.set_last_input(&input); - for (index,param) in pos_params.into_iter().enumerate() { - let key = format!("{}",index + 1); - shellenv.set_parameter(key, param); - } - let state = descend(&input, &shellenv); - match state { - Ok(parse_state) => { - let mut walker = NodeWalker::new(parse_state.ast, &mut shellenv); - match walker.start_walk() { - Ok(code) => { - info!("Last exit status: {:?}", code); - if let RshWaitStatus::Fail { code, cmd, span } = code { - if code == 127 { - if let Some(cmd) = cmd { - let err = ShellErrorFull::from(shellenv.get_last_input(),ShellError::from_no_cmd(&cmd, span)); - write(stderr, format!("{}", err).as_bytes()).unwrap(); - } - } - } - } - Err(e) => { - let err = ShellErrorFull::from(shellenv.get_last_input(),e); - write(stderr.as_fd(), format!("{}", err).as_bytes()).unwrap(); - } - } - } - Err(e) => { - let err = ShellErrorFull::from(shellenv.get_last_input(),e); - write(stderr.as_fd(), format!("{}", err).as_bytes()).unwrap(); - } - } -} - -async fn main_interactive(mut shellenv: ShellEnv) { - env_logger::init(); - let mut event_loop = EventLoop::new(&mut shellenv); - let _ = event_loop.listen().await; -} - -//#[tokio::main] -//async fn main() { -//env_logger::init(); -//loop { -//let mut stdout = io::stdout(); -//stdout.write_all(b"> ").await.unwrap(); -//stdout.flush().await.unwrap(); -// -//let stdin = io::stdin(); -//let mut reader = BufReader::new(stdin); -// -//let mut input = String::new(); -//reader.read_line(&mut input).await.unwrap(); -// -//let trimmed_input = input.trim().to_string(); -//trace!("Received input! {}",trimmed_input); -//let shellenv = ShellEnv::new(false,false); -//let state = descend(&input,&shellenv); -//if let Err(e) = state { -//println!("{}",e); -//} else { -//println!("debug tree:"); -//println!("{}",state.unwrap().ast); -//} -//} -//} diff --git a/overlay/pkgs/rsh/rsh/src/prompt.rs b/overlay/pkgs/rsh/rsh/src/prompt.rs deleted file mode 100644 index 3610e77..0000000 --- a/overlay/pkgs/rsh/rsh/src/prompt.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::comp::RshHelper; -use crate::event::Signals; -use std::path::{Path, PathBuf}; - -use tokio::sync::mpsc; -use log::info; -use rustyline::{self, config::Configurer, error::ReadlineError, history::{DefaultHistory, History}, ColorMode, Config, EditMode, Editor}; - -use crate::{event::ShellEvent, shellenv::ShellEnv}; -use crate::interp::parse::{descend, NdType}; -use crate::interp::expand; - - - -fn init_prompt(shellenv: &ShellEnv) -> Editor { - let mut config = Config::builder(); - - // Read options from ShellEnv - let max_size = shellenv.get_shopt("max_hist").max(1000); // Default to 1000 - let hist_dupes = shellenv.get_shopt("hist_ignore_dupes") != 0; - let comp_limit = shellenv.get_shopt("comp_limit").max(5); // Default to 5 - let edit_mode = match shellenv.get_shopt("edit_mode") { - 0 => EditMode::Emacs, - _ => EditMode::Vi, - }; - let auto_hist = shellenv.get_shopt("auto_hist") != 0; - let prompt_highlight = match shellenv.get_shopt("prompt_highlight") { - 0 => ColorMode::Disabled, - _ => ColorMode::Enabled, - }; - let tab_stop = shellenv.get_shopt("tab_stop").max(1); // Default to at least 1 - - // Build configuration - config = config - .max_history_size(max_size) - .unwrap_or_else(|e| { - eprintln!("Invalid max history size: {}", e); - std::process::exit(1); - }) - .history_ignore_dups(hist_dupes) - .unwrap() - .completion_prompt_limit(comp_limit) - .edit_mode(edit_mode) - .auto_add_history(auto_hist) - .color_mode(prompt_highlight) - .tab_stop(tab_stop); - - let config = config.build(); - - // Initialize editor - let mut rl = Editor::with_config(config).unwrap_or_else(|e| { - eprintln!("Failed to initialize Rustyline editor: {}", e); - std::process::exit(1); - }); - rl.set_completion_type(rustyline::CompletionType::List); - rl.set_helper(Some(RshHelper::new(shellenv))); - - // Load history file - let hist_path = expand::expand_var(shellenv, "$HIST_FILE".into()); - info!("history path in init_prompt(): {}",hist_path); - let hist_path = PathBuf::from(hist_path); - if let Err(e) = rl.load_history(&hist_path) { - eprintln!("No previous history found or failed to load history: {}", e); - } - - rl -} - -pub async fn prompt(sender: mpsc::Sender, shellenv: &mut ShellEnv) { - let mut rl = init_prompt(shellenv); - let hist_path = expand::expand_var(shellenv, "$HIST_FILE".into()); - let prompt = expand::expand_prompt(shellenv); - match rl.readline(&prompt) { - Ok(line) => { - let _ = rl.history_mut().add(&line); - let _ = rl.history_mut().save(Path::new(&hist_path)); - shellenv.set_last_input(&line); - let state = descend(&line,shellenv); - match state { - Ok(parse_state) => { - if let NdType::Root { deck } = &parse_state.ast.nd_type { - if !deck.is_empty() { - let _ = sender.send(ShellEvent::NewAST(parse_state.ast)).await; - } - } - } - Err(e) => { - let _ = sender.send(ShellEvent::CatchError(e)).await; - } - } - } - Err(ReadlineError::Interrupted) => { - let _ = sender.send(ShellEvent::Signal(Signals::SIGINT)).await; - } - Err(ReadlineError::Eof) => { - let _ = sender.send(ShellEvent::Signal(Signals::SIGQUIT)).await; - } - Err(e) => { - eprintln!("{:?}",e); - } - } - let _ = sender.send(ShellEvent::Prompt).await; -} diff --git a/overlay/pkgs/rsh/rsh/src/shellenv.rs b/overlay/pkgs/rsh/rsh/src/shellenv.rs deleted file mode 100644 index 0e2c4bf..0000000 --- a/overlay/pkgs/rsh/rsh/src/shellenv.rs +++ /dev/null @@ -1,640 +0,0 @@ -use std::borrow::BorrowMut; -use std::env; -use std::fmt::Display; -use nix::sys::signal::{self, Signal}; -use nix::unistd::{gethostname, Pid, User}; -use nix::NixPath; -use std::collections::{HashSet,HashMap}; -use std::ffi::CString; -use std::fs::File; -use std::io::Read; -use std::os::fd::RawFd; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; - -use bitflags::bitflags; -use log::{debug, info, trace}; - -use crate::event::{ShellError, ShellErrorFull}; -use crate::execute::{NodeWalker, RshWaitStatus}; -use crate::interp::expand::expand_var; -use crate::interp::helper; -use crate::interp::parse::{descend, Node, Span}; - -bitflags! { - #[derive(Debug,Clone)] - pub struct JobFlags: i8 { - const LONG = 0b00000001; - const PIDS_ONLY = 0b00000010; - const NEW_ONLY = 0b00000100; - const RUNNING = 0b00001000; - const STOPPED = 0b00010000; - } -} - -#[derive(Debug,Clone)] -pub struct Job { - job_id: i32, - pids: Vec, - commands: Vec, - pgid: Pid, - status: RshWaitStatus, - current: bool, - prev: bool, -} - -impl Job { - pub fn new(job_id: i32, pids: Vec, commands: Vec, pgid: Pid) -> Self { - Self { job_id, pgid, pids, commands, status: RshWaitStatus::Running, current: true, prev: false } - } - pub fn status(&mut self, new_stat: RshWaitStatus) { - self.status = new_stat; - } - pub fn pids(&self) -> &[Pid] { - &self.pids - } - pub fn pgid(&self) -> &Pid { - &self.pgid - } - pub fn commands(&self) -> Vec { - self.commands.clone() - } - pub fn get_status(&self) -> RshWaitStatus { - self.status.clone() - } - pub fn signal_proc(&self, sig: Signal) -> Result<(),ShellError> { - if self.pids().len() == 1 { - let pid = *self.pids().first().unwrap(); - signal::kill(pid, sig).map_err(|_| ShellError::from_io()) - } else { - signal::killpg(self.pgid, sig).map_err(|_| ShellError::from_io()) - } - } - pub fn print(&self, long: bool) -> String { - let mut output = String::new(); - - // Add job ID and status - let symbol = if self.current { "+" } else if self.prev { "-" } else { " " }; - output.push_str(&format!("[{}]{} \t", self.job_id, symbol)); - let padding_num = symbol.len() + self.job_id.to_string().len() + 3; - let mut padding: String = " ".repeat(padding_num); - padding.push('\t'); - - // Add commands and PIDs - for (i, cmd) in self.commands.iter().enumerate() { - let mut cmd = cmd.clone(); - if i != self.commands.len() - 1 { - cmd.push_str(" |"); - } - if long { - // Long format includes PIDs - output.push_str(&format!( - "{}{} {} {}\n", - if i != 0 { padding.clone() } else { "".into() }, - self.pids().get(i).unwrap(), - self.status, - cmd - )); - } else { - // Short format only includes commands - output.push_str(&format!( - "{}{} {}\n", - if i != 0 { padding.clone() } else { "".into() }, - self.status, - cmd - ) - ); - } - } - - output - } - -} - -impl Display for Job { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Determine the job symbol - let symbol = if self.current { "+" } else if self.prev { "-" } else { "" }; - - // Write the job ID and symbol - write!(f, "[{}]{} ", self.job_id, symbol)?; - - // Iterate over processes in the job - for i in 0..self.pids.len() { - let cmd = self.commands.get(i).unwrap(); - // Add padding for subsequent lines - let padding = if i == 0 { "" } else { " " }; - let cmd = if i == self.pids.len() - 1 { cmd } else { &format!("{}{}",cmd," |") }; - writeln!(f, "{}\t{}", padding, cmd)?; - } - - Ok(()) - } -} - -bitflags! { - #[derive(Debug,Copy,Clone,PartialEq)] - pub struct EnvFlags: u32 { - // Guard conditions against infinite alias/var/function recursion - const NO_ALIAS = 0b00000000000000000000000000000001; - const NO_VAR = 0b00000000000000000000000000000010; - const NO_FUNC = 0b00000000000000000000000000000100; - - // Context - const IN_FUNC = 0b00000000000000000000000000001000; // Enables the `return` builtin - const INTERACTIVE = 0b00000000000000000000000000010000; - const CLEAN = 0b00000000000000000000000000100000; // Do not inherit env vars from parent - const NO_RC = 0b00000000000000000000000001000000; - - // Options set by 'set' command - const EXPORT_ALL_VARS = 0b00000000000000000000000010000000; // set -a - const REPORT_JOBS_ASAP = 0b00000000000000000000000100000000; // set -b - const EXIT_ON_ERROR = 0b00000000000000000000001000000000; // set -e - const NO_GLOB = 0b00000000000000000000010000000000; // set -f - const HASH_CMDS = 0b00000000000000000000100000000000; // set -h - const ASSIGN_ANYWHERE = 0b00000000000000000001000000000000; // set -k - const ENABLE_JOB_CTL = 0b00000000000000000010000000000000; // set -m - const NO_EXECUTE = 0b00000000000000000100000000000000; // set -n - const ENABLE_RSHELL = 0b00000000000000001000000000000000; // set -r - const EXIT_AFTER_EXEC = 0b00000000000000010000000000000000; // set -t - const UNSET_IS_ERROR = 0b00000000000000100000000000000000; // set -u - const PRINT_INPUT = 0b00000000000001000000000000000000; // set -v - const STACK_TRACE = 0b00000000000010000000000000000000; // set -x - const EXPAND_BRACES = 0b00000000000100000000000000000000; // set -B - const NO_OVERWRITE = 0b00000000001000000000000000000000; // set -C - const INHERIT_ERR = 0b00000000010000000000000000000000; // set -E - const HIST_SUB = 0b00000000100000000000000000000000; // set -H - const NO_CD_SYMLINKS = 0b00000001000000000000000000000000; // set -P - const INHERIT_RET = 0b00000010000000000000000000000000; // set -T - } -} - -impl PartialEq for ShellEnv { - fn eq(&self, other: &Self) -> bool { - // Compare the inner value of `output_buffer` - let self_output = self.output_buffer.lock().unwrap(); - let other_output = other.output_buffer.lock().unwrap(); - - *self_output == *other_output - && self.flags == other.flags - && self.env_vars == other.env_vars - && self.variables == other.variables - && self.aliases == other.aliases - && self.shopts == other.shopts - && self.functions == other.functions - && self.parameters == other.parameters - && self.open_fds == other.open_fds - && self.last_input == other.last_input - } -} - -#[derive(Clone,Debug)] -pub struct JobTable { - jobs: HashMap, - curr_job: Option, - prev_job: Option, - updated_since_check: HashMap -} - -impl JobTable { - fn new() -> Self { - Self { - jobs: HashMap::new(), - curr_job: None, - prev_job: None, - updated_since_check: HashMap::new() - } - } - pub fn print_jobs(&self, flags: &JobFlags) { - let jobs = self.jobs.values().collect::>(); - for job in jobs { - // Filter jobs based on flags - if flags.contains(JobFlags::RUNNING) && !matches!(job.status, RshWaitStatus::Running) { - continue; - } - if flags.contains(JobFlags::STOPPED) && !matches!(job.status, RshWaitStatus::Stopped {..}) { - continue; - } - if flags.contains(JobFlags::PIDS_ONLY) { - // Print only PIDs - for pid in &job.pids { - println!("{}", pid); - } - } else { - // Print the job in the selected format - println!("{}", job.print(flags.contains(JobFlags::LONG))); - } - } - } -} - -#[derive(Debug,Clone)] -pub struct ShellEnv { - pub flags: EnvFlags, - pub output_buffer: Arc>, - pub env_vars: HashMap, - pub variables: HashMap, - pub aliases: HashMap, - pub shopts: HashMap, - pub functions: HashMap>, - pub parameters: HashMap, - pub open_fds: HashSet, - pub last_input: Option, - pub job_table: JobTable -} - -impl ShellEnv { - // Constructor - pub fn new(flags: EnvFlags) -> Self { - let mut open_fds = HashSet::new(); - let shopts = init_shopts(); - // TODO: probably need to find a way to initialize env vars that doesnt rely on a parent process - let clean_env = flags.contains(EnvFlags::CLEAN); - let env_vars = Self::init_env_vars(clean_env); - open_fds.insert(0); - open_fds.insert(1); - open_fds.insert(2); - let mut shellenv = Self { - flags: EnvFlags::empty(), - output_buffer: Arc::new(Mutex::new(String::new())), - env_vars, - variables: HashMap::new(), - aliases: HashMap::new(), - shopts, - functions: HashMap::new(), - parameters: HashMap::new(), - open_fds, - last_input: None, - job_table: JobTable::new() - }; - if !flags.contains(EnvFlags::NO_RC) { - let runtime_commands_path = &expand_var(&shellenv, "${HOME}/.rshrc".into()); - let runtime_commands_path = Path::new(runtime_commands_path); - if runtime_commands_path.exists() { - if let Err(e) = shellenv.source_file(runtime_commands_path.to_path_buf()) { - let err = ShellErrorFull::from(shellenv.get_last_input(),e); - eprintln!("Failed to source ~/.rshrc: {}",err); - } - } else { - eprintln!("Warning: Runtime commands file '{}' not found.", runtime_commands_path.display()); - } - } - shellenv - } - - pub fn new_job(&mut self, pids: Vec, commands: Vec, pgid: Pid) { - let job_id = self.job_table.jobs.len() + 1; - let job = Job::new(job_id as i32,pids,commands,pgid); - println!("{}",job); - self.job_table.jobs.insert(job_id as i32, job); - self.set_curr_job(job_id as i32); - } - - pub fn borrow_jobs(&mut self) -> &mut HashMap { - &mut self.job_table.jobs - } - - pub fn set_curr_job(&mut self, job_id: i32) { - self.job_table.prev_job = self.job_table.curr_job; - self.job_table.curr_job = Some(job_id); - } - - fn init_env_vars(clean: bool) -> HashMap { - let pathbuf_to_string = |pb: Result| pb.unwrap_or_default().to_string_lossy().to_string(); - // First, inherit any env vars from the parent process if clean bit not set - let mut env_vars = HashMap::new(); - if !clean { - env_vars = std::env::vars().collect::>(); - } - let home; - let username; - let uid; - if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() { - home = user.dir; - username = user.name; - uid = user.uid; - } else { - home = PathBuf::new(); - username = "unknown".into(); - uid = 0.into(); - } - let home = pathbuf_to_string(Ok(home)); - let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default(); - - env_vars.insert("HOSTNAME".into(), hostname.clone()); - env::set_var("HOSTNAME", hostname); - env_vars.insert("UID".into(), uid.to_string()); - env::set_var("UID", uid.to_string()); - env_vars.insert("TMPDIR".into(), "/tmp".into()); - env::set_var("TMPDIR", "/tmp"); - env_vars.insert("TERM".into(), "xterm-256color".into()); - env::set_var("TERM", "xterm-256color"); - env_vars.insert("LANG".into(), "en_US.UTF-8".into()); - env::set_var("LANG", "en_US.UTF-8"); - env_vars.insert("USER".into(), username.clone()); - env::set_var("USER", username.clone()); - env_vars.insert("LOGNAME".into(), username.clone()); - env::set_var("LOGNAME", username); - env_vars.insert("PWD".into(), pathbuf_to_string(std::env::current_dir())); - env::set_var("PWD", pathbuf_to_string(std::env::current_dir())); - env_vars.insert("OLDPWD".into(), pathbuf_to_string(std::env::current_dir())); - env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir())); - env_vars.insert("HOME".into(), home.clone()); - env::set_var("HOME", home.clone()); - env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe())); - env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); - env_vars.insert("HIST_FILE".into(),format!("{}/.rsh_hist",home)); - env::set_var("HIST_FILE",format!("{}/.rsh_hist",home)); - env_vars - } - - pub fn mod_flags(&mut self, transform: F) where F: FnOnce(&mut EnvFlags) { - transform(&mut self.flags) - } - - pub fn is_interactive(&self) -> bool { - self.flags.contains(EnvFlags::INTERACTIVE) - } - - pub fn set_last_input(&mut self, input: &str) { - self.last_input = Some(input.to_string()) - } - - pub fn get_last_input(&mut self) -> String { - self.last_input.clone().unwrap_or_default() - } - - pub fn source_profile(&mut self) -> Result<(),ShellError> { - let home = self.get_variable("HOME").unwrap(); - let path = PathBuf::from(format!("{}/.rsh_profile",home)); - self.source_file(path) - } - - pub fn source_file(&mut self, path: PathBuf) -> Result<(),ShellError> { - let mut file = File::open(&path).map_err(|_| ShellError::from_io())?; - let mut buffer = String::new(); - file.read_to_string(&mut buffer).map_err(|_| ShellError::from_io())?; - self.last_input = Some(buffer.clone()); - - - let state = descend(&buffer, self); - match state { - Ok(parse_state) => { - let mut walker = NodeWalker::new(parse_state.ast, self); - match walker.start_walk() { - Ok(code) => { - info!("Last exit status: {:?}", code); - if let RshWaitStatus::Fail { code, cmd, span } = code { - if code == 127 { - if let Some(cmd) = cmd { - let err = ShellErrorFull::from(self.get_last_input(),ShellError::from_no_cmd(&format!("Command not found: {}",cmd), span)); - eprintln!("{}", err); - } - } - } - } - Err(e) => { - let err = ShellErrorFull::from(self.get_last_input(), e); - eprintln!("{}", err); - } - } - } - Err(e) => { - let err = ShellErrorFull::from(self.get_last_input(), e); - eprintln!("Encountered error while sourcing file: {}\n{}",path.display(), err); - } - } - Ok(()) - } - - pub fn change_dir(&mut self, path: &Path, span: Span) -> Result<(), ShellError> { - let result = helper::suppress_err(|| env::set_current_dir(path)); - match result { - Ok(_) => { - let old_pwd = self.env_vars.remove("PWD").unwrap_or_default(); - self.export_variable("OLDPWD".into(), old_pwd); - let new_dir = env::current_dir().unwrap().to_string_lossy().to_string(); - self.export_variable("PWD".into(), new_dir); - Ok(()) - } - Err(e) => Err(ShellError::from_execf(&e.to_string(), 1, span)) - } - } - - pub fn set_flags(&mut self, flags: EnvFlags) { - self.flags |= flags; - self.update_flags_param(); - } - - pub fn unset_flags(&mut self, flags:EnvFlags) { - self.flags &= !flags; - self.update_flags_param(); - } - - pub fn update_flags_param(&mut self) { - let mut flag_list = "abefhkmnrptuvxBCEHPT".chars(); - let mut flag_string = String::new(); - while let Some(ch) = flag_list.next() { - match ch { - 'a' if self.flags.contains(EnvFlags::EXPORT_ALL_VARS) => flag_string.push(ch), - 'b' if self.flags.contains(EnvFlags::REPORT_JOBS_ASAP) => flag_string.push(ch), - 'e' if self.flags.contains(EnvFlags::EXIT_ON_ERROR) => flag_string.push(ch), - 'f' if self.flags.contains(EnvFlags::NO_GLOB) => flag_string.push(ch), - 'h' if self.flags.contains(EnvFlags::HASH_CMDS) => flag_string.push(ch), - 'k' if self.flags.contains(EnvFlags::ASSIGN_ANYWHERE) => flag_string.push(ch), - 'm' if self.flags.contains(EnvFlags::ENABLE_JOB_CTL) => flag_string.push(ch), - 'n' if self.flags.contains(EnvFlags::NO_EXECUTE) => flag_string.push(ch), - 'r' if self.flags.contains(EnvFlags::ENABLE_RSHELL) => flag_string.push(ch), - 't' if self.flags.contains(EnvFlags::EXIT_AFTER_EXEC) => flag_string.push(ch), - 'u' if self.flags.contains(EnvFlags::UNSET_IS_ERROR) => flag_string.push(ch), - 'v' if self.flags.contains(EnvFlags::PRINT_INPUT) => flag_string.push(ch), - 'x' if self.flags.contains(EnvFlags::STACK_TRACE) => flag_string.push(ch), - 'B' if self.flags.contains(EnvFlags::EXPAND_BRACES) => flag_string.push(ch), - 'C' if self.flags.contains(EnvFlags::NO_OVERWRITE) => flag_string.push(ch), - 'E' if self.flags.contains(EnvFlags::INHERIT_ERR) => flag_string.push(ch), - 'H' if self.flags.contains(EnvFlags::HIST_SUB) => flag_string.push(ch), - 'P' if self.flags.contains(EnvFlags::NO_CD_SYMLINKS) => flag_string.push(ch), - 'T' if self.flags.contains(EnvFlags::INHERIT_RET) => flag_string.push(ch), - _ => { /* Do nothing */ } - } - } - self.set_parameter("-".into(), flag_string); - } - - pub fn open_fd(&mut self, fd: RawFd) { - self.open_fds.insert(fd); - } - - pub fn close_fd(&mut self, fd: RawFd) { - self.open_fds.remove(&fd); - } - - pub fn handle_exit_status(&mut self, wait_status: RshWaitStatus) { - match wait_status { - RshWaitStatus::Success { .. } => self.set_parameter("?".into(), "0".into()), - RshWaitStatus::Fail { code, cmd: _, span: _ } => self.set_parameter("?".into(), code.to_string()), - _ => unimplemented!() - } - } - - pub fn set_interactive(&mut self, interactive: bool) { - if interactive { - self.flags |= EnvFlags::INTERACTIVE - } else { - self.flags &= !EnvFlags::INTERACTIVE - } - } - - // Getters and Setters for `variables` - pub fn get_variable(&self, key: &str) -> Option { - if let Some(value) = self.variables.get(key) { - Some(value.to_string()) - } else if let Some(value) = self.env_vars.get(key) { - Some(value.to_string()) - } else { - self.get_parameter(key).map(|val| val.to_string()) - } - } - - /// For C FFI calls - pub fn get_cvars(&self) -> Vec { - self.env_vars - .iter() - .map(|(key, value)| { - let env_pair = format!("{}={}", key, value); - CString::new(env_pair).unwrap() }) - .collect::>() - } - - pub fn set_variable(&mut self, key: String, mut value: String) { - debug!("inserted var: {} with value: {}",key,value); - if value.starts_with('"') && value.ends_with('"') { - value = value.strip_prefix('"').unwrap().into(); - value = value.strip_suffix('"').unwrap().into(); - } - self.variables.insert(key.clone(), value); - trace!("testing variable get: {} = {}", key, self.get_variable(key.as_str()).unwrap()) - } - - pub fn get_shopt(&self, key: &str) -> usize { - self.shopts[key] - } - - pub fn set_shopt(&mut self, key: &str, value: usize) { - self.shopts.insert(key.into(),value); - } - - pub fn export_variable(&mut self, key: String, value: String) { - let value = value.trim_matches(|ch| ch == '"').to_string(); - if value.as_str() == "" { - self.variables.remove(&key); - self.env_vars.remove(&key); - } else { - self.variables.insert(key.clone(),value.clone()); - self.env_vars.insert(key,value); - } - } - - pub fn remove_variable(&mut self, key: &str) -> Option { - self.variables.remove(key) - } - - // Getters and Setters for `aliases` - pub fn get_alias(&self, key: &str) -> Option<&String> { - if self.flags.contains(EnvFlags::NO_ALIAS) { - return None - } - self.aliases.get(key) - } - - pub fn set_alias(&mut self, key: String, mut value: String) -> Result<(), String> { - if self.get_function(key.as_str()).is_some() { - return Err(format!("The name `{}` is already being used as a function",key)) - } - if value.starts_with('"') && value.ends_with('"') { - value = value.strip_prefix('"').unwrap().into(); - value = value.strip_suffix('"').unwrap().into(); - } - self.aliases.insert(key, value); - Ok(()) - } - - pub fn remove_alias(&mut self, key: &str) -> Option { - self.aliases.remove(key) - } - - // Getters and Setters for `functions` - pub fn get_function(&self, name: &str) -> Option> { - self.functions.get(name).cloned() - } - - pub fn set_function(&mut self, name: String, body: Box) -> Result<(),ShellError> { - if self.get_alias(name.as_str()).is_some() { - return Err(ShellError::from_parse(format!("The name `{}` is already being used as an alias",name).as_str(), body.span())) - } - self.functions.insert(name, body); - Ok(()) - } - - pub fn remove_function(&mut self, name: &str) -> Option> { - self.functions.remove(name) - } - - // Getters and Setters for `parameters` - pub fn get_parameter(&self, key: &str) -> Option<&String> { - if key == "*" { - // Return the un-split parameter string - return self.parameters.get("@") - } - self.parameters.get(key) - } - - pub fn set_parameter(&mut self, key: String, value: String) { - if key.chars().next().unwrap().is_ascii_digit() { - let mut pos_params = self.parameters.remove("@").unwrap_or_default(); - if pos_params.is_empty() { - pos_params = value.clone(); - } else { - pos_params = format!("{} {}",pos_params,value); - } - self.parameters.insert("@".into(),pos_params.clone()); - let num_params = pos_params.split(' ').count(); - self.parameters.insert("#".into(),num_params.to_string()); - } - self.parameters.insert(key, value); - } - - pub fn clear_pos_parameters(&mut self) { - let mut index = 1; - while let Some(_value) = self.get_parameter(index.to_string().as_str()) { - self.parameters.remove(index.to_string().as_str()); - index += 1; - } - } - - // Utility method to clear the environment - pub fn clear(&mut self) { - self.variables.clear(); - self.aliases.clear(); - self.functions.clear(); - self.parameters.clear(); - } -} - -fn init_shopts() -> HashMap { - let mut shopts = HashMap::new(); - shopts.insert("dotglob".into(),0); - shopts.insert("trunc_prompt_path".into(),4); - shopts.insert("int_comments".into(),1); - shopts.insert("autocd".into(),1); - shopts.insert("hist_ignore_dupes".into(),1); - shopts.insert("max_hist".into(),1000); - shopts.insert("edit_mode".into(),1); - shopts.insert("comp_limit".into(),100); - shopts.insert("auto_hist".into(),1); - shopts.insert("prompt_highlight".into(),1); - shopts.insert("tab_stop".into(),4); - shopts.insert("bell_style".into(),1); - shopts -} diff --git a/overlay/pkgs/rsh/rsh/tests/basic_case.sh b/overlay/pkgs/rsh/rsh/tests/basic_case.sh deleted file mode 100755 index 4f10fec..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_case.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -case var in - var) echo found it - ;; - bar) echo not here - ;; - car) echo not here either - ;; -esac diff --git a/overlay/pkgs/rsh/rsh/tests/basic_commands.sh b/overlay/pkgs/rsh/rsh/tests/basic_commands.sh deleted file mode 100755 index fa03b34..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_commands.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -ls -echo "Hello, World!" -pwd diff --git a/overlay/pkgs/rsh/rsh/tests/basic_for.sh b/overlay/pkgs/rsh/rsh/tests/basic_for.sh deleted file mode 100755 index aef25b9..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_for.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -for i in 1 2 3; do - echo $i; -done diff --git a/overlay/pkgs/rsh/rsh/tests/basic_if.sh b/overlay/pkgs/rsh/rsh/tests/basic_if.sh deleted file mode 100755 index b75ace2..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_if.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -if [ 1 -eq 1 ]; then - echo true -else - echo false -fi - -if [ 1 -ne 1 ]; then - echo true -else - echo false -fi diff --git a/overlay/pkgs/rsh/rsh/tests/basic_until.sh b/overlay/pkgs/rsh/rsh/tests/basic_until.sh deleted file mode 100755 index 0bb3d8e..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_until.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -var=1 -until [ $var -eq 0 ]; do - var=0; - echo looped -done diff --git a/overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh b/overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh deleted file mode 100755 index 73a11fb..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -var="value" -echo $var diff --git a/overlay/pkgs/rsh/rsh/tests/basic_while.sh b/overlay/pkgs/rsh/rsh/tests/basic_while.sh deleted file mode 100755 index 5deaa31..0000000 --- a/overlay/pkgs/rsh/rsh/tests/basic_while.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -var=1 -while [ $var -eq 1 ]; do - var=0; - echo looped; -done diff --git a/overlay/pkgs/rsh/rsh/tests/chain.sh b/overlay/pkgs/rsh/rsh/tests/chain.sh deleted file mode 100755 index fda5b66..0000000 --- a/overlay/pkgs/rsh/rsh/tests/chain.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -true && echo it works -false && echo it doesnt work diff --git a/overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh b/overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh deleted file mode 100755 index a022692..0000000 --- a/overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -var="" -echo $var diff --git a/overlay/pkgs/rsh/rsh/tests/pipeline.sh b/overlay/pkgs/rsh/rsh/tests/pipeline.sh deleted file mode 100755 index 5d2ef26..0000000 --- a/overlay/pkgs/rsh/rsh/tests/pipeline.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -echo hello world | grep w diff --git a/overlay/pkgs/rsh/rsh/tests/very_nested.sh b/overlay/pkgs/rsh/rsh/tests/very_nested.sh deleted file mode 100755 index 8a480c3..0000000 --- a/overlay/pkgs/rsh/rsh/tests/very_nested.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh -for var in 1 2 3; do case var in;1) if until until until echo; do echo; done; do if echo; then echo; elif echo; then if echo; then echo; elif echo; then echo; fi; fi; done; do for i in 1 2 3; do echo; done; done; then until if until echo; do echo; done; then if echo; then echo; elif echo; then echo; fi; elif for i in 1 2 3; do echo; done; then until echo; do echo; done; fi; do if echo; then echo; elif echo; then echo; fi; done; fi;;2) until echo; do echo; done;;3) for i in 1 2 3; do echo; done ;;esac; done