Skip to content

Commit 1ef5243

Browse files
committed
feat(oma-ubuntu-cmd-not-found): init for ubuntu command-not-found query
1 parent 27cb4c6 commit 1ef5243

File tree

6 files changed

+222
-9
lines changed

6 files changed

+222
-9
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ oma-fetch = { path = "./oma-fetch", default-features = false }
5050
oma-topics = { path = "./oma-topics", optional = true, default-features = false }
5151
oma-history = { path = "./oma-history" }
5252
oma-repo-verify = { path = "./oma-repo-verify" }
53+
oma-ubuntu-cmd-not-found = { path = "./oma-ubuntu-cmd-not-found", optional = true }
5354

5455
# i18n
5556
i18n-embed = { version = "0.15.0", features = ["fluent-system", "desktop-requester"]}
@@ -70,13 +71,14 @@ sequoia-openssl-backend = ["oma-refresh/sequoia-openssl-backend"]
7071
sequoia-nettle-backend = ["oma-refresh/sequoia-nettle-backend"]
7172
egg = ["dep:colored", "dep:image"]
7273
tokio-console = ["dep:console-subscriber"]
74+
ubuntu = ["dep:oma-ubuntu-cmd-not-found"]
7375
rustls = ["reqwest/rustls-tls", "oma-fetch/rustls", "oma-refresh/rustls", "oma-topics/rustls"]
7476
openssl = ["reqwest/native-tls", "oma-fetch/native-tls", "oma-refresh/native-tls", "oma-topics/native-tls"]
7577
generic = ["sequoia-nettle-backend", "rustls"]
7678
default = ["aosc", "generic"]
7779

7880
[workspace]
79-
members = ["oma-contents", "oma-console", "oma-topics", "oma-fetch", "oma-refresh", "oma-utils", "oma-pm", "oma-history", "oma-pm-operation-type", "oma-repo-verify"]
81+
members = ["oma-contents", "oma-console", "oma-topics", "oma-fetch", "oma-refresh", "oma-utils", "oma-pm", "oma-history", "oma-pm-operation-type", "oma-repo-verify", "oma-ubuntu-cmd-not-found"]
8082

8183
[package.metadata.deb]
8284
copyright = "2024, AOSC Dev <[email protected]>"

oma-ubuntu-cmd-not-found/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "oma-ubuntu-cmd-not-found"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Ubuntu command-not-found database handling library"
6+
7+
[dependencies]
8+
rusqlite = { version = "0.32", features = ["bundled"] }
9+
tracing = "0.1"
10+
thiserror = "1.0"

