aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.rs9
-rw-r--r--src/event.rs24
-rw-r--r--src/gentoo/package.rs21
-rw-r--r--src/gentoo/portage.rs79
-rw-r--r--src/main.rs13
-rw-r--r--src/ui.rs97
6 files changed, 227 insertions, 16 deletions
diff --git a/src/app.rs b/src/app.rs
index f2b4363..b4e6456 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -35,6 +35,15 @@ impl<'a> App<'a> {
self.portage.installed_packages.clone()
}
+ pub fn current_package(&self) -> Package {
+ let selected_package_index = match self.list_state.selected() {
+ Some(selected) => selected,
+ None => 0,
+ };
+
+ self.portage.installed_packages[selected_package_index].clone()
+ }
+
pub fn toggle_search_window(&mut self) {
self.showing_search_window = !self.showing_search_window;
self.textarea.clear();
diff --git a/src/event.rs b/src/event.rs
index 530e244..4b0ac43 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,6 +1,6 @@
use std::io;
-use ratatui::crossterm::event::{self, Event, KeyCode};
+use ratatui::crossterm::event::{self, Event, KeyCode, KeyModifiers};
use crate::app::App;
@@ -15,6 +15,28 @@ pub fn handle_event(app: &mut App) -> io::Result<bool> {
KeyCode::Char('n') => app.next_search(),
KeyCode::Char('N') => app.prev_search(),
+ KeyCode::Char('d') => match key.modifiers {
+ KeyModifiers::CONTROL => app
+ .list_state
+ .select(Some(app.list_state.selected().unwrap_or(0) + 30)),
+
+ _ => return Ok(false),
+ },
+
+ KeyCode::Char('u') => match key.modifiers {
+ KeyModifiers::CONTROL => {
+ if let Some(selected) = app.list_state.selected() {
+ if selected < 30 {
+ app.list_state.select(Some(0));
+ } else {
+ app.list_state.select(Some(selected - 30))
+ }
+ }
+ }
+
+ _ => return Ok(false),
+ },
+
_ => return Ok(false),
}
} else {
diff --git a/src/gentoo/package.rs b/src/gentoo/package.rs
index 0fe9a6c..1d7b3f3 100644
--- a/src/gentoo/package.rs
+++ b/src/gentoo/package.rs
@@ -6,15 +6,34 @@ pub struct Package {
pub name: String,
pub use_flags: Vec<UseFlag>,
pub version: String,
+ pub repository: String,
+ pub maintainer: Option<String>,
+ pub description: Option<String>,
+ pub homepage: Option<Vec<String>>,
+ pub license: Option<String>,
}
#[allow(dead_code)]
impl Package {
- pub fn new(name: String, version: String, use_flags: Vec<UseFlag>) -> Self {
+ pub fn new(
+ name: String,
+ version: String,
+ repository: String,
+ homepage: Option<Vec<String>>,
+ license: Option<String>,
+ description: Option<String>,
+ maintainer: Option<String>,
+ use_flags: Vec<UseFlag>,
+ ) -> Self {
Package {
name: name,
use_flags: use_flags,
version: version,
+ repository: repository,
+ maintainer: maintainer,
+ description: description,
+ homepage: homepage,
+ license: license,
}
}
}
diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs
index 40b03c0..882cc5f 100644
--- a/src/gentoo/portage.rs
+++ b/src/gentoo/portage.rs
@@ -1,4 +1,6 @@
-use std::fs;
+use std::{fs, io, path::PathBuf};
+
+use quick_xml::{Reader, events::Event};
use crate::gentoo::{Package, UseFlag};
@@ -25,6 +27,45 @@ fn split_pkg(input: &str) -> (&str, Option<&str>) {
(input, None)
}
+fn extract_maintainer(path: &PathBuf) -> io::Result<Option<String>> {
+ let s = fs::read_to_string(path)?;
+ let mut reader = Reader::from_str(s.as_str());
+ reader.config_mut().trim_text(true);
+
+ let mut buf = Vec::new();
+ let mut in_maintainer = false;
+
+ loop {
+ match reader.read_event_into(&mut buf) {
+ Ok(Event::Start(e)) if e.name().as_ref() == b"maintainer" => {
+ in_maintainer = true;
+ }
+
+ Ok(Event::Text(e)) if in_maintainer => {
+ match e.xml_content() {
+ Ok(s) => {
+ return Ok(Some(s.into()));
+ }
+ Err(_) => return Ok(None),
+ };
+ }
+
+ Ok(Event::End(e)) if e.name().as_ref() == b"maintainer" => {
+ in_maintainer = false;
+ }
+
+ Ok(Event::Eof) => break,
+ Err(_) => break,
+
+ _ => {}
+ }
+
+ buf.clear();
+ }
+
+ Ok(None)
+}
+
impl Portage {
pub fn new() -> Self {
Self {
@@ -32,7 +73,7 @@ impl Portage {
}
}
- pub fn load_installed_packages(&mut self) -> Result<(), std::io::Error> {
+ pub fn load_installed_packages(&mut self) -> io::Result<()> {
let categories = fs::read_dir("/var/db/pkg")?;
for category in categories {
@@ -72,9 +113,43 @@ impl Portage {
})
.collect();
+ // let category = fs::read_to_string(pkg.path().join("CATEGORY"))?;
+ let repository = fs::read_to_string(pkg.path().join("repository"))?
+ .trim()
+ .to_string();
+ let homepage: Option<Vec<String>> =
+ match fs::read_to_string(pkg.path().join("HOMEPAGE")) {
+ Ok(homepage) => Some(homepage.split('\n').map(|s| s.to_string()).collect()),
+ Err(_) => None,
+ };
+
+ let license: Option<String> = match fs::read_to_string(pkg.path().join("LICENSE")) {
+ Ok(license) => Some(license.trim().to_string()),
+ Err(_) => None,
+ };
+
+ let description: Option<String> =
+ match fs::read_to_string(pkg.path().join("DESCRIPTION")) {
+ Ok(description) => Some(description.trim().to_string()),
+ Err(_) => None,
+ };
+
+ let repo_path = PathBuf::from("/var/db/repos/")
+ .join(&repository)
+ .join(cat_name.to_str().unwrap())
+ .join(pkg_name)
+ .join("metadata.xml");
+
+ let maintainer = extract_maintainer(&repo_path)?;
+
self.installed_packages.push(Package::new(
format!("{}/{}", cat_name.to_str().unwrap(), pkg_name),
pkg_version.unwrap().into(),
+ repository,
+ homepage,
+ license,
+ description,
+ maintainer,
use_flags,
));
}
diff --git a/src/main.rs b/src/main.rs
index 55e721a..1c25018 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,9 +20,20 @@ use ratatui::{
use crate::{app::App, event::handle_event, gentoo::Portage, ui::ui};
+struct TuiGuard;
+
+impl Drop for TuiGuard {
+ fn drop(&mut self) {
+ _ = restore_tui();
+ }
+}
+
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
init_panic_hook();
+
+ let _guard = TuiGuard;
+
let mut tui = init_tui()?;
let mut p = Portage::new();
@@ -32,7 +43,7 @@ fn main() -> color_eyre::Result<()> {
run(&mut tui, &mut app)?;
- restore_tui()?;
+ // restore_tui()?;
Ok(())
}
diff --git a/src/ui.rs b/src/ui.rs
index 92c2758..0bd42e3 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -2,9 +2,9 @@ use ratatui::{
Frame,
layout::{Constraint, Direction, Layout},
prelude::Rect,
- style::{Color, Modifier, Style},
+ style::{Color, Modifier, Style, Stylize},
text::{Line, Span},
- widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
+ widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap},
};
use crate::app::App;
@@ -15,12 +15,15 @@ pub fn ui(frame: &mut Frame, app: &mut App) {
let layout = Layout::vertical(constraints);
let [top, bottom] = frame.area().layout(&layout);
- let split = Layout::horizontal([Constraint::Percentage(70), Constraint::Percentage(30)])
- .spacing(1)
- .split(top);
+ let split =
+ Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).split(top);
+
+ let split_vert =
+ Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).split(split[1]);
render_packages(frame, split[0], app);
- render_use_flags(frame, split[1], app);
+ render_use_flags(frame, split_vert[0], app);
+ render_package_metadata(frame, split_vert[1], app);
let text = match app.showing_search_window {
false => {
@@ -69,6 +72,82 @@ pub fn ui(frame: &mut Frame, app: &mut App) {
}
}
+fn render_package_metadata(frame: &mut Frame, area: Rect, app: &mut App) {
+ let pkg = app.current_package();
+
+ let bold_style = Style::default().add_modifier(Modifier::BOLD);
+ let maintainer = Line::from_iter([
+ Span::styled("Maintainer: ", bold_style),
+ Span::raw(pkg.maintainer.as_deref().unwrap_or("Unknown")),
+ ]);
+
+ let version = Line::from_iter([
+ Span::styled("Version: ", bold_style),
+ Span::raw(pkg.version),
+ ]);
+
+ let repository = Line::from_iter([
+ Span::styled("Repository: ", bold_style),
+ Span::raw(pkg.repository),
+ ]);
+
+ let license = Line::from_iter([
+ Span::styled("License: ", bold_style),
+ Span::raw(pkg.license.as_deref().unwrap_or("Unknown")),
+ ]);
+
+ let description = Line::from_iter([
+ Span::styled("Description: ", bold_style),
+ Span::raw(pkg.description.as_deref().unwrap_or("Unknown")),
+ ]);
+
+ // let homepage = Line::from_iter([
+ // Span::styled("Homepage: ", bold_style),
+ // Span::styled(
+ // pkg.homepage.as_deref().unwrap_or("Unknown"),
+ // Style::default().fg(Color::LightBlue),
+ // ),
+ // ]);
+
+ let mut lines = Vec::new();
+
+ // First line: label
+ lines.push(Line::from(vec![
+ Span::styled("Homepage: ", bold_style),
+ Span::raw(""),
+ ]));
+
+ // Following lines: one URL per line, indented
+ if let Some(homepages) = pkg.homepage {
+ for url in homepages.into_iter().filter(|s| !s.is_empty()) {
+ lines.push(Line::from(vec![
+ Span::raw(" "), // indentation
+ Span::styled(
+ url,
+ Style::default()
+ .fg(Color::LightBlue)
+ .add_modifier(Modifier::UNDERLINED),
+ ),
+ ]));
+ }
+ } else {
+ lines.push(Line::from(vec![
+ Span::raw(" "),
+ Span::styled("Unknown", Style::default().fg(Color::DarkGray)),
+ ]));
+ }
+
+ let mut homepage = lines;
+ let mut lines = vec![maintainer, version, repository, license, description];
+ lines.append(&mut homepage);
+
+ let paragraph = Paragraph::new(lines)
+ .block(Block::default().title("Metadata").borders(Borders::ALL))
+ .wrap(Wrap { trim: false });
+
+ frame.render_widget(paragraph, area);
+}
+
fn search_popup_rect(percent_x: u16, r: Rect) -> Rect {
// Cut the given rectangle into three vertical pieces
let popup_layout = Layout::default()
@@ -103,11 +182,7 @@ fn render_packages(frame: &mut Frame, area: Rect, app: &mut App) {
}
fn render_use_flags(frame: &mut Frame, area: Rect, app: &mut App) {
- let selected_package_index = match app.list_state.selected() {
- Some(selected) => selected,
- None => 0,
- };
- let selected_package = app.installed_packages()[selected_package_index].clone();
+ let selected_package = app.current_package();
let items = selected_package.use_flags.into_iter().map(|x| {
let color = if x.enabled {