Add Nix configuration parser and option extraction
Introduce rnix dependency to parse Nix files and collect boolean options with their full attribute paths from a test configuration file
This commit is contained in:
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -8,6 +8,12 @@ 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 = "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"
|
||||||
@@ -58,6 +64,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"
|
||||||
@@ -206,8 +218,15 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"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"
|
||||||
@@ -313,7 +332,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]]
|
||||||
@@ -405,6 +433,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 +586,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"
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
|
rnix = "0.12.0"
|
||||||
|
|||||||
175
src/main.rs
175
src/main.rs
@@ -1,167 +1,24 @@
|
|||||||
use crossterm::{
|
use rnix::{Parse, Root};
|
||||||
event,
|
use std::fs;
|
||||||
event::{KeyCode, KeyEvent, KeyEventKind},
|
|
||||||
};
|
|
||||||
use ratatui::{
|
|
||||||
DefaultTerminal, Frame, Terminal,
|
|
||||||
prelude::{Buffer, Constraint, CrosstermBackend, Layout, Rect, Stylize},
|
|
||||||
style::{Color, Style},
|
|
||||||
symbols::border,
|
|
||||||
text::Line,
|
|
||||||
widgets::{Block, Gauge, Widget},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
io,
|
|
||||||
sync::mpsc::{self, Sender},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
mod nix;
|
||||||
let mut terminal: Terminal<CrosstermBackend<io::Stdout>> = ratatui::init();
|
|
||||||
let mut app: App = App {
|
|
||||||
exit: false,
|
|
||||||
progress_bar_color: Color::Green,
|
|
||||||
background_progress: 0_f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (event_tx, event_rx) = mpsc::channel::<Event>();
|
fn main() {
|
||||||
|
let content: String =
|
||||||
|
fs::read_to_string("src/test.nix").expect("Nie można odczytać pliku src/test.nix");
|
||||||
|
|
||||||
let tx_to_input_events: Sender<Event> = event_tx.clone();
|
let ast: Parse<Root> = Root::parse(&content);
|
||||||
thread::spawn(move || {
|
|
||||||
handle_input_events(tx_to_input_events);
|
|
||||||
});
|
|
||||||
|
|
||||||
let tx_to_background_events: Sender<Event> = event_tx.clone();
|
if !ast.errors().is_empty() {
|
||||||
thread::spawn(move || {
|
eprintln!("Błędy parsowania:");
|
||||||
run_background_thread(tx_to_background_events);
|
for error in ast.errors() {
|
||||||
});
|
eprintln!(" - {}", error);
|
||||||
|
|
||||||
let app_result: Result<(), io::Error> = app.run(&mut terminal, event_rx);
|
|
||||||
|
|
||||||
ratatui::restore();
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
eprintln!();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
let options: Vec<(String, bool)> = nix::collect_nix_options_with_path(&ast.syntax(), "");
|
||||||
|
for (path, value) in options {
|
||||||
fn run_background_thread(tx: mpsc::Sender<Event>) {
|
println!("{} = {};", path, value);
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
src/nix.rs
Normal file
68
src/nix.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use rnix::{NodeOrToken, SyntaxKind, SyntaxNode};
|
||||||
|
|
||||||
|
pub fn collect_nix_options_with_path(node: &SyntaxNode, current_path: &str) -> Vec<(String, bool)> {
|
||||||
|
let mut result: Vec<(String, bool)> = Vec::new();
|
||||||
|
|
||||||
|
for child in node.children_with_tokens() {
|
||||||
|
if let NodeOrToken::Node(child_node) = child {
|
||||||
|
match 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_with_path(
|
||||||
|
&grand_node,
|
||||||
|
&new_path,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 value_text: String = value_node.text().to_string();
|
||||||
|
let mut bool_value: bool = false;
|
||||||
|
|
||||||
|
if value_text == "true" {
|
||||||
|
bool_value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push((full_path, bool_value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
result.extend(collect_nix_options_with_path(&child_node, current_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
76
src/test.nix
Normal file
76
src/test.nix
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
_: {
|
||||||
|
/*
|
||||||
|
Container & Packaging
|
||||||
|
*/
|
||||||
|
docker.enable = true; # Docker: container runtime and management
|
||||||
|
flatpak = {
|
||||||
|
enable = true; # Flatpak: universal packaging system for Linux
|
||||||
|
packages = {
|
||||||
|
sober = false; # Roblox client
|
||||||
|
warehouse = true; # Flatpak manager
|
||||||
|
flatseal = true; # Flatpak permissions manager
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gaming
|
||||||
|
*/
|
||||||
|
gamemode.enable = true; # GameMode: optimizes system performance for gaming
|
||||||
|
gamescope.enable = false; # Gamescope: micro‑compositor for games
|
||||||
|
steam.enable = true; # Steam: platform for buying and playing games
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
/*
|
||||||
|
Container & Packaging
|
||||||
|
*/
|
||||||
|
distrobox.enable = false; # Distrobox: containerized development environments
|
||||||
|
lazydocker.enable = false; # Lazydocker: simple TUI for Docker
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gaming
|
||||||
|
*/
|
||||||
|
prismlauncher.enable = false; # Prism Launcher: Minecraft modded launcher
|
||||||
|
spaceCadetPinball.enable = true; # SpaceCadet Pinball: classic pinball game
|
||||||
|
ttySolitaire.enable = true; # TTY Solitaire: terminal‑based solitaire game
|
||||||
|
|
||||||
|
/*
|
||||||
|
Development Tools
|
||||||
|
*/
|
||||||
|
exercism.enable = true; # Exercism: coding practice platform
|
||||||
|
lazygit.enable = false; # Lazygit: simple TUI for Git
|
||||||
|
opencode.enable = true; # OpenCode: tools for coding and development
|
||||||
|
jan.enable = true; # Jan: AI chat UI
|
||||||
|
|
||||||
|
/*
|
||||||
|
Communication & Collaboration
|
||||||
|
*/
|
||||||
|
mattermost.enable = true; # Mattermost: open‑source Slack alternative
|
||||||
|
slack.enable = true; # Slack: team communication and collaboration tool
|
||||||
|
tutanota.enable = true; # Tutanota: secure email client
|
||||||
|
|
||||||
|
/*
|
||||||
|
Productivity / Knowledge Management
|
||||||
|
*/
|
||||||
|
bitwarden.enable = false; # Bitwarden: password manager (desktop)
|
||||||
|
iotas.enable = true; # Iotas: lightweight notes manager
|
||||||
|
logseq.enable = false; # Logseq: knowledge base and outliner
|
||||||
|
|
||||||
|
/*
|
||||||
|
Media & Graphics
|
||||||
|
*/
|
||||||
|
affinity.enable = false; # Affinity: professional graphics suite
|
||||||
|
eyeOfGnome.enable = true; # Eye of GNOME: image viewer
|
||||||
|
freetube.enable = false; # FreeTube: privacy‑friendly YouTube client
|
||||||
|
gimp.enable = false; # GIMP: GNU Image Manipulation Program
|
||||||
|
kdenlive.enable = false; # Kdenlive: video editing software
|
||||||
|
plex.enable = true; # Plex: media player and server client
|
||||||
|
|
||||||
|
/*
|
||||||
|
Utilities / Misc
|
||||||
|
*/
|
||||||
|
eddieAirVPN.enable = true; # Eddie AirVPN: VPN client
|
||||||
|
galculator.enable = true; # Galculator: simple calculator
|
||||||
|
gedit.enable = false; # Gedit: GNOME text editor
|
||||||
|
winboat.enable = false; # Winboat: Windows remote desktop via RDP
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user