gostui-setup #1
181
Cargo.lock
generated
181
Cargo.lock
generated
@@ -8,6 +8,62 @@ version = "0.2.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -35,6 +91,52 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.49"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_str"
|
name = "compact_str"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -58,6 +160,12 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "countme"
|
||||||
|
version = "3.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
@@ -201,13 +309,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "garandos-tui"
|
name = "garandos_tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"rnix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -253,6 +369,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -313,7 +435,16 @@ version = "0.12.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.15.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -328,6 +459,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -405,6 +542,34 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rnix"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f15e00b0ab43abd70d50b6f8cd021290028f9b7fdd7cdfa6c35997173bc1ba9"
|
||||||
|
dependencies = [
|
||||||
|
"rowan",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rowan"
|
||||||
|
version = "0.15.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b"
|
||||||
|
dependencies = [
|
||||||
|
"countme",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"memoffset",
|
||||||
|
"rustc-hash",
|
||||||
|
"text-size",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.44"
|
||||||
@@ -530,6 +695,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "text-size"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@@ -565,6 +736,12 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.1+wasi-snapshot-preview1"
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "garandos-tui"
|
name = "garandos_tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
|
rnix = "0.12.0"
|
||||||
|
|||||||
19
LICENCE
Normal file
19
LICENCE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2025 GarandPLG
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
14
default.nix
14
default.nix
@@ -5,8 +5,20 @@
|
|||||||
}:
|
}:
|
||||||
rustPlatform.buildRustPackage {
|
rustPlatform.buildRustPackage {
|
||||||
name = "garandos-tui";
|
name = "garandos-tui";
|
||||||
|
pname = "garandos-tui";
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
# buildInputs = [ ];
|
# buildInputs = [ ];
|
||||||
nativeBuildInputs = [pkg-config];
|
nativeBuildInputs = [pkg-config];
|
||||||
cargoHash = lib.fakeHash;
|
cargoHash = "sha256-cFAkKwgLzj6Hr2pq7W/1Ps1G3yKzgEam/qV6p31gadA=";
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "TUI for managing GarandsOS' hosts enabled modules";
|
||||||
|
homepage = "https://gitea.garandplg.com/GarandPLG/garandos-tui";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = [
|
||||||
|
"Garand_PLG"
|
||||||
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
55
flake.nix
55
flake.nix
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
description = "TUI for managing GarandsOS' hosts configuration";
|
description = "TUI for managing GarandsOS' hosts enabled modules";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
@@ -32,10 +32,6 @@
|
|||||||
cargo = rustToolchain;
|
cargo = rustToolchain;
|
||||||
rustc = rustToolchain;
|
rustc = rustToolchain;
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
|
||||||
packageVersion = cargoToml.package.version;
|
|
||||||
# ...
|
|
||||||
in {
|
in {
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
default =
|
default =
|
||||||
@@ -44,11 +40,56 @@
|
|||||||
develop = naerskLib.buildPackage {
|
develop = naerskLib.buildPackage {
|
||||||
name = "garandos-tui";
|
name = "garandos-tui";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
# buildInputs = with pkgs; [];
|
|
||||||
nativeBuildInputs = with pkgs; [pkg-config];
|
nativeBuildInputs = with pkgs; [pkg-config];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nixosModules.garandos-tui = {
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib) mkOption mkIf types;
|
||||||
|
cfg = config.programs.garandos-tui;
|
||||||
|
in {
|
||||||
|
options.programs.garandos-tui = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable the Garandos TUI module";
|
||||||
|
};
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = self.packages.${system}.default;
|
||||||
|
defaultText = "self.packages.${system}.default";
|
||||||
|
description = "The Garandos TUI package";
|
||||||
|
};
|
||||||
|
systemModulesFilePath = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "";
|
||||||
|
example = "/home/\${username}/garandos/hosts/\${hostname}/system-modules.nix";
|
||||||
|
description = "The path to the Host's system modules file";
|
||||||
|
};
|
||||||
|
homeModulesFilePath = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "";
|
||||||
|
example = "/home/\${username}/garandos/hosts/\${hostname}/home-modules.nix";
|
||||||
|
description = "The path to the Host's home modules file";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = mkIf (cfg.enable && cfg.systemModulesFilePath != "" && cfg.homeModulesFilePath != "") {
|
||||||
|
environment.systemPackages = [
|
||||||
|
(pkgs.writeScriptBin "garandos-tui" ''
|
||||||
|
#!${pkgs.runtimeShell}
|
||||||
|
exec ${cfg.package}/bin/garandos_tui \
|
||||||
|
--sf '${cfg.systemModulesFilePath}' \
|
||||||
|
--hf '${cfg.homeModulesFilePath}' \
|
||||||
|
"$@"
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
devShells.${system}.default = pkgs.mkShell {
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
env.RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
env.RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
@@ -57,8 +98,6 @@
|
|||||||
nativeBuildInputs = with pkgs; [pkg-config];
|
nativeBuildInputs = with pkgs; [pkg-config];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
echo "garandos-tui v${packageVersion}"
|
|
||||||
echo ""
|
|
||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " nix build - Build production version"
|
echo " nix build - Build production version"
|
||||||
echo " nix run - Run production version"
|
echo " nix run - Run production version"
|
||||||
|
|||||||
295
src/app.rs
Normal file
295
src/app.rs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
use crate::nix::{
|
||||||
|
ConfigOption, ConfigSource, collect_nix_options, get_nix_value_by_path, toggle_bool_at_path,
|
||||||
|
};
|
||||||
|
use crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind};
|
||||||
|
use ratatui::{
|
||||||
|
DefaultTerminal, Frame,
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::{Constraint, Layout, Rect},
|
||||||
|
style::{Color, Stylize},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Borders, Paragraph, Widget},
|
||||||
|
};
|
||||||
|
use rnix::{Parse, Root, SyntaxNode};
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::Result,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::mpsc::{self, Receiver},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
pub exit: bool,
|
||||||
|
pub system_path: PathBuf,
|
||||||
|
pub home_path: PathBuf,
|
||||||
|
pub system_ast: Parse<Root>,
|
||||||
|
pub home_ast: Parse<Root>,
|
||||||
|
pub system_modules: Vec<ConfigOption>,
|
||||||
|
pub home_modules: Vec<ConfigOption>,
|
||||||
|
pub current_file: ConfigSource,
|
||||||
|
pub selected_index: usize,
|
||||||
|
pub last_action_status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Input(KeyEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn run(&mut self, terminal: &mut DefaultTerminal, rx: Receiver<Event>) -> Result<()> {
|
||||||
|
while !self.exit {
|
||||||
|
let event: Event = match rx.recv() {
|
||||||
|
Ok(ev) => ev,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
match event {
|
||||||
|
Event::Input(key_event) => self.handle_key_event(key_event)?,
|
||||||
|
}
|
||||||
|
terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, frame: &mut Frame<'_>) {
|
||||||
|
frame.render_widget(self, frame.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
|
||||||
|
// q - wyjście z programu
|
||||||
|
if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
|
||||||
|
self.exit = true;
|
||||||
|
}
|
||||||
|
// 1/← - zmień plik na ConfigSource::System (pomiń wykonywaniej, jeżeli już jest wybrany)
|
||||||
|
else if key_event.kind == KeyEventKind::Press
|
||||||
|
&& (key_event.code == KeyCode::Char('1') || key_event.code == KeyCode::Left)
|
||||||
|
{
|
||||||
|
self.current_file = ConfigSource::System;
|
||||||
|
self.selected_index = 0_usize;
|
||||||
|
}
|
||||||
|
// 2/→ - zmień plik na ConfigSource::Home (pomiń wykonywaniej, jeżeli już jest wybrany)
|
||||||
|
else if key_event.kind == KeyEventKind::Press
|
||||||
|
&& (key_event.code == KeyCode::Char('2') || key_event.code == KeyCode::Right)
|
||||||
|
{
|
||||||
|
self.current_file = ConfigSource::Home;
|
||||||
|
self.selected_index = 0_usize;
|
||||||
|
}
|
||||||
|
// ↑ - scroll w górę
|
||||||
|
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Up {
|
||||||
|
self.selected_index = self.selected_index.saturating_sub(1);
|
||||||
|
}
|
||||||
|
// ↓ - scroll w dół
|
||||||
|
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Down {
|
||||||
|
let max_index = match self.current_file {
|
||||||
|
ConfigSource::System => self.system_modules.len() - 1,
|
||||||
|
ConfigSource::Home => self.home_modules.len() - 1,
|
||||||
|
};
|
||||||
|
self.selected_index = self.selected_index.saturating_add(1).min(max_index);
|
||||||
|
}
|
||||||
|
// ENTER/SPACE - zmień wartość boolen danej opcji
|
||||||
|
else if key_event.kind == KeyEventKind::Press
|
||||||
|
&& (key_event.code == KeyCode::Enter || key_event.code == KeyCode::Char(' '))
|
||||||
|
{
|
||||||
|
let (modules, new_node, new_ast, new_modules, new_module_value) =
|
||||||
|
self.get_selected_option_context();
|
||||||
|
|
||||||
|
self.last_action_status = if let Some(module) = modules.get(self.selected_index) {
|
||||||
|
match self.current_file {
|
||||||
|
ConfigSource::System => {
|
||||||
|
self.system_modules = new_modules;
|
||||||
|
self.system_ast = new_ast;
|
||||||
|
fs::write(&self.system_path, new_node.to_string())
|
||||||
|
.expect("Nie można zapisać pliku modułów systemowych");
|
||||||
|
}
|
||||||
|
ConfigSource::Home => {
|
||||||
|
self.home_modules = new_modules;
|
||||||
|
self.home_ast = new_ast;
|
||||||
|
fs::write(&self.home_path, new_node.to_string())
|
||||||
|
.expect("Nie można zapisać pliku modułów domowych");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{} = {} -> {}",
|
||||||
|
module.path.clone(),
|
||||||
|
module.value.clone(),
|
||||||
|
new_module_value
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"Nothing changed".to_string()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_selected_option_context(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
Vec<ConfigOption>,
|
||||||
|
SyntaxNode,
|
||||||
|
Parse<Root>,
|
||||||
|
Vec<ConfigOption>,
|
||||||
|
bool,
|
||||||
|
) {
|
||||||
|
let modules: &Vec<ConfigOption> = match self.current_file {
|
||||||
|
ConfigSource::System => &self.system_modules,
|
||||||
|
ConfigSource::Home => &self.home_modules,
|
||||||
|
};
|
||||||
|
let node: &SyntaxNode = match self.current_file {
|
||||||
|
ConfigSource::System => &self.system_ast.syntax(),
|
||||||
|
ConfigSource::Home => &self.home_ast.syntax(),
|
||||||
|
};
|
||||||
|
let fallback_module_value: bool = modules
|
||||||
|
.get(self.selected_index)
|
||||||
|
.map(|m| m.value.clone())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let (new_node, new_ast, new_modules, new_module_value) = match modules
|
||||||
|
.get(self.selected_index)
|
||||||
|
{
|
||||||
|
Some(module) => {
|
||||||
|
let new_node: SyntaxNode = toggle_bool_at_path(node, module.path.as_str());
|
||||||
|
|
||||||
|
let new_ast: Parse<Root> = Root::parse(&new_node.to_string().as_str());
|
||||||
|
|
||||||
|
let new_modules: Vec<ConfigOption> =
|
||||||
|
collect_nix_options(&new_node, "", "".to_string(), self.current_file.clone());
|
||||||
|
|
||||||
|
let new_module_value: bool = get_nix_value_by_path(&new_node, module.path.as_str())
|
||||||
|
.unwrap_or_else(|| module.value.clone());
|
||||||
|
|
||||||
|
(new_node, new_ast, new_modules, new_module_value)
|
||||||
|
}
|
||||||
|
None => (
|
||||||
|
node.clone(),
|
||||||
|
Root::parse(&node.to_string().as_str()),
|
||||||
|
Vec::new(),
|
||||||
|
fallback_module_value,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
modules.to_vec(),
|
||||||
|
new_node,
|
||||||
|
new_ast,
|
||||||
|
new_modules,
|
||||||
|
new_module_value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &App {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let vertical_layout: Layout = Layout::vertical([
|
||||||
|
Constraint::Percentage(4),
|
||||||
|
Constraint::Percentage(91),
|
||||||
|
Constraint::Percentage(5),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let [title_area, content_area, footer_area] = vertical_layout.areas(area);
|
||||||
|
|
||||||
|
{
|
||||||
|
let title: Line<'_> =
|
||||||
|
Line::from("GarandOS TUI".bold().italic().yellow()).left_aligned();
|
||||||
|
|
||||||
|
title.render(title_area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let modules: &Vec<ConfigOption> = match self.current_file {
|
||||||
|
ConfigSource::System => &self.system_modules,
|
||||||
|
ConfigSource::Home => &self.home_modules,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
|
let mut last_category: String = String::new();
|
||||||
|
|
||||||
|
for (idx, module) in modules.iter().enumerate() {
|
||||||
|
if module.category != last_category {
|
||||||
|
lines.push("".into());
|
||||||
|
let cat_line = Line::from(vec![
|
||||||
|
Span::raw(format!("/* {} */", module.category))
|
||||||
|
.dark_gray()
|
||||||
|
.italic(),
|
||||||
|
]);
|
||||||
|
lines.push(cat_line);
|
||||||
|
last_category = module.category.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkbox: &'static str = if module.value { "☑" } else { "☐" };
|
||||||
|
|
||||||
|
let mut line: Line<'_> =
|
||||||
|
Line::from(vec![Span::raw(format!("{} {}", checkbox, module.path))]);
|
||||||
|
|
||||||
|
if idx == self.selected_index {
|
||||||
|
line = line.style(Color::Yellow)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_title: &'static str = match self.current_file {
|
||||||
|
ConfigSource::System => "[ System Configuration ]",
|
||||||
|
ConfigSource::Home => "[ Home Configuration ]",
|
||||||
|
};
|
||||||
|
|
||||||
|
let content: Paragraph<'_> = Paragraph::new(lines)
|
||||||
|
.left_aligned()
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(block_title.blue().bold()),
|
||||||
|
)
|
||||||
|
.scroll((self.selected_index as u16, 0));
|
||||||
|
content.render(content_area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let footer_line1: Line<'_> = Line::from(vec![
|
||||||
|
Span::from("Use "),
|
||||||
|
Span::from("↑/↓".italic().green()),
|
||||||
|
Span::from(" to navigate, "),
|
||||||
|
Span::from("Enter/Space".bold().white()),
|
||||||
|
Span::from(" to toggle, "),
|
||||||
|
Span::from("1/2 or ←/→".bold().blue()),
|
||||||
|
Span::from(" to switch file, "),
|
||||||
|
Span::from("q".bold().red()),
|
||||||
|
Span::from(" to quit."),
|
||||||
|
]);
|
||||||
|
let last_action: Span<'_> = if self.last_action_status.is_empty() {
|
||||||
|
Span::from("Nothing changed")
|
||||||
|
} else {
|
||||||
|
self.last_action_status.clone().into()
|
||||||
|
};
|
||||||
|
let footer_line2: Line<'_> = Line::from(vec![
|
||||||
|
Span::from("Last action: "),
|
||||||
|
last_action.italic().green(),
|
||||||
|
Span::from("."),
|
||||||
|
]);
|
||||||
|
let footer: Paragraph<'_> =
|
||||||
|
Paragraph::new(vec![footer_line1, footer_line2]).left_aligned();
|
||||||
|
|
||||||
|
footer.render(footer_area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input_events(tx: mpsc::Sender<Event>) {
|
||||||
|
loop {
|
||||||
|
match event::read() {
|
||||||
|
Ok(ev) => {
|
||||||
|
if let event::Event::Key(key_event) = ev {
|
||||||
|
if tx.send(Event::Input(key_event)).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/cli.rs
Normal file
29
src/cli.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use crate::nix::{NixModules, build_nix_modules};
|
||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(
|
||||||
|
version,
|
||||||
|
about = "Potrzebne pliki znajdziesz w ~/garandos/hosts/<Twój-Host>/",
|
||||||
|
long_about = "Potrzebne pliki znajdziesz w ~/garandos/hosts/<Twój-Host>/"
|
||||||
|
)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Ścieżka do pliku system-modules.nix",
|
||||||
|
value_name = "SYSTEM_MODULES"
|
||||||
|
)]
|
||||||
|
pub sf: PathBuf,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Ścieżka do pliku home-modules.nix",
|
||||||
|
value_name = "HOME_MODULES"
|
||||||
|
)]
|
||||||
|
pub hf: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_modules() -> NixModules {
|
||||||
|
let args: Cli = Cli::parse();
|
||||||
|
build_nix_modules(args.sf, args.hf)
|
||||||
|
}
|
||||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod app;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod nix;
|
||||||
166
src/main.rs
166
src/main.rs
@@ -1,28 +1,29 @@
|
|||||||
use crossterm::{
|
use garandos_tui::{
|
||||||
event,
|
app::{App, Event, handle_input_events},
|
||||||
event::{KeyCode, KeyEvent, KeyEventKind},
|
cli::get_modules,
|
||||||
};
|
nix::{ConfigSource, NixModules},
|
||||||
use ratatui::{
|
|
||||||
DefaultTerminal, Frame, Terminal,
|
|
||||||
prelude::{Buffer, Constraint, CrosstermBackend, Layout, Rect, Stylize},
|
|
||||||
style::{Color, Style},
|
|
||||||
symbols::border,
|
|
||||||
text::Line,
|
|
||||||
widgets::{Block, Gauge, Widget},
|
|
||||||
};
|
};
|
||||||
|
use ratatui::{Terminal, prelude::CrosstermBackend};
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io::{Result, Stdout},
|
||||||
sync::mpsc::{self, Sender},
|
sync::mpsc::{self, Sender},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut terminal: Terminal<CrosstermBackend<io::Stdout>> = ratatui::init();
|
let nix_modules: NixModules = get_modules();
|
||||||
|
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
||||||
let mut app: App = App {
|
let mut app: App = App {
|
||||||
exit: false,
|
exit: false,
|
||||||
progress_bar_color: Color::Green,
|
system_path: nix_modules.system_path,
|
||||||
background_progress: 0_f64,
|
home_path: nix_modules.home_path,
|
||||||
|
system_ast: nix_modules.system_ast,
|
||||||
|
home_ast: nix_modules.home_ast,
|
||||||
|
system_modules: nix_modules.system_modules,
|
||||||
|
home_modules: nix_modules.home_modules,
|
||||||
|
current_file: ConfigSource::System,
|
||||||
|
selected_index: 0_usize,
|
||||||
|
last_action_status: "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<Event>();
|
let (event_tx, event_rx) = mpsc::channel::<Event>();
|
||||||
@@ -32,136 +33,7 @@ fn main() -> io::Result<()> {
|
|||||||
handle_input_events(tx_to_input_events);
|
handle_input_events(tx_to_input_events);
|
||||||
});
|
});
|
||||||
|
|
||||||
let tx_to_background_events: Sender<Event> = event_tx.clone();
|
let app_result: Result<()> = app.run(&mut terminal, event_rx);
|
||||||
thread::spawn(move || {
|
|
||||||
run_background_thread(tx_to_background_events);
|
|
||||||
});
|
|
||||||
|
|
||||||
let app_result: Result<(), io::Error> = app.run(&mut terminal, event_rx);
|
|
||||||
|
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
app_result
|
app_result
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Event {
|
|
||||||
Input(KeyEvent),
|
|
||||||
Progress(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input_events(tx: mpsc::Sender<Event>) {
|
|
||||||
loop {
|
|
||||||
match event::read() {
|
|
||||||
Ok(ev) => {
|
|
||||||
if let event::Event::Key(key_event) = ev {
|
|
||||||
if tx.send(Event::Input(key_event)).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_background_thread(tx: mpsc::Sender<Event>) {
|
|
||||||
let mut progress: f64 = 0_f64;
|
|
||||||
const INCREMENT: f64 = 0.01_f64;
|
|
||||||
loop {
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
progress += INCREMENT;
|
|
||||||
progress = progress.min(1_f64);
|
|
||||||
if tx.send(Event::Progress(progress)).is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
exit: bool,
|
|
||||||
progress_bar_color: Color,
|
|
||||||
background_progress: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn run(&mut self, terminal: &mut DefaultTerminal, rx: mpsc::Receiver<Event>) -> io::Result<()> {
|
|
||||||
while !self.exit {
|
|
||||||
let event: Event = match rx.recv() {
|
|
||||||
Ok(ev) => ev,
|
|
||||||
Err(_) => break,
|
|
||||||
};
|
|
||||||
match event {
|
|
||||||
Event::Input(key_event) => self.handle_key_event(key_event)?,
|
|
||||||
Event::Progress(progress) => self.background_progress = progress,
|
|
||||||
}
|
|
||||||
terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&self, frame: &mut Frame<'_>) {
|
|
||||||
frame.render_widget(self, frame.area());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_key_event(&mut self, key_event: KeyEvent) -> io::Result<()> {
|
|
||||||
if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
|
|
||||||
self.exit = true;
|
|
||||||
} else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('c') {
|
|
||||||
if self.progress_bar_color == Color::Green {
|
|
||||||
self.progress_bar_color = Color::Red;
|
|
||||||
} else {
|
|
||||||
self.progress_bar_color = Color::Green;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for &App {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer)
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let vertical_layout: Layout =
|
|
||||||
Layout::vertical([Constraint::Percentage(20), Constraint::Percentage(80)]);
|
|
||||||
let [title_area, gauge_area] = vertical_layout.areas(area);
|
|
||||||
|
|
||||||
Line::from("Process overview")
|
|
||||||
.bold()
|
|
||||||
.render(title_area, buf);
|
|
||||||
|
|
||||||
let instructions: Line<'_> = Line::from(vec![
|
|
||||||
" Change color ".into(),
|
|
||||||
"<C>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q>".blue().bold(),
|
|
||||||
])
|
|
||||||
.centered();
|
|
||||||
|
|
||||||
let block: Block<'_> = Block::bordered()
|
|
||||||
.title(Line::from(" Background processes "))
|
|
||||||
.title_bottom(instructions)
|
|
||||||
.border_set(border::THICK);
|
|
||||||
|
|
||||||
let progress_bar: Gauge<'_> = Gauge::default()
|
|
||||||
.gauge_style(Style::default().fg(self.progress_bar_color))
|
|
||||||
.block(block)
|
|
||||||
.label(format!(
|
|
||||||
"Process Bar: {:.2}%",
|
|
||||||
self.background_progress * 100_f64
|
|
||||||
))
|
|
||||||
.ratio(self.background_progress);
|
|
||||||
|
|
||||||
progress_bar.render(
|
|
||||||
Rect {
|
|
||||||
x: gauge_area.left(),
|
|
||||||
y: gauge_area.top(),
|
|
||||||
width: gauge_area.width,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
buf,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
250
src/nix.rs
Normal file
250
src/nix.rs
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
use rnix::{NodeOrToken, Parse, Root, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize};
|
||||||
|
use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct NixModules {
|
||||||
|
pub system_path: PathBuf,
|
||||||
|
pub system_ast: Parse<Root>,
|
||||||
|
pub system_modules: Vec<ConfigOption>,
|
||||||
|
pub home_path: PathBuf,
|
||||||
|
pub home_ast: Parse<Root>,
|
||||||
|
pub home_modules: Vec<ConfigOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ConfigOption {
|
||||||
|
pub category: String,
|
||||||
|
pub path: String,
|
||||||
|
pub value: bool,
|
||||||
|
pub source: ConfigSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
|
pub enum ConfigSource {
|
||||||
|
System,
|
||||||
|
Home,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_nix(src: &str) -> Parse<Root> {
|
||||||
|
let ast: Parse<Root> = Root::parse(src);
|
||||||
|
if !ast.errors().is_empty() {
|
||||||
|
eprintln!("Błędy parsowania:");
|
||||||
|
for err in ast.errors() {
|
||||||
|
eprintln!(" - {}", err);
|
||||||
|
}
|
||||||
|
panic!("Błąd parsowania pliku .nix");
|
||||||
|
}
|
||||||
|
ast
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_nix_modules(system_path: PathBuf, home_path: PathBuf) -> NixModules {
|
||||||
|
let system_src: String = fs::read_to_string(&system_path).expect("Failed to read system file");
|
||||||
|
let home_src: String = fs::read_to_string(&home_path).expect("Failed to read home file");
|
||||||
|
|
||||||
|
let system_ast: Parse<Root> = parse_nix(&system_src);
|
||||||
|
let home_ast: Parse<Root> = parse_nix(&home_src);
|
||||||
|
|
||||||
|
let system_modules: Vec<ConfigOption> = collect_nix_options(
|
||||||
|
&system_ast.syntax(),
|
||||||
|
"",
|
||||||
|
"".to_string(),
|
||||||
|
ConfigSource::System,
|
||||||
|
);
|
||||||
|
let home_modules: Vec<ConfigOption> =
|
||||||
|
collect_nix_options(&home_ast.syntax(), "", "".to_string(), ConfigSource::Home);
|
||||||
|
|
||||||
|
NixModules {
|
||||||
|
system_path,
|
||||||
|
system_ast,
|
||||||
|
system_modules,
|
||||||
|
home_path,
|
||||||
|
home_ast,
|
||||||
|
home_modules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_nix_value_by_path(node: &SyntaxNode, query_path: &str) -> Option<bool> {
|
||||||
|
let options: Vec<ConfigOption> =
|
||||||
|
collect_nix_options(node, "", "".to_string(), ConfigSource::System);
|
||||||
|
|
||||||
|
let map: HashMap<&str, bool> = options
|
||||||
|
.iter()
|
||||||
|
.map(|option| (option.path.as_str(), option.value))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
map.get(query_path).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_bool_at_path(node: &SyntaxNode, query_path: &str) -> SyntaxNode {
|
||||||
|
let src: String = node.text().to_string();
|
||||||
|
|
||||||
|
fn walk(
|
||||||
|
syntax_node: &SyntaxNode,
|
||||||
|
cur_path: &str,
|
||||||
|
query_path: &str,
|
||||||
|
) -> Option<(rnix::TextRange, bool)> {
|
||||||
|
let children: Vec<NodeOrToken<SyntaxNode, SyntaxToken>> =
|
||||||
|
syntax_node.children_with_tokens().collect();
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
match child {
|
||||||
|
NodeOrToken::Node(attr_val_node)
|
||||||
|
if attr_val_node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE =>
|
||||||
|
{
|
||||||
|
let mut attr_path: String = String::new();
|
||||||
|
let mut bool_node: Option<SyntaxNode> = None;
|
||||||
|
|
||||||
|
for grand in attr_val_node.children_with_tokens() {
|
||||||
|
if let NodeOrToken::Node(grand_node) = grand {
|
||||||
|
match grand_node.kind() {
|
||||||
|
SyntaxKind::NODE_ATTRPATH => {
|
||||||
|
attr_path = grand_node.text().to_string();
|
||||||
|
}
|
||||||
|
SyntaxKind::NODE_IDENT | SyntaxKind::NODE_LITERAL => {
|
||||||
|
let txt = grand_node.text();
|
||||||
|
if txt == "true" || txt == "false" {
|
||||||
|
bool_node = Some(grand_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxKind::NODE_ATTR_SET => {
|
||||||
|
let next_path: String = if cur_path.is_empty() {
|
||||||
|
attr_path.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", cur_path, attr_path)
|
||||||
|
};
|
||||||
|
if let Some(res) = walk(&grand_node, &next_path, query_path) {
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bool_node) = bool_node {
|
||||||
|
let full_path: String = if cur_path.is_empty() {
|
||||||
|
attr_path.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", cur_path, attr_path)
|
||||||
|
};
|
||||||
|
if full_path == query_path {
|
||||||
|
let range: TextRange = bool_node.text_range();
|
||||||
|
let current_is_true: bool = bool_node.text() == "true";
|
||||||
|
return Some((range, current_is_true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeOrToken::Node(inner) => {
|
||||||
|
if let Some(res) = walk(&inner, cur_path, query_path) {
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((range, current_is_true)) = walk(&node, "", query_path) {
|
||||||
|
let start: usize = <TextSize as Into<usize>>::into(range.start());
|
||||||
|
let end: usize = <TextSize as Into<usize>>::into(range.end());
|
||||||
|
let mut new_src: String = src.clone();
|
||||||
|
new_src.replace_range(start..end, if current_is_true { "false" } else { "true" });
|
||||||
|
Root::parse(&new_src).syntax()
|
||||||
|
} else {
|
||||||
|
Root::parse(&src).syntax()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_nix_options(
|
||||||
|
node: &SyntaxNode,
|
||||||
|
current_path: &str,
|
||||||
|
mut current_category: String,
|
||||||
|
current_source: ConfigSource,
|
||||||
|
) -> Vec<ConfigOption> {
|
||||||
|
let mut result: Vec<ConfigOption> = Vec::new();
|
||||||
|
|
||||||
|
let children: Vec<NodeOrToken<SyntaxNode, SyntaxToken>> = node.children_with_tokens().collect();
|
||||||
|
|
||||||
|
for i in 0..children.len() {
|
||||||
|
match &children[i] {
|
||||||
|
NodeOrToken::Token(token) if token.kind() == SyntaxKind::TOKEN_COMMENT => {
|
||||||
|
let text: &str = token.text();
|
||||||
|
if text.starts_with("/*") && text.ends_with("*/") {
|
||||||
|
let content: String = text
|
||||||
|
.trim_start_matches("/*")
|
||||||
|
.trim_end_matches("*/")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
current_category = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeOrToken::Node(child_node)
|
||||||
|
if child_node.kind() == SyntaxKind::NODE_ATTRPATH_VALUE =>
|
||||||
|
{
|
||||||
|
let mut attr_path: String = String::new();
|
||||||
|
let mut value_node: Option<SyntaxNode> = None;
|
||||||
|
|
||||||
|
for grand in child_node.children_with_tokens() {
|
||||||
|
if let NodeOrToken::Node(grand_node) = grand {
|
||||||
|
match grand_node.kind() {
|
||||||
|
SyntaxKind::NODE_ATTRPATH => {
|
||||||
|
attr_path = grand_node.text().to_string();
|
||||||
|
}
|
||||||
|
SyntaxKind::NODE_IDENT | SyntaxKind::NODE_LITERAL => {
|
||||||
|
let text: String = grand_node.text().to_string();
|
||||||
|
if text == "true" || text == "false" {
|
||||||
|
value_node = Some(grand_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxKind::NODE_ATTR_SET => {
|
||||||
|
let new_path: String = if current_path.is_empty() {
|
||||||
|
attr_path.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", current_path, attr_path)
|
||||||
|
};
|
||||||
|
result.extend(collect_nix_options(
|
||||||
|
&grand_node,
|
||||||
|
&new_path,
|
||||||
|
current_category.clone(),
|
||||||
|
current_source.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value_node) = value_node {
|
||||||
|
let full_path: String = if current_path.is_empty() {
|
||||||
|
attr_path
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", current_path, attr_path)
|
||||||
|
};
|
||||||
|
let bool_value: bool = value_node.text() == "true";
|
||||||
|
result.push(ConfigOption {
|
||||||
|
category: current_category.clone(),
|
||||||
|
path: full_path,
|
||||||
|
value: bool_value,
|
||||||
|
source: current_source.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeOrToken::Node(child_node) => {
|
||||||
|
result.extend(collect_nix_options(
|
||||||
|
child_node,
|
||||||
|
current_path,
|
||||||
|
current_category.clone(),
|
||||||
|
current_source.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user