oma-ubuntu-cmd-not-found/src/lib.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use rusqlite::Connection;
2+
pub use rusqlite::Error;
3+
use std::path::Path;
4+
5+
pub struct UbuntuCmdNotFound {
6+
db: Connection,
7+
}
8+
9+
impl UbuntuCmdNotFound {
10+
const DEFAULT_DB_PATH: &str = "/var/lib/command-not-found/commands.db";
11+
pub fn new(db: impl AsRef<Path>) -> Result<Self, rusqlite::Error> {
12+
let db = Connection::open(db)?;
13+
14+
Ok(Self { db })
15+
}
16+
17+
pub fn default_new() -> Result<Self, rusqlite::Error> {
18+
Self::new(Self::DEFAULT_DB_PATH)
19+
}
20+
21+
pub fn query(&self, query: &str) -> Result<Vec<(String, String)>, rusqlite::Error> {
22+
let count = self.query_where_command_count(query)?;
23+
24+
if count != 0 {
25+
let res = self.query_where_command(query)?;
26+
return Ok(res);
27+
}
28+
29+
let res = self.query_where_command_like(query)?;
30+
31+
Ok(res)
32+
}
33+
34+
fn query_where_command_like(&self, query: &str) -> Result<Vec<(String, String)>, Error> {
35+
let mut stmt = self
36+
.db
37+
.prepare("SELECT pkgID, command FROM commands WHERE command LIKE ?1")?;
38+
let query_str = format!("{}%", query);
39+
let res_iter = stmt.query_map([query_str], |row| {
40+
let pkg_id: i64 = row.get(0)?;
41+
let cmd: String = row.get(1)?;
42+
43+
Ok((pkg_id, cmd))
44+
})?;
45+
let mut res = vec![];
46+
for i in res_iter {
47+
let (pkg_id, cmd) = i?;
48+
let pkgs = self.get_pkg_from_from_pkg_id(pkg_id)?;
49+
50+
for pkg in pkgs {
51+
res.push((pkg, cmd.clone()));
52+
}
53+
}
54+
Ok(res)
55+
}
56+
57+
fn query_where_command_count(&self, query: &str) -> Result<i64, Error> {
58+
let mut stmt = self
59+
.db
60+
.prepare("SELECT COUNT(command) AS count FROM commands WHERE command = ?1")?;
61+
let mut res_iter = stmt.query_map([query], |row| row.get(0))?;
62+
63+
if let Some(Ok(n)) = res_iter.next() {
64+
return Ok(n);
65+
}
66+
67+
Ok(0)
68+
}
69+
70+
fn query_where_command(&self, query: &str) -> Result<Vec<(String, String)>, Error> {
71+
let mut stmt = self
72+
.db
73+
.prepare("SELECT pkgID, command FROM commands WHERE command = ?1")?;
74+
let res_iter = stmt.query_map([query], |row| {
75+
let pkg_id: i64 = row.get(0)?;
76+
let cmd: String = row.get(1)?;
77+
78+
Ok((pkg_id, cmd))
79+
})?;
80+
let mut res = vec![];
81+
for i in res_iter {
82+
let (pkg_id, cmd) = i?;
83+
let pkgs = self.get_pkg_from_from_pkg_id(pkg_id)?;
84+
85+
for pkg in pkgs {
86+
res.push((pkg, cmd.clone()));
87+
}
88+
}
89+
Ok(res)
90+
}
91+
92+
fn get_pkg_from_from_pkg_id(&self, pkg_id: i64) -> Result<Vec<String>, rusqlite::Error> {
93+
let mut res = vec![];
94+
let mut stmt = self
95+
.db
96+
.prepare("SELECT name FROM packages where pkgID = ?1")?;
97+
let pkgs = stmt.query_map([pkg_id], |row| {
98+
let res: String = row.get(0)?;
99+
Ok(res)
100+
})?;
101+
102+
for pkg in pkgs {
103+
let pkg = pkg?;
104+
res.push(pkg);
105+
}
106+
107+
Ok(res)
108+
}
109+
}

src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,16 @@ impl From<anyhow::Error> for OutputError {
607607
}
608608
}
609609

