Updated flake inputs
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
result
|
||||
target
|
||||
hardware.nix
|
||||
|
||||
286
flake.lock
generated
286
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731959031,
|
||||
"narHash": "sha256-TGcvIjftziC1CjuiHCzrYDwmOoSFYIhdiKmLetzB5L0=",
|
||||
"lastModified": 1736102453,
|
||||
"narHash": "sha256-5qb4kb7Xbt8jJFL/oDqOor9Z2+E+A+ql3PiyDvsfWZ0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "4468981c1c50999f315baa1508f0e53c4ee70c52",
|
||||
"rev": "4846091641f3be0ad7542086d52769bb7932bde6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -107,11 +107,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728330715,
|
||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||
"lastModified": 1735644329,
|
||||
"narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||
"rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -125,11 +125,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734088167,
|
||||
"narHash": "sha256-OIitVU+IstPbX/NWn2jLF+/sT9dVKcO2FKeRAzlyX6c=",
|
||||
"lastModified": 1736165297,
|
||||
"narHash": "sha256-OT+sF4eNDFN/OdyUfIQwyp28+CFQL7PAdWn0wGU7F0U=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "d32f2d1750d61a476a236526b725ec5a32e16342",
|
||||
"rev": "76816af65d5294761636a838917e335992a52e0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -208,11 +208,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733312601,
|
||||
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
||||
"lastModified": 1736143030,
|
||||
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
||||
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -286,18 +286,45 @@
|
||||
"nixpkgs": [
|
||||
"nixvim",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735882644,
|
||||
"narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "a5a961387e75ae44cc20f0a57ae463da5e959656",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"stylix",
|
||||
"flake-compat"
|
||||
],
|
||||
"gitignore": "gitignore_3",
|
||||
"nixpkgs": [
|
||||
"stylix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": [
|
||||
"nixvim",
|
||||
"stylix",
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733318908,
|
||||
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
|
||||
"lastModified": 1731363552,
|
||||
"narHash": "sha256-vFta1uHnD29VUY4HJOO/D6p6rxyObnf+InnSMT4jlMU=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
|
||||
"rev": "cd1af27aa85026ac759d5d3fccf650abe7e1bbf0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -350,6 +377,28 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore_3": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"stylix",
|
||||
"git-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"
|
||||
}
|
||||
},
|
||||
"gnome-shell": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@@ -374,11 +423,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734093295,
|
||||
"narHash": "sha256-hSwgGpcZtdDsk1dnzA0xj5cNaHgN9A99hRF/mxMtwS4=",
|
||||
"lastModified": 1736089250,
|
||||
"narHash": "sha256-/LPWMiiJGPHGd7ZYEgmbE2da4zvBW0acmshUjYC3WG4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "66c5d8b62818ec4c1edb3e941f55ef78df8141a8",
|
||||
"rev": "172b91bfb2b7f5c4a8c6ceac29fd53a01ef07196",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -395,11 +444,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733484277,
|
||||
"narHash": "sha256-i5ay20XsvpW91N4URET/nOc0VQWOAd4c4vbqYtcH8Rc=",
|
||||
"lastModified": 1736089250,
|
||||
"narHash": "sha256-/LPWMiiJGPHGd7ZYEgmbE2da4zvBW0acmshUjYC3WG4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "d00c6f6d0ad16d598bf7e2956f52c1d9d5de3c3a",
|
||||
"rev": "172b91bfb2b7f5c4a8c6ceac29fd53a01ef07196",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -416,11 +465,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733085484,
|
||||
"narHash": "sha256-dVmNuUajnU18oHzBQWZm1BQtANCHaqNuxTHZQ+GN0r8=",
|
||||
"lastModified": 1735774425,
|
||||
"narHash": "sha256-C73gLFnEh8ZI0uDijUgCDWCd21T6I6tsaWgIBHcfAXg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "c1fee8d4a60b89cae12b288ba9dbc608ff298163",
|
||||
"rev": "5f6aa268e419d053c3d5025da740e390b12ac936",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -463,11 +512,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728669738,
|
||||
"narHash": "sha256-EDNAU9AYcx8OupUzbTbWE1d3HYdeG0wO6Msg3iL1muk=",
|
||||
"lastModified": 1734906540,
|
||||
"narHash": "sha256-vQ/L9hZFezC0LquLo4TWXkyniWtYBlFHAKIsDc7PYJE=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprcursor",
|
||||
"rev": "0264e698149fcb857a66a53018157b41f8d97bb0",
|
||||
"rev": "69270ba8f057d55b0e6c2dca0e165d652856e613",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -492,11 +541,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733248371,
|
||||
"narHash": "sha256-FFLJzFTyNhS7tBEEECx0B8Ye/bpmxhFVEKlECgMLc6c=",
|
||||
"lastModified": 1736115290,
|
||||
"narHash": "sha256-Jcn6yAzfUMcxy3tN/iZRbi/QgrYm7XLyVRl9g/nbUl4=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "cc95e5babc6065bc3ab4cd195429a9900836ef13",
|
||||
"rev": "52202272d89da32a9f866c0d10305a5e3d954c50",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -521,11 +570,11 @@
|
||||
"xdph": "xdph"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734121737,
|
||||
"narHash": "sha256-aVI7Oic+PgEX4T6PRH572L2fMxGqnWM9K9xSPNjoV9Y=",
|
||||
"lastModified": 1736191857,
|
||||
"narHash": "sha256-2ADoC5iTawhEmEuDrl5z+gLbLHEsYqdjwnlF5Y5MZmA=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "452a7e6905de61f076065ef4c294dc62f86327ae",
|
||||
"revCount": 5537,
|
||||
"rev": "b9f110ef8726fcba2b4ee69856027731e73003a5",
|
||||
"revCount": 5638,
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://github.com/hyprwm/Hyprland"
|
||||
@@ -548,11 +597,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728345020,
|
||||
"narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=",
|
||||
"lastModified": 1735774328,
|
||||
"narHash": "sha256-vIRwLS9w+N99EU1aJ+XNOU6mJTxrUBa31i1r82l0V7s=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-protocols",
|
||||
"rev": "a7c183800e74f337753de186522b9017a07a8cee",
|
||||
"rev": "e3b6af97ddcfaafbda8e2828c719a5af84f662cb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -577,11 +626,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733472316,
|
||||
"narHash": "sha256-PvXiFLIExJEJj+goLbIuXLTN5CSDSAUsAfiYSdbbWg0=",
|
||||
"lastModified": 1736114838,
|
||||
"narHash": "sha256-FxbuGQExtN37ToWYnGmO6weOYN6WPHN/RAqbr7gNPek=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-qtutils",
|
||||
"rev": "969427419276c7ee170301ef1ebe0f68eb6eb2e2",
|
||||
"rev": "6997fe382dcf396704227d2b98ffdd5066da6959",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -606,11 +655,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728168612,
|
||||
"narHash": "sha256-AnB1KfiXINmuiW7BALYrKqcjCnsLZPifhb/7BsfPbns=",
|
||||
"lastModified": 1735393019,
|
||||
"narHash": "sha256-NPpqA8rtmDLsEmZOmz+qR67zsB6Y503Jnv+nSFLKJZ8=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "f054f2e44d6a0b74607a6bc0f52dba337a3db38e",
|
||||
"rev": "55608efdaa387af7bfdc0eddb404c409958efa43",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -627,11 +676,11 @@
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1732810179,
|
||||
"narHash": "sha256-J39wjWN4+dxjwf8Fw5yv1aVcbkOg7oQi42zVY4ZxJMU=",
|
||||
"lastModified": 1735584197,
|
||||
"narHash": "sha256-B1PqiHp/jmDVXVrvyh/eu2KP3LCyi1JL0h3vuy/wVnM=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprpicker",
|
||||
"rev": "d26cb2f4396e4fd3477f5f12131c87c2f5002ab7",
|
||||
"rev": "444c40e5e3dc4058a6a762ba5e73ada6d6469055",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -652,11 +701,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1732288281,
|
||||
"narHash": "sha256-XTU9B53IjGeJiJ7LstOhuxcRjCOFkQFl01H78sT9Lg4=",
|
||||
"lastModified": 1736164519,
|
||||
"narHash": "sha256-1LimBKvDpBbeX+qW7T240WEyw+DBVpDotZB4JYm8Aps=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "b26f33cc1c8a7fd5076e19e2cce3f062dca6351c",
|
||||
"rev": "3c895da64b0eb19870142196fa48c07090b441c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -677,11 +726,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727300645,
|
||||
"narHash": "sha256-OvAtVLaSRPnbXzOwlR1fVqCXR7i+ICRX3aPMCdIiv+c=",
|
||||
"lastModified": 1733502241,
|
||||
"narHash": "sha256-KAUNC4Dgq8WQjYov5auBw/usaHixhacvb7cRDd0AG/k=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "3f5293432b6dc6a99f26aca2eba3876d2660665c",
|
||||
"rev": "104117aed6dd68561be38b50f218190aa47f2cd8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -702,11 +751,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": {
|
||||
@@ -742,11 +791,11 @@
|
||||
},
|
||||
"impermanence": {
|
||||
"locked": {
|
||||
"lastModified": 1731242966,
|
||||
"narHash": "sha256-B3C3JLbGw0FtLSWCjBxU961gLNv+BOOBC6WvstKLYMw=",
|
||||
"lastModified": 1734945620,
|
||||
"narHash": "sha256-olIfsfJK4/GFmPH8mXMmBDAkzVQ1TWJmeGT3wBGfQPY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "impermanence",
|
||||
"rev": "3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a",
|
||||
"rev": "d000479f4f41390ff7cf9204979660ad5dd16176",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -791,11 +840,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733570843,
|
||||
"narHash": "sha256-sQJAxY1TYWD1UyibN/FnN97paTFuwBw3Vp3DNCyKsMk=",
|
||||
"lastModified": 1736085891,
|
||||
"narHash": "sha256-bTl9fcUo767VaSx4Q5kFhwiDpFQhBKna7lNbGsqCQiA=",
|
||||
"owner": "lnl7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "a35b08d09efda83625bef267eb24347b446c80b8",
|
||||
"rev": "ba9b3173b0f642ada42b78fb9dfc37ca82266f6c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -806,11 +855,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733749988,
|
||||
"narHash": "sha256-+5qdtgXceqhK5ZR1YbP1fAUsweBIrhL38726oIEAtDs=",
|
||||
"lastModified": 1735915915,
|
||||
"narHash": "sha256-Q4HuFAvoKAIiTRZTUxJ0ZXeTC7lLfC9/dggGHNXNlCw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bc27f0fde01ce4e1bfec1ab122d72b7380278e68",
|
||||
"rev": "a27871180d30ebee8aa6b11bf7fef8a52f024733",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -820,22 +869,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1730741070,
|
||||
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1712163089,
|
||||
@@ -854,11 +887,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1733392399,
|
||||
"narHash": "sha256-kEsTJTUQfQFIJOcLYFt/RvNxIK653ZkTBIs4DG+cBns=",
|
||||
"lastModified": 1736012469,
|
||||
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d0797a04b81caeae77bcff10a9dde78bc17f5661",
|
||||
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -870,11 +903,11 @@
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1727122398,
|
||||
"narHash": "sha256-o8VBeCWHBxGd4kVMceIayf5GApqTavJbTa44Xcg5Rrk=",
|
||||
"lastModified": 1734119587,
|
||||
"narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "30439d93eb8b19861ccbe3e581abf97bdc91b093",
|
||||
"rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -886,11 +919,11 @@
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1733940404,
|
||||
"narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=",
|
||||
"lastModified": 1736012469,
|
||||
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713",
|
||||
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -902,16 +935,16 @@
|
||||
},
|
||||
"nixpkgs_6": {
|
||||
"locked": {
|
||||
"lastModified": 1732238832,
|
||||
"narHash": "sha256-sQxuJm8rHY20xq6Ah+GwIUkF95tWjGRd1X8xF+Pkk38=",
|
||||
"lastModified": 1735648875,
|
||||
"narHash": "sha256-fQ4k/hyQiH9RRPznztsA9kbcDajvwV1sRm01el6Sr3c=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8edf06bea5bcbee082df1b7369ff973b91618b8d",
|
||||
"rev": "47e29c20abef74c45322eca25ca1550cdf5c3b50",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -931,11 +964,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734103614,
|
||||
"narHash": "sha256-H5JN0fajkKZLir/GN6QHmLsR3cW+/EIOR+W/VmwHKfI=",
|
||||
"lastModified": 1736157655,
|
||||
"narHash": "sha256-/ggXMK8Q/rN94kaaSHPtEcf4SPKgPXfzSbDgAR6Odzs=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "c181014422fa9261db06fc9b5ecbf67f42c30ec3",
|
||||
"rev": "31139e0605fd886d981e0a197e30ceac4b859d6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -954,11 +987,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733411491,
|
||||
"narHash": "sha256-315rJ7O9cOllPDaFscnJhcMleORHbxon0Kq9LAKJ5p4=",
|
||||
"lastModified": 1735854821,
|
||||
"narHash": "sha256-Iv59gMDZajNfezTO0Fw6LHE7uKAShxbvMidmZREit7c=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "68e9fad70d95d08156cf10a030bd39487bed8ffe",
|
||||
"rev": "836908e3bddd837ae0f13e215dd48767aee355f0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -974,15 +1007,14 @@
|
||||
"nixpkgs": [
|
||||
"hyprland",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733318908,
|
||||
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
|
||||
"lastModified": 1735882644,
|
||||
"narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
|
||||
"rev": "a5a961387e75ae44cc20f0a57ae463da5e959656",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1013,11 +1045,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734063436,
|
||||
"narHash": "sha256-wE1sIAnsjWbyXXjwC/+oxSFXFDCROiwLY1pSQ7pU9js=",
|
||||
"lastModified": 1736136999,
|
||||
"narHash": "sha256-bI3pSXx4fihlWNi4b8+Kp04RkDhctEgGdJuNQw/2O88=",
|
||||
"owner": "gerg-l",
|
||||
"repo": "spicetify-nix",
|
||||
"rev": "7981c1e87aa1adeec524524db52f75bf6598fb55",
|
||||
"rev": "046dc4cc0bcf460cafbfd3fe26fdc584f5b0fb80",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1034,20 +1066,22 @@
|
||||
"base16-vim": "base16-vim",
|
||||
"flake-compat": "flake-compat_4",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"git-hooks": "git-hooks_2",
|
||||
"gnome-shell": "gnome-shell",
|
||||
"home-manager": "home-manager_3",
|
||||
"nixpkgs": "nixpkgs_6",
|
||||
"systems": "systems_4",
|
||||
"tinted-foot": "tinted-foot",
|
||||
"tinted-kitty": "tinted-kitty",
|
||||
"tinted-tmux": "tinted-tmux"
|
||||
"tinted-tmux": "tinted-tmux",
|
||||
"tinted-zed": "tinted-zed"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734110168,
|
||||
"narHash": "sha256-Q0eeLYn45ErXlqGQyXmLLHGe1mqnUiK0Y9wZRa1SNFI=",
|
||||
"lastModified": 1736179037,
|
||||
"narHash": "sha256-uhLRE3x5TrFeQm1waBz6ecqa2EHilFM4jLxYbOJPGrU=",
|
||||
"owner": "danth",
|
||||
"repo": "stylix",
|
||||
"rev": "a9e3779949925ef22f5a215c5f49cf520dea30b1",
|
||||
"rev": "a6b53aa677ab9bd9098abbdc47924854c76c3eb1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1166,6 +1200,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"tinted-zed": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1725758778,
|
||||
"narHash": "sha256-8P1b6mJWyYcu36WRlSVbuj575QWIFZALZMTg5ID/sM4=",
|
||||
"owner": "tinted-theming",
|
||||
"repo": "base16-zed",
|
||||
"rev": "122c9e5c0e6f27211361a04fae92df97940eccf9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tinted-theming",
|
||||
"repo": "base16-zed",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -1174,11 +1224,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733440889,
|
||||
"narHash": "sha256-qKL3vjO+IXFQ0nTinFDqNq/sbbnnS5bMI1y0xX215fU=",
|
||||
"lastModified": 1736115332,
|
||||
"narHash": "sha256-FBG9d7e0BTFfxVdw4b5EmNll2Mv7hfRc54hbB4LrKko=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "50862ba6a8a0255b87377b9d2d4565e96f29b410",
|
||||
"rev": "1788ca5acd4b542b923d4757d4cfe4183cc6a92d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1215,11 +1265,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733157064,
|
||||
"narHash": "sha256-NetqJHAN4bbZDQADvpep+wXk2AbMZ2bN6tINz8Kpz6M=",
|
||||
"lastModified": 1734907020,
|
||||
"narHash": "sha256-p6HxwpRKVl1KIiY5xrJdjcEeK3pbmc///UOyV6QER+w=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "xdg-desktop-portal-hyprland",
|
||||
"rev": "fd85ef39369f95eed67fdf3f025e86916edeea2f",
|
||||
"rev": "d7f18dda5e511749fa1511185db3536208fb1a63",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -142,6 +142,9 @@
|
||||
specialArgs = {
|
||||
inherit self inputs username;
|
||||
host = "oganesson";
|
||||
overlays = [
|
||||
(import ./overlay/overlay.nix { root = self; })
|
||||
];
|
||||
};
|
||||
inherit system;
|
||||
pkgs = import nixpkgs {
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
{ pkgs, username, ... }: {
|
||||
{ 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 ];
|
||||
|
||||
# My module options
|
||||
@@ -8,6 +35,7 @@
|
||||
sddmConfig.enable = false;
|
||||
stylixConfig.enable = true;
|
||||
nixSettings.enable = true;
|
||||
#consoleSettings.enable = true;
|
||||
};
|
||||
hardwareCfg = {
|
||||
networkModule.enable = true;
|
||||
@@ -25,7 +53,7 @@
|
||||
|
||||
environment = {
|
||||
variables = { PATH = "${pkgs.clang-tools}/bin:$PATH"; };
|
||||
shells = with pkgs; [ zsh bash ];
|
||||
shells = [ rsh pkgs.zsh pkgs.bash ];
|
||||
};
|
||||
|
||||
users = {
|
||||
@@ -36,7 +64,7 @@
|
||||
isNormalUser = true;
|
||||
initialPassword = "1234";
|
||||
shell = pkgs.zsh;
|
||||
extraGroups = [ "wheel" "persist" "libvirtd" ];
|
||||
extraGroups = [ "input" "wheel" "persist" "libvirtd" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{ lib, self, config, host, pkgs, ... }:
|
||||
|
||||
let
|
||||
scheme = "tokyo-night-dark";
|
||||
scheme = "ayu-dark";
|
||||
wallpaper = "${self}/assets/wallpapers/dark-waves.jpg";
|
||||
server = (host == "xenon");
|
||||
in {
|
||||
@@ -16,10 +16,11 @@ in {
|
||||
image = wallpaper;
|
||||
polarity = "dark";
|
||||
autoEnable = true;
|
||||
opacity.terminal = 0.5;
|
||||
opacity.terminal = 1.0;
|
||||
targets = {
|
||||
waybar.enable = false;
|
||||
btop.enable = false;
|
||||
nixvim.enable = false;
|
||||
nixvim.transparentBackground = {
|
||||
main = false;
|
||||
signColumn = false;
|
||||
|
||||
@@ -49,6 +49,16 @@ in {
|
||||
wf-recorder
|
||||
toilet
|
||||
vkbasalt
|
||||
firefox
|
||||
dust
|
||||
porsmo
|
||||
rustup
|
||||
w3m
|
||||
neovide
|
||||
python3
|
||||
ghostty
|
||||
fd
|
||||
delta
|
||||
] ++ scripts;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
config = lib.mkIf config.movOpts.envConfig.zshConfig.shellAliases.enable {
|
||||
programs.zsh = {
|
||||
shellAliases = {
|
||||
grep = "grep --color=auto";
|
||||
grep = "rg";
|
||||
find = "fd";
|
||||
cat = "bat";
|
||||
yazi = "y";
|
||||
mv = "mv -v";
|
||||
cp = "cp -vr";
|
||||
@@ -28,6 +30,7 @@
|
||||
gpush = "gitpush_sfx";
|
||||
gpull = "gitpull_sfx";
|
||||
greb = "gitrebase_sfx";
|
||||
rsh = "$HOME/Coding/projects/rust/rsh/target/debug/rsh";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,7 +37,12 @@ in
|
||||
${shellsound} ${sndpath}/nvim.wav
|
||||
command nvim "$@"
|
||||
}
|
||||
neovide() {
|
||||
${shellsound} ${sndpath}/nvim.wav
|
||||
command neovide "$@"
|
||||
}
|
||||
alias vi="nvim"
|
||||
alias vide="neovide"
|
||||
kitty_theme() {
|
||||
if [ $TERM = "xterm-kitty" ]; then
|
||||
if [ -n "$SSH_CONNECTION" ]; then
|
||||
@@ -135,6 +140,10 @@ in
|
||||
touch $HOME/.zsh_history
|
||||
chmod 600 $HOME/.zsh_history
|
||||
fi
|
||||
if [ "$TERM" = "linux" ]; then
|
||||
echo -en "\e]P0101010"
|
||||
setfont ter-v32b
|
||||
fi
|
||||
setopt APPEND_HISTORY # Append history to the history file (don't overwrite)
|
||||
setopt INC_APPEND_HISTORY # Append to the history file incrementally
|
||||
setopt SHARE_HISTORY # Share history between all zsh sessions
|
||||
|
||||
@@ -13,20 +13,12 @@
|
||||
};
|
||||
userEmail = "kylerclay@proton.me";
|
||||
userName = "${username}";
|
||||
diff-so-fancy = {
|
||||
enable = true;
|
||||
markEmptyLines = false;
|
||||
stripLeadingSymbols = false;
|
||||
};
|
||||
extraConfig = {
|
||||
color.diff = {
|
||||
# meta = "black yellow bold";
|
||||
# frag = "white blue bold";
|
||||
old = "#A9B1D6 #301A1F";
|
||||
new = "#A9B1D6 #12261E";
|
||||
# plain = "normal";
|
||||
# whitespace = "reverse red";
|
||||
};
|
||||
core.pager = "delta";
|
||||
interactive.diffFilter = "delta --color-only";
|
||||
delta.navigate = "true";
|
||||
delta.dark = "true";
|
||||
merge.conflictstyle = "zdiff3";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
config = lib.mkIf config.movOpts.programConfigs.kittyConfig.enable {
|
||||
programs.kitty = {
|
||||
enable = true;
|
||||
font = {
|
||||
package = lib.mkForce pkgs.fira-code;
|
||||
name = lib.mkForce "Fira Code";
|
||||
size = lib.mkForce 20;
|
||||
};
|
||||
|
||||
settings = {
|
||||
confirm_os_window_close = 0;
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
desc =
|
||||
"Load previous session window settings for the opened file (folds, cursor pos, etc)";
|
||||
}
|
||||
{
|
||||
command = "silent! FloatermNew --name=def_term --width=0.8 --height=0.8 --wintype=topright --silent";
|
||||
event = [ "VimEnter" ];
|
||||
pattern = [ "*" ];
|
||||
desc = "Start the floaterm window";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,24 +6,14 @@
|
||||
key = "!fs";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>UndotreeToggle<CR>"; # select entire document
|
||||
key = "!t";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "gg<S-V>G"; # select entire document
|
||||
key = "<C-a>";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>ChatGPTEditWithInstructions<CR>";
|
||||
key = "!egpt";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>ChatGPTCompleteCode<CR>";
|
||||
key = "!cgpt";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>ChatGPT<CR>";
|
||||
key = "!gpt";
|
||||
key = "!a";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
@@ -31,11 +21,6 @@
|
||||
key = "!ca";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>Telescope<CR>";
|
||||
key = "!t";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>lua vim.lsp.buf.format()<CR>";
|
||||
key = "!fmt";
|
||||
@@ -57,20 +42,15 @@
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>FloatermToggle shadeterm<CR>";
|
||||
action = "<cmd>FloatermToggle def_term<CR>";
|
||||
key = "<F2>";
|
||||
mode = "n";
|
||||
mode = [ "n" "t" ];
|
||||
}
|
||||
{
|
||||
action = "<cmd>NvimTreeToggle<CR>";
|
||||
key = "<F3>";
|
||||
mode = "n";
|
||||
}
|
||||
{
|
||||
action = "<cmd>FloatermToggle shadeterm<CR>";
|
||||
key = "<F2>";
|
||||
mode = "t";
|
||||
}
|
||||
{
|
||||
action = "<cmd>COQnow<CR>";
|
||||
key = "!cq";
|
||||
|
||||
@@ -5,6 +5,7 @@ in {
|
||||
programs.nixvim = {
|
||||
colorschemes.base16 = {
|
||||
enable = true;
|
||||
colorscheme = "ayu-dark";
|
||||
#colorscheme = {
|
||||
# base00 = "#${scheme.base00}";
|
||||
# base01 = "#${scheme.base01}";
|
||||
@@ -51,8 +52,10 @@ in {
|
||||
vim.opt.linebreak = true
|
||||
vim.opt.textwidth = 0
|
||||
vim.opt.breakat = " \t!@*-+;:,./?"
|
||||
vim.opt.guifont = "Fira Code:h18"
|
||||
|
||||
vim.g.mapleader = "!"
|
||||
vim.g.rust_recommended_style = 0
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
16
modules/home/programs/nixvim/plugins/airline.nix
Normal file
16
modules/home/programs/nixvim/plugins/airline.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
programs.nixvim = {
|
||||
plugins.airline = {
|
||||
enable = true;
|
||||
settings = {
|
||||
left_sep = "";
|
||||
right_sep = "";
|
||||
powerline_fonts = 1;
|
||||
theme = "dark";
|
||||
section_x = "";
|
||||
section_y = "";
|
||||
skip_empty_sections = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -12,12 +12,15 @@
|
||||
./lsp.nix
|
||||
./rustaceanvim.nix
|
||||
./fidget.nix
|
||||
./lualine.nix
|
||||
# ./lualine.nix
|
||||
./airline.nix
|
||||
./nvim-lightbulb.nix
|
||||
./neocord.nix
|
||||
./plugins.nix
|
||||
./nvim-tree.nix
|
||||
./telescope.nix
|
||||
./indent-blankline.nix
|
||||
./gitsigns.nix
|
||||
./extra_plugins.nix
|
||||
];
|
||||
}
|
||||
|
||||
14
modules/home/programs/nixvim/plugins/gitsigns.nix
Normal file
14
modules/home/programs/nixvim/plugins/gitsigns.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
programs.nixvim = {
|
||||
plugins.gitsigns = {
|
||||
enable = true;
|
||||
settings.signs = {
|
||||
add.text = "│";
|
||||
change.text = "/";
|
||||
delete.text = "-";
|
||||
topdelete.text = "-";
|
||||
changedelete.text = "\\";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{ pkgs, ... }: {
|
||||
programs.nixvim = {
|
||||
extraPlugins = [
|
||||
(pkgs.vimUtils.buildVimPlugin {
|
||||
name = "haskell-tools.nvim";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "mrcjkb";
|
||||
repo = "haskell-tools.nvim";
|
||||
rev = "39c4ced6f1bff1abc8d4df5027efd11ac38c6e6c";
|
||||
hash = "sha256-f+M35EwAlHwjJ2Xs2u9FLnyH0FJT22D0LLShDXCbEEs=";
|
||||
};
|
||||
})
|
||||
];
|
||||
plugins = { haskell-scope-highlighting.enable = true; };
|
||||
#extraPlugins = [
|
||||
#(pkgs.vimUtils.buildVimPlugin {
|
||||
#name = "haskell-tools.nvim";
|
||||
#src = pkgs.fetchFromGitHub {
|
||||
#owner = "mrcjkb";
|
||||
#repo = "haskell-tools.nvim";
|
||||
#rev = "39c4ced6f1bff1abc8d4df5027efd11ac38c6e6c";
|
||||
#hash = "sha256-f+M35EwAlHwjJ2Xs2u9FLnyH0FJT22D0LLShDXCbEEs=";
|
||||
#};
|
||||
#})
|
||||
#];
|
||||
#plugins = { haskell-scope-highlighting.enable = true; };
|
||||
};
|
||||
}
|
||||
|
||||
10
modules/home/programs/nixvim/plugins/indent-blankline.nix
Normal file
10
modules/home/programs/nixvim/plugins/indent-blankline.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
programs.nixvim = {
|
||||
plugins.indent-blankline = {
|
||||
enable = true;
|
||||
#settings = {
|
||||
#indent.char = "│";
|
||||
#};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
programs.nixvim = {
|
||||
plugins.lualine = {
|
||||
enable = true;
|
||||
enable = false;
|
||||
settings = {
|
||||
options = {
|
||||
icons_enabled = true;
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
};
|
||||
nix.enable = true;
|
||||
endwise.enable = true;
|
||||
undotree.enable = true;
|
||||
firenvim.enable = true;
|
||||
helpview.enable = true;
|
||||
floaterm.enable = true;
|
||||
fugitive.enable = true;
|
||||
gitsigns.enable = true;
|
||||
indent-blankline.enable = true;
|
||||
lastplace.enable = true;
|
||||
markdown-preview.enable = true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
settings.diagnostics_warn_symbol = "■";
|
||||
settings.diagnostics_hint_symbol = "■";
|
||||
settings.diagnostics_info_symbol = "■";
|
||||
settings.character = "│";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
programs.nixvim = {
|
||||
plugins.vim-matchup = {
|
||||
enable = true;
|
||||
enableSurround = true;
|
||||
textObj.enable = true;
|
||||
motion = {
|
||||
enable = true;
|
||||
cursorEnd = true;
|
||||
};
|
||||
matchParen = {
|
||||
hiSurroundAlways = true;
|
||||
offscreen = { method = "popup"; };
|
||||
};
|
||||
treesitterIntegration = {
|
||||
enable = true;
|
||||
includeMatchWords = true;
|
||||
settings = {
|
||||
surround_enabled = 1;
|
||||
text_obj_enabled = 1;
|
||||
motion_enabled = 1;
|
||||
motion_cursor_end = 1;
|
||||
matchparen_deferred_hi_surround_always = true;
|
||||
matchparen_offscreen = { method = "popup"; };
|
||||
treesitter = {
|
||||
enable = true;
|
||||
include_match_words = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
loader.systemd-boot.enable = true;
|
||||
loader.efi.canTouchEfiVariables = true;
|
||||
loader.systemd-boot.configurationLimit = 10;
|
||||
loader.grub.gfxmodeEfi = "1024x768";
|
||||
loader.grub.extraConfig = ''
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="nomodeset"
|
||||
GRUB_GFXPAYLOAD_LINUX=1024x768
|
||||
'';
|
||||
kernelPackages = pkgs.linuxPackages_latest;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
cliphist
|
||||
fail2ban
|
||||
git
|
||||
gcc
|
||||
lldb
|
||||
hyprland-workspaces
|
||||
hyprpaper
|
||||
hyprpicker
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
lib.mkEnableOption "enables default system programs";
|
||||
};
|
||||
config = lib.mkIf config.movOpts.softwareCfg.sysProgs.enable {
|
||||
environment.etc."shells" = {
|
||||
enable = true;
|
||||
text = ''
|
||||
/run/current-system/sw/bin/zsh
|
||||
/run/current-system/sw/bin/bash
|
||||
/run/current-system/sw/bin/zsh
|
||||
/nix/store/m7l6yzmflrf9hjs8707lk9nkhi6f73n1-zsh-5.9/bin/zsh
|
||||
/run/current-system/sw/bin/bash
|
||||
/run/current-system/sw/bin/sh
|
||||
/nix/store/f33kh08pa7pmy4kvsmsibda46sh46s66-bash-interactive-5.2p37/bin/bash
|
||||
/nix/store/f33kh08pa7pmy4kvsmsibda46sh46s66-bash-interactive-5.2p37/bin/sh
|
||||
/bin/sh
|
||||
/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
'';
|
||||
};
|
||||
programs = {
|
||||
hyprland.enable = lib.mkDefault true;
|
||||
zsh.enable = lib.mkDefault true;
|
||||
|
||||
@@ -19,15 +19,16 @@
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = true;
|
||||
};
|
||||
openssh = {
|
||||
enable = true;
|
||||
allowSFTP = true;
|
||||
};
|
||||
ratbagd.enable = true;
|
||||
pcscd.enable = true;
|
||||
udev.enable = true;
|
||||
dbus.enable = true;
|
||||
mullvad-vpn.enable = true;
|
||||
blueman.enable = true;
|
||||
openssh = {
|
||||
enable = true;
|
||||
allowSFTP = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
14
modules/sys/sysenv/console.nix
Normal file
14
modules/sys/sysenv/console.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{ lib, config, pkgs, ... }: {
|
||||
options = {
|
||||
movOpts.sysEnv.consoleSettings.enable =
|
||||
lib.mkEnableOption "enables my console settings";
|
||||
};
|
||||
config = lib.mkIf config.movOpts.sysEnv.consoleSettings.enable {
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
console = {
|
||||
earlySetup = true;
|
||||
font = "Lat2-Terminus32";
|
||||
packages = with pkgs; [ terminus_font ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{ inputs, nixpkgs, nixvim, config, self, username, host, ... }: {
|
||||
imports = [ (import ./sddm.nix) ] ++ [ (import ./issue.nix) ]
|
||||
++ [ (import ./nix.nix) ] ++ [ (import ./stylix.nix) ];
|
||||
++ [ (import ./nix.nix) ] ++ [ (import ./stylix.nix) ];
|
||||
#++ [ (import ./console.nix) ];
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
lib.mkEnableOption "enables my nixos settings";
|
||||
};
|
||||
config = lib.mkIf config.movOpts.sysEnv.nixSettings.enable {
|
||||
system.stateVersion = "25.05";
|
||||
system.stateVersion = "24.05";
|
||||
nix = {
|
||||
settings = {
|
||||
auto-optimise-store = true;
|
||||
experimental-features = [ "nix-command" "flakes" ];
|
||||
substituters = [ "https://nix-gaming.cachix.org" ];
|
||||
};
|
||||
gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 7d";
|
||||
};
|
||||
};
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
|
||||
@@ -19,9 +19,9 @@ in {
|
||||
};
|
||||
polarity = "dark";
|
||||
autoEnable = true;
|
||||
opacity.terminal = 0.5;
|
||||
opacity.terminal = 0.8;
|
||||
targets = {
|
||||
console.enable = true;
|
||||
console.enable = false;
|
||||
feh.enable = true;
|
||||
grub.enable = true;
|
||||
gtk.enable = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ host, root, ... }: self: super:
|
||||
{ host ? "oganesson", root, ... }: self: super:
|
||||
|
||||
let
|
||||
extraFigletFonts = super.fetchFromGitHub {
|
||||
@@ -22,6 +22,7 @@ in
|
||||
# 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 {};
|
||||
};
|
||||
myScripts = {
|
||||
# Scripts written using pkgs.writeShellApplication
|
||||
|
||||
19
overlay/pkgs/rsh/package.nix
Normal file
19
overlay/pkgs/rsh/package.nix
Normal file
@@ -0,0 +1,19 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
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 ];
|
||||
};
|
||||
}
|
||||
17
overlay/pkgs/rsh/rsh/.envrc
Normal file
17
overlay/pkgs/rsh/rsh/.envrc
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/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
|
||||
26
overlay/pkgs/rsh/rsh/.gitignore
vendored
Normal file
26
overlay/pkgs/rsh/rsh/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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.*
|
||||
1690
overlay/pkgs/rsh/rsh/Cargo.lock
generated
Normal file
1690
overlay/pkgs/rsh/rsh/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
overlay/pkgs/rsh/rsh/Cargo.toml
Normal file
31
overlay/pkgs/rsh/rsh/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[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"
|
||||
1
overlay/pkgs/rsh/rsh/README.md
Normal file
1
overlay/pkgs/rsh/rsh/README.md
Normal file
@@ -0,0 +1 @@
|
||||
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.
|
||||
5
overlay/pkgs/rsh/rsh/TODO.md
Normal file
5
overlay/pkgs/rsh/rsh/TODO.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 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
|
||||
9
overlay/pkgs/rsh/rsh/file.sh
Executable file
9
overlay/pkgs/rsh/rsh/file.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
(#!python
|
||||
|
||||
import sys
|
||||
|
||||
word1 = sys.argv[1]
|
||||
word2 = sys.argv[2]
|
||||
|
||||
print(word1,word2)
|
||||
)
|
||||
933
overlay/pkgs/rsh/rsh/flake.lock
generated
Normal file
933
overlay/pkgs/rsh/rsh/flake.lock
generated
Normal file
@@ -0,0 +1,933 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
47
overlay/pkgs/rsh/rsh/flake.nix
Normal file
47
overlay/pkgs/rsh/rsh/flake.nix
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
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
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
159
overlay/pkgs/rsh/rsh/ideas.md
Normal file
159
overlay/pkgs/rsh/rsh/ideas.md
Normal file
@@ -0,0 +1,159 @@
|
||||
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 <word1> <word2> !` -> simple find and replace in a string or file
|
||||
- `range <int1> <int2> !` -> needs work, but this idea is useful
|
||||
- `filter <pattern> !` -> only print lines from cmd output with <pattern>
|
||||
- `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 <args> @ <input_source>?` 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<u8> 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)
|
||||
25
overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix
Normal file
25
overlay/pkgs/rsh/rsh/nix/devshell/flake-module.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
{ 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
18
overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix
Normal file
18
overlay/pkgs/rsh/rsh/nix/rust-overlay/flake-module.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{ 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 = { }; };
|
||||
};
|
||||
}
|
||||
202
overlay/pkgs/rsh/rsh/roadmap.md
Normal file
202
overlay/pkgs/rsh/rsh/roadmap.md
Normal file
@@ -0,0 +1,202 @@
|
||||
## 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<bla; a++)) do done { } may be used instead of do done
|
||||
- [ ] while [condition] do done (optional brackets)
|
||||
- [ ] while (( condition )) do done
|
||||
- [ ] until [condition] do done
|
||||
- [ ] do done
|
||||
- [ ] break
|
||||
- [ ] continue
|
||||
- [ ] function function_name() { } and function() { }
|
||||
- [ ] select variable in list (optional in list) do command break done
|
||||
- [ ] command execution
|
||||
- [x] pipes
|
||||
- [x] > < >> << 2>&1 etc redirections. don’t forget <<EOF kind.
|
||||
- [ ] <(command list) >(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 ?
|
||||
46
overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock
generated
Normal file
46
overlay/pkgs/rsh/rsh/rsh_derive/Cargo.lock
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
# 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"
|
||||
11
overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml
Normal file
11
overlay/pkgs/rsh/rsh/rsh_derive/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rsh_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.37"
|
||||
syn = "2.0.91"
|
||||
53
overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs
Normal file
53
overlay/pkgs/rsh/rsh/rsh_derive/src/lib.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
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<Self> {
|
||||
Box::new(self)
|
||||
}
|
||||
|
||||
fn get_redirs(&self) -> &Vec<Box<dyn Node>> {
|
||||
&self.redirs
|
||||
}
|
||||
|
||||
fn push_redir(&mut self, redir: Box<dyn Node>) {
|
||||
self.redirs.push(redir)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn tokens(&self) -> &VecDeque<Tk> {
|
||||
&self.tokens
|
||||
}
|
||||
|
||||
fn flags(&self) -> NdFlags {
|
||||
self.flags
|
||||
}
|
||||
|
||||
fn mod_flags(&mut self, transform: Box<dyn FnOnce(&mut NdFlags)>) {
|
||||
transform(&mut self.flags)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
2
overlay/pkgs/rsh/rsh/rust-toolchain.toml
Normal file
2
overlay/pkgs/rsh/rsh/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
716
overlay/pkgs/rsh/rsh/src/builtin.rs
Normal file
716
overlay/pkgs/rsh/rsh/src/builtin.rs
Normal file
@@ -0,0 +1,716 @@
|
||||
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<Node>) -> Result<Vec<RustFd>, ShellError> {
|
||||
let mut fd_stack: Vec<RustFd> = 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<CString>,newline: bool) -> CString {
|
||||
let mut cat: Vec<u8> = 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<T, ShellError>`.
|
||||
/// * `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<i32, ShellError> { Ok(s.parse::<i32>().unwrap()) };
|
||||
/// let property = |x: &i32| -> bool { *x > 0 };
|
||||
/// let result = do_test(&mut args, transform, property);
|
||||
/// assert!(result.unwrap());
|
||||
/// ```
|
||||
fn do_test<T, F1, F2>(
|
||||
args: &mut VecDeque<Tk>,
|
||||
transform: F1,
|
||||
property: F2,
|
||||
span: Span
|
||||
) -> Result<bool, ShellError>
|
||||
where
|
||||
F1: FnOnce(Tk) -> Result<T, ShellError>,
|
||||
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<T, ShellError>`.
|
||||
/// * `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<i32, ShellError> { Ok(s.parse::<i32>().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<T, F1, F2>(
|
||||
arg1: Tk,
|
||||
args: &mut VecDeque<Tk>,
|
||||
transform: F1,
|
||||
comparison: F2
|
||||
) -> Result<bool, ShellError>
|
||||
where
|
||||
F1: Fn(Tk) -> Result<T, ShellError>,
|
||||
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<bool, ShellError>` - 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<Tk>,
|
||||
command: Tk,
|
||||
result1: bool,
|
||||
operator: Tk
|
||||
) -> Result<bool, ShellError> {
|
||||
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<Tk>) -> Result<RshWaitStatus, ShellError> {
|
||||
info!("Starting builtin test");
|
||||
let span = Span::from(argv.front().unwrap().span().start,argv.back().unwrap().span().end);
|
||||
let is_false = || -> Result<RshWaitStatus, ShellError> { Ok(RshWaitStatus::Fail { code: 1, cmd: Some("test".into()), span }) };
|
||||
let is_true = || -> Result<RshWaitStatus, ShellError> { Ok(RshWaitStatus::Success { span }) };
|
||||
let is_int = |tk: &Tk| -> bool { tk.text().parse::<i32>().is_ok() };
|
||||
let to_int = |tk: Tk| -> Result<i32, ShellError> {
|
||||
tk.text().parse::<i32>().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, ShellError> {
|
||||
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<String, ShellError> { 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<RshWaitStatus, ShellError> {
|
||||
let mut argv: VecDeque<Tk> = 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<RshWaitStatus,ShellError> {
|
||||
let mut argv = node
|
||||
.get_argv()?
|
||||
.iter()
|
||||
.map(|arg| CString::new(arg.text()).unwrap())
|
||||
.collect::<VecDeque<CString>>();
|
||||
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<RshWaitStatus,ShellError> {
|
||||
let mut argv = node
|
||||
.get_argv()?
|
||||
.iter()
|
||||
.map(|arg| CString::new(arg.text()).unwrap())
|
||||
.collect::<VecDeque<CString>>();
|
||||
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<RshWaitStatus,ShellError> {
|
||||
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<RshWaitStatus, ShellError> {
|
||||
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<RshWaitStatus, ShellError> {
|
||||
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<RshWaitStatus,ShellError> {
|
||||
let mut argv = node.get_argv()?.into_iter().collect::<VecDeque<Tk>>();
|
||||
argv.pop_front();
|
||||
let mut flags = JobFlags::empty();
|
||||
while let Some(arg) = argv.pop_front() {
|
||||
let mut chars = arg.text().chars().collect::<VecDeque<char>>();
|
||||
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<RshWaitStatus, ShellError> {
|
||||
let mut flags = EchoFlags::empty();
|
||||
let span = node.span();
|
||||
let mut argv = node.get_argv()?.into_iter().collect::<VecDeque<Tk>>();
|
||||
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::<VecDeque<CString>>();
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
169
overlay/pkgs/rsh/rsh/src/comp.rs
Normal file
169
overlay/pkgs/rsh/rsh/src/comp.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
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<String>, // 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<Self::Candidate>), 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<String>) -> Option<String> {
|
||||
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
|
||||
}
|
||||
522
overlay/pkgs/rsh/rsh/src/event.rs
Normal file
522
overlay/pkgs/rsh/rsh/src/event.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
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<Span>) {
|
||||
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<ShellEvent>,
|
||||
receiver: mpsc::Receiver<ShellEvent>,
|
||||
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<ShellEvent>` for sending events.
|
||||
pub fn inbox(&self) -> mpsc::Sender<ShellEvent> {
|
||||
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<i32, ShellError> {
|
||||
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<i32, ShellError> {
|
||||
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<ShellEvent>,
|
||||
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<ShellEvent>) -> 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<i32, ShellError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1115
overlay/pkgs/rsh/rsh/src/execute.rs
Normal file
1115
overlay/pkgs/rsh/rsh/src/execute.rs
Normal file
File diff suppressed because it is too large
Load Diff
7
overlay/pkgs/rsh/rsh/src/file.output
Normal file
7
overlay/pkgs/rsh/rsh/src/file.output
Normal file
@@ -0,0 +1,7 @@
|
||||
[?2004h
|
||||
[K[1;32mprojects/rust/rsh/src[1;36m/[0m
|
||||
[32m$[36m>[0m
|
||||
[3C
|
||||
[K[A
|
||||
[K[1;32mprojects/rust/rsh/src[1;36m/[0m
|
||||
[32m$[36m>[0m export PS1=">> "
|
||||
2
overlay/pkgs/rsh/rsh/src/file.rsh
Executable file
2
overlay/pkgs/rsh/rsh/src/file.rsh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
echo hellothere$@
|
||||
3
overlay/pkgs/rsh/rsh/src/file.txt
Normal file
3
overlay/pkgs/rsh/rsh/src/file.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
PROCESSED: DATA 0
|
||||
PROCESSED: DATA 1
|
||||
PROCESSED: DATA 2
|
||||
125
overlay/pkgs/rsh/rsh/src/interp/debug.rs
Normal file
125
overlay/pkgs/rsh/rsh/src/interp/debug.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
639
overlay/pkgs/rsh/rsh/src/interp/expand.rs
Normal file
639
overlay/pkgs/rsh/rsh/src/interp/expand.rs
Normal file
@@ -0,0 +1,639 @@
|
||||
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<Vec<Tk>,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<char> {
|
||||
//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::<VecDeque<char>>();
|
||||
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::<VecDeque<char>>();
|
||||
|
||||
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<Node, ShellError> {
|
||||
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<Tk> {
|
||||
trace!("expand(): Starting expansion with token: {:?}", token);
|
||||
let mut working_buffer: VecDeque<Tk> = VecDeque::new();
|
||||
let mut product_buffer: VecDeque<Tk> = 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<Tk>) {
|
||||
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<String>) -> VecDeque<String> {
|
||||
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<String> {
|
||||
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::<i32>().unwrap()..=right.parse::<i32>().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::<VecDeque<char>>();
|
||||
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::<VecDeque<char>>();
|
||||
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::<String>();
|
||||
|
||||
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<Tk> {
|
||||
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
|
||||
}
|
||||
252
overlay/pkgs/rsh/rsh/src/interp/helper.rs
Normal file
252
overlay/pkgs/rsh/rsh/src/interp/helper.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
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<T> {
|
||||
fn extended(self, vec: Vec<T>) -> Vec<T>;
|
||||
}
|
||||
|
||||
impl<T> VecExtension<T> for Vec<T> {
|
||||
fn extended(self, vec: Vec<T>) -> Vec<T> {
|
||||
let mut new_vec = self;
|
||||
new_vec.extend(vec);
|
||||
new_vec
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VecDequeExtension<T> {
|
||||
fn map_rotate<F>(&mut self, transform: F) where F: FnMut(T);
|
||||
}
|
||||
|
||||
impl<T> VecDequeExtension<T> for VecDeque<T> {
|
||||
/// 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<T> {
|
||||
/// fn map_rotate<F>(&mut self, transform: F)
|
||||
/// where
|
||||
/// F: FnMut(&mut T);
|
||||
/// }
|
||||
///
|
||||
/// impl<T> VecDequeExtension<T> for VecDeque<T> {
|
||||
/// fn map_rotate<F>(&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<String> = 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<F>(&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::<VecDeque<char>>();
|
||||
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<F: FnOnce() -> 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<String> {
|
||||
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<Node>) -> VecDeque<Node> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
5
overlay/pkgs/rsh/rsh/src/interp/mod.rs
Normal file
5
overlay/pkgs/rsh/rsh/src/interp/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod parse;
|
||||
pub mod helper;
|
||||
pub mod token;
|
||||
pub mod expand;
|
||||
pub mod debug;
|
||||
1567
overlay/pkgs/rsh/rsh/src/interp/parse.rs
Normal file
1567
overlay/pkgs/rsh/rsh/src/interp/parse.rs
Normal file
File diff suppressed because it is too large
Load Diff
985
overlay/pkgs/rsh/rsh/src/interp/token.rs
Normal file
985
overlay/pkgs/rsh/rsh/src/interp/token.rs
Normal file
@@ -0,0 +1,985 @@
|
||||
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<HashMap<&'static str, Regex>> = Lazy::new(|| {
|
||||
let mut regex = HashMap::new();
|
||||
regex.insert("redirection",Regex::new(r"^(?P<fd_out>[0-9]+)?(?P<operator>>{1,2}|<{1,3})(?:[&]?(?P<fd_target>[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<Tk> },
|
||||
Vars { vars: Vec<Tk> },
|
||||
|
||||
// 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<Self,ShellError> {
|
||||
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<TkType,ShellError> {
|
||||
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<TkType,ShellError> {
|
||||
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<i32>,
|
||||
pub file_target: Option<Box<Tk>>
|
||||
}
|
||||
|
||||
#[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<Self> {
|
||||
use crate::interp::token::TkState::*;
|
||||
let mut execs = vec![
|
||||
Command,
|
||||
Subshell
|
||||
];
|
||||
execs.extend(Self::openers());
|
||||
execs
|
||||
}
|
||||
pub fn body() -> Vec<Self> {
|
||||
use crate::interp::token::TkState::*;
|
||||
vec![
|
||||
Command,
|
||||
Arg,
|
||||
Separator,
|
||||
Redirect,
|
||||
SQuote,
|
||||
DQuote,
|
||||
CommandSub,
|
||||
Subshell
|
||||
]
|
||||
}
|
||||
pub fn openers() -> Vec<Self> {
|
||||
use crate::interp::token::TkState::*;
|
||||
vec![
|
||||
For,
|
||||
Loop,
|
||||
If,
|
||||
Case,
|
||||
Select
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RshTokenizer {
|
||||
input: String,
|
||||
char_stream: VecDeque<char>,
|
||||
context: TkState,
|
||||
pub tokens: Vec<Tk>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl RshTokenizer {
|
||||
pub fn new(input: &str) -> Self {
|
||||
let input = input.trim().to_string();
|
||||
let char_stream = input.chars().collect::<VecDeque<char>>();
|
||||
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<char> {
|
||||
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::<i32>().ok()).unwrap_or(1);
|
||||
operator = caps.name("operator").unwrap().as_str();
|
||||
fd_target = caps.name("fd_target").and_then(|fd| fd.as_str().parse::<i32>().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(..));
|
||||
}
|
||||
}
|
||||
168
overlay/pkgs/rsh/rsh/src/main.rs
Normal file
168
overlay/pkgs/rsh/rsh/src/main.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
//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::<Vec<String>>();
|
||||
|
||||
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<String>, mut shellenv: ShellEnv) {
|
||||
let stderr = unsafe { BorrowedFd::borrow_raw(STDERR_FILENO) };
|
||||
let mut pos_params: Vec<String> = 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);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
103
overlay/pkgs/rsh/rsh/src/prompt.rs
Normal file
103
overlay/pkgs/rsh/rsh/src/prompt.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
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<RshHelper, DefaultHistory> {
|
||||
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<ShellEvent>, 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;
|
||||
}
|
||||
640
overlay/pkgs/rsh/rsh/src/shellenv.rs
Normal file
640
overlay/pkgs/rsh/rsh/src/shellenv.rs
Normal file
@@ -0,0 +1,640 @@
|
||||
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<Pid>,
|
||||
commands: Vec<String>,
|
||||
pgid: Pid,
|
||||
status: RshWaitStatus,
|
||||
current: bool,
|
||||
prev: bool,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn new(job_id: i32, pids: Vec<Pid>, commands: Vec<String>, 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<String> {
|
||||
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<i32,Job>,
|
||||
curr_job: Option<i32>,
|
||||
prev_job: Option<i32>,
|
||||
updated_since_check: HashMap<i32,Job>
|
||||
}
|
||||
|
||||
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::<Vec<&Job>>();
|
||||
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<Mutex<String>>,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
pub variables: HashMap<String, String>,
|
||||
pub aliases: HashMap<String, String>,
|
||||
pub shopts: HashMap<String,usize>,
|
||||
pub functions: HashMap<String, Box<Node>>,
|
||||
pub parameters: HashMap<String, String>,
|
||||
pub open_fds: HashSet<i32>,
|
||||
pub last_input: Option<String>,
|
||||
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<Pid>, commands: Vec<String>, 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<i32,Job> {
|
||||
&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<String,String> {
|
||||
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| 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::<HashMap<String,String>>();
|
||||
}
|
||||
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<F>(&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<String> {
|
||||
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<CString> {
|
||||
self.env_vars
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let env_pair = format!("{}={}", key, value);
|
||||
CString::new(env_pair).unwrap() })
|
||||
.collect::<Vec<CString>>()
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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<String> {
|
||||
self.aliases.remove(key)
|
||||
}
|
||||
|
||||
// Getters and Setters for `functions`
|
||||
pub fn get_function(&self, name: &str) -> Option<Box<Node>> {
|
||||
self.functions.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn set_function(&mut self, name: String, body: Box<Node>) -> 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<Box<Node>> {
|
||||
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<String,usize> {
|
||||
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
|
||||
}
|
||||
9
overlay/pkgs/rsh/rsh/tests/basic_case.sh
Executable file
9
overlay/pkgs/rsh/rsh/tests/basic_case.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/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
|
||||
4
overlay/pkgs/rsh/rsh/tests/basic_commands.sh
Executable file
4
overlay/pkgs/rsh/rsh/tests/basic_commands.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
ls
|
||||
echo "Hello, World!"
|
||||
pwd
|
||||
4
overlay/pkgs/rsh/rsh/tests/basic_for.sh
Executable file
4
overlay/pkgs/rsh/rsh/tests/basic_for.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
for i in 1 2 3; do
|
||||
echo $i;
|
||||
done
|
||||
12
overlay/pkgs/rsh/rsh/tests/basic_if.sh
Executable file
12
overlay/pkgs/rsh/rsh/tests/basic_if.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/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
|
||||
6
overlay/pkgs/rsh/rsh/tests/basic_until.sh
Executable file
6
overlay/pkgs/rsh/rsh/tests/basic_until.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
var=1
|
||||
until [ $var -eq 0 ]; do
|
||||
var=0;
|
||||
echo looped
|
||||
done
|
||||
3
overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh
Executable file
3
overlay/pkgs/rsh/rsh/tests/basic_var_sub.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
var="value"
|
||||
echo $var
|
||||
6
overlay/pkgs/rsh/rsh/tests/basic_while.sh
Executable file
6
overlay/pkgs/rsh/rsh/tests/basic_while.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
var=1
|
||||
while [ $var -eq 1 ]; do
|
||||
var=0;
|
||||
echo looped;
|
||||
done
|
||||
3
overlay/pkgs/rsh/rsh/tests/chain.sh
Executable file
3
overlay/pkgs/rsh/rsh/tests/chain.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
true && echo it works
|
||||
false && echo it doesnt work
|
||||
3
overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh
Executable file
3
overlay/pkgs/rsh/rsh/tests/empty_var_sub.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
var=""
|
||||
echo $var
|
||||
2
overlay/pkgs/rsh/rsh/tests/pipeline.sh
Executable file
2
overlay/pkgs/rsh/rsh/tests/pipeline.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/home/pagedmov/Coding/projects/rust/rsh/target/debug/rsh
|
||||
echo hello world | grep w
|
||||
2
overlay/pkgs/rsh/rsh/tests/very_nested.sh
Executable file
2
overlay/pkgs/rsh/rsh/tests/very_nested.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/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
|
||||
@@ -45,7 +45,7 @@ pkgs.writeShellApplication {
|
||||
running=true
|
||||
|
||||
declare -A commands=(
|
||||
["Change Wallpaper"]="moveonscreen --center && if chpaper; then running=false; else moveonscreen; fi"
|
||||
["Change Wallpaper"]="hyprctl dispatch resizeactive 20% 50% && moveonscreen --center && if chpaper; then running=false; else moveonscreen; fi"
|
||||
["Change System Color Scheme"]="hyprctl dispatch resizeactive 10% 80% && moveonscreen --center && if chscheme; then running=false; else hyprctl dispatch resizeactive exact 40% 25% && moveonscreen; fi"
|
||||
["Open System Monitor"]="btop_cmd"
|
||||
["Open Volume Controls"]="hyprctl dispatch resizeactive 10% 80% && moveonscreen --center && alsamixer && hyprctl dispatch resizeactive exact 40% 25% && moveonscreen"
|
||||
@@ -64,24 +64,7 @@ pkgs.writeShellApplication {
|
||||
|
||||
# Use fzf to select a command with preview
|
||||
while $running; do
|
||||
selected_command=$(printf "%s\n" "''${ordered_commands[@]}" | fzf --preview="
|
||||
cleaned_key=\$(echo {} | tr -d \"'\"); \
|
||||
echo \"Cleaned key: \$cleaned_key\"; \
|
||||
declare -A descriptions=(
|
||||
[\"Change Wallpaper\"]=\"Choose a wallpaper to switch to from the assets/wallpapers folder in the system flake directory. Requires rebuilding the system and restarting hyprpaper.\"
|
||||
[\"Change System Color Scheme\"]=\"Changes the base16 color scheme used by stylix to color system applications.\"
|
||||
[\"Open System Monitor\"]=\"Opens a btop window.\"
|
||||
[\"Open Volume Controls\"]=\"Opens alsamixer.\"
|
||||
[\"Open Keyring\"]=\"Opens a fuzzy finder with all of the paths held in ~/.password-store. Selecting one uses pass to copy that password to the clipboard. Password is cleared from clipboard after 45 seconds, and isn't saved to clipboard history.\"
|
||||
[\"View Clipboard History\"]=\"Opens clipboard history. Selecting an item copies it to the clipboard.\"
|
||||
); \
|
||||
if [[ -v descriptions[\$cleaned_key] ]]; then \
|
||||
clear; \
|
||||
echo \''${descriptions[\$cleaned_key]} | fmt -w 28; \
|
||||
else \
|
||||
clear; \
|
||||
echo \"No description available\"; \
|
||||
fi" --prompt="> ")
|
||||
selected_command=$(printf "%s\n" "''${ordered_commands[@]}" | fzf --prompt="> ")
|
||||
|
||||
|
||||
#Execute the selected command if selection is not empty
|
||||
|
||||
Reference in New Issue
Block a user