Skip to content

Commit 325a22d

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

File tree

6 files changed

+227
-8
lines changed

6 files changed

+227
-8
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: 4 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]>"
@@ -104,3 +106,4 @@ default-features = false
104106
lto = "thin"
105107
opt-level = 3
106108
codegen-units = 1
109+
debug = 2

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: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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_where_command_like(&self, query: &str) -> Result<Vec<(String, String)>, Error> {
22+
let mut stmt = self
23+
.db
24+
.prepare("SELECT pkgID, command FROM commands WHERE command LIKE ?1")?;
25+
let query_str = format!("{}%", query);
26+
let res_iter = stmt.query_map([query_str], |row| {
27+
let pkg_id: i64 = row.get(0)?;
28+
let cmd: String = row.get(1)?;
29+
30+
Ok((pkg_id, cmd))
31+
})?;
32+
let mut res = vec![];
33+
for i in res_iter {
34+
let (pkg_id, cmd) = i?;
35+
let pkgs = self.get_pkg_from_from_pkg_id(pkg_id)?;
36+
37+
for pkg in pkgs {
38+
res.push((pkg, cmd.clone()));
39+
}
40+
}
41+
Ok(res)
42+
}
43+
44+
pub fn query_where_command_count(&self, query: &str) -> Result<i64, Error> {
45+
let mut stmt = self
46+
.db
47+
.prepare("SELECT COUNT(command) AS count FROM commands WHERE command = ?1")?;
48+
let mut res_iter = stmt.query_map([query], |row| row.get(0))?;
49+
50+
if let Some(Ok(n)) = res_iter.next() {
51+
return Ok(n);
52+
}
53+
54+
Ok(0)
55+
}
56+
57+
pub fn query_where_command_like_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 LIKE ?")?;
61+
let query_str = format!("{}%", query);
62+
let mut res_iter = stmt.query_map([query_str], |row| row.get(0))?;
63+
64+
if let Some(Ok(n)) = res_iter.next() {
65+
return Ok(n);
66+
}
67+
68+
Ok(0)
69+
}
70+
71+
pub fn query_where_command(&self, query: &str) -> Result<Vec<(String, String)>, Error> {
72+
let mut stmt = self
73+
.db
74+
.prepare("SELECT pkgID, command FROM commands WHERE command = ?1")?;
75+
let res_iter = stmt.query_map([query], |row| {
76+
let pkg_id: i64 = row.get(0)?;
77+
let cmd: String = row.get(1)?;
78+
79+
Ok((pkg_id, cmd))
80+
})?;
81+
let mut res = vec![];
82+
for i in res_iter {
83+
let (pkg_id, cmd) = i?;
84+
let pkgs = self.get_pkg_from_from_pkg_id(pkg_id)?;
85+
86+
for pkg in pkgs {
87+
res.push((pkg, cmd.clone()));
88+
}
89+
}
90+
Ok(res)
91+
}
92+
93+
fn get_pkg_from_from_pkg_id(&self, pkg_id: i64) -> Result<Vec<String>, rusqlite::Error> {
94+
let mut res = vec![];
95+
let mut stmt = self
96+
.db
97+
.prepare("SELECT name FROM packages where pkgID = ?1")?;
98+
let pkgs = stmt.query_map([pkg_id], |row| row.get(0))?;
99+
100+
for pkg in pkgs {
101+
let pkg = pkg?;
102+
res.push(pkg);
103+
}
104+
105+
Ok(res)
106+
}
107+
}

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: 86 additions & 7 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,19 @@ 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";
19-
13+
#[cfg(not(feature = "ubuntu"))]
2014
type IndexSet<T> = indexmap::IndexSet<T, ahash::RandomState>;
2115

16+
#[cfg(not(feature = "ubuntu"))]
2217
pub fn execute(query: &str) -> Result<i32, OutputError> {
18+
use oma_console::due_to;
19+
use oma_contents::searcher::{pure_search, ripgrep_search, Mode};
20+
use oma_contents::OmaContentsError;
21+
use std::error::Error;
22+
23+
const FILTER_JARO_NUM: u8 = 204;
24+
const APT_LIST_PATH: &str = "/var/lib/apt/lists";
25+
2326
let mut res = IndexSet::with_hasher(ahash::RandomState::new());
2427

2528
let cb = |line| {
@@ -136,6 +139,82 @@ pub fn execute(query: &str) -> Result<i32, OutputError> {
136139
Ok(127)
137140
}
138141

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

0 commit comments

Comments
 (0)