610+
#[cfg(feature = "ubuntu")]
611+
impl From<oma_ubuntu_cmd_not_found::Error> for OutputError {
612+
fn from(value: oma_ubuntu_cmd_not_found::Error) -> Self {
613+
Self {
614+
description: value.to_string(),
615+
source: Some(Box::new(value)),
616+
}
617+
}
618+
}
619+
610620
pub fn oma_apt_error_to_output(err: OmaAptError) -> OutputError {
611621
debug!("{:?}", err);
612622
match err {

src/subcommand/command_not_found.rs

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
use std::error::Error;
21
use std::io::stdout;
32

43
use ahash::AHashMap;
5-
use oma_console::due_to;
64
use oma_console::print::Action;
7-
use oma_contents::searcher::{pure_search, ripgrep_search, Mode};
8-
use oma_contents::OmaContentsError;
95
use oma_pm::apt::{AptConfig, OmaApt, OmaAptArgs};
106
use oma_pm::format_description;
117
use tracing::error;
@@ -14,12 +10,18 @@ use crate::error::OutputError;
1410
use crate::table::PagerPrinter;
1511
use crate::{color_formatter, fl};
1612

17-
const FILTER_JARO_NUM: u8 = 204;
18-
const APT_LIST_PATH: &str = "/var/lib/apt/lists";
13+
#[cfg(not(feature = "ubuntu"))]
14+
pub fn execute(query: &str) -> Result<i32, OutputError> {
15+
use oma_console::due_to;
16+
use oma_contents::searcher::{pure_search, ripgrep_search, Mode};
17+
use oma_contents::OmaContentsError;
18+
use std::error::Error;
1919

20-
type IndexSet<T> = indexmap::IndexSet<T, ahash::RandomState>;
20+
const FILTER_JARO_NUM: u8 = 204;
21+
const APT_LIST_PATH: &str = "/var/lib/apt/lists";
22+
23+
type IndexSet<T> = indexmap::IndexSet<T, ahash::RandomState>;
2124

22-
pub fn execute(query: &str) -> Result<i32, OutputError> {
2325
let mut res = IndexSet::with_hasher(ahash::RandomState::new());
2426

2527
let cb = |line| {
@@ -136,6 +138,76 @@ pub fn execute(query: &str) -> Result<i32, OutputError> {
136138
Ok(127)
137139
}
138140

141+
#[cfg(feature = "ubuntu")]
142+
pub fn execute(query: &str) -> Result<i32, OutputError> {
143+
use oma_ubuntu_cmd_not_found::UbuntuCmdNotFound;
144+
145+
let cnf = UbuntuCmdNotFound::default_new()?;
146+
let query_res = cnf.query(query)?;
147+
let mut too_many = false;
148+
let mut map: AHashMap<String, String> = AHashMap::new();
149+
let apt_config = AptConfig::new();
150+
let oma_apt_args = OmaAptArgs::builder().build();
151+
let apt = OmaApt::new(vec![], oma_apt_args, false, apt_config)?;
152+
let mut res = vec![];
153+
154+
for (i, (pkg, cmd)) in query_res.iter().enumerate() {
155+
if i == 10 {
156+
too_many = true;
157+
break;
158+
}
159+
160+
let desc = if let Some(desc) = map.get(pkg) {
161+
desc.to_string()
162+
} else if let Some(pkg) = apt.cache.get(&pkg) {
163+
let desc = pkg
164+
.candidate()
165+
.and_then(|x| {
166+
x.description()
167+
.map(|x| format_description(&x).0.to_string())
168+
})
169+
.unwrap_or_else(|| "no description.".to_string());
170+
171+
map.insert(pkg.name().to_string(), desc.to_string());
172+
173+
desc
174+
} else {
175+
continue;
176+
};
177+
178+
let entry = (
179+
color_formatter()
180+
.color_str(pkg, Action::Emphasis)
181+
.bold()
182+
.to_string(),
183+
color_formatter()
184+
.color_str(cmd, Action::Secondary)
185+
.to_string(),
186+
desc,
187+
);
188+
189+
res.push(entry);
190+
}
191+
192+
if res.is_empty() {
193+
error!("{}", fl!("command-not-found", kw = query));
194+
} else {
195+
println!("{}\n", fl!("command-not-found-with-result", kw = query));
196+
let mut printer = PagerPrinter::new(stdout());
197+
printer
198+
.print_table(res, vec!["Name", "Path", "Description"])
199+
.ok();
200+
201+
if too_many {
202+
println!("\n{}", fl!("cnf-too-many-query"));
203+
println!("{}", fl!("cnf-too-many-query-2", query = query));
204+
}
205+
}
206+
207+
Ok(127)
208+
}
209+
210+
#[cfg(not(feature = "ubuntu"))]
139211
fn jaro_nums(input: IndexSet<(String, String)>, query: &str) -> Vec<(String, String, u8)> {
140212
let mut output = vec![];
141213

0 commit comments

Comments
 (0)