crate_paths_cli_core/backend/local/
mod.rs

1pub mod error;
2
3use super::BackendError;
4use super::LocalBackendError;
5use crate::error::CoreError;
6use crate::item::ItemEntry;
7use crate::parser;
8use inflector::Inflector as _;
9use std::path::PathBuf;
10use std::process::Command;
11
12fn fetch_crate_all_items_html(crate_name: &str) -> Result<String, LocalBackendError> {
13    let cargo_status = Command::new("cargo")
14        .args(["doc", "--workspace"])
15        .status()
16        .map_err(LocalBackendError::CargoDocExecution)?;
17
18    if !cargo_status.success() {
19        return Err(LocalBackendError::CargoDocStatus(
20            cargo_status.code().unwrap_or(1),
21        ));
22    }
23
24    let locate_project_output = Command::new("cargo")
25        .args(["locate-project", "--workspace", "--message-format=plain"])
26        .output()
27        .map_err(LocalBackendError::CargoLocateProjectExecution)?;
28
29    if !locate_project_output.status.success() {
30        return Err(LocalBackendError::CargoLocateProjectStatus(
31            locate_project_output.status.code().unwrap_or(1),
32        ));
33    }
34
35    let cargo_toml_path_str = String::from_utf8(locate_project_output.stdout)
36        .map_err(LocalBackendError::CargoLocateProjectOutput)?;
37
38    let cargo_toml_path = PathBuf::from(cargo_toml_path_str.trim());
39
40    let workspace_root = cargo_toml_path
41        .parent()
42        .ok_or_else(|| LocalBackendError::WorkspaceRootNotFound(cargo_toml_path.clone()))?;
43
44    let base_doc_path = workspace_root.join("target").join("doc");
45    let possible_names = [crate_name, &crate_name.to_snake_case()];
46
47    const ALL_HTML: &str = "all.html";
48
49    let doc_file_path = possible_names
50        .iter()
51        .map(|name| base_doc_path.join(name).join(ALL_HTML))
52        .find(|path| path.exists())
53        .ok_or_else(|| {
54            LocalBackendError::DocFileNotFound(base_doc_path.join(crate_name).join(ALL_HTML))
55        })?;
56
57    std::fs::read_to_string(&doc_file_path)
58        .map_err(|e| LocalBackendError::FileRead(doc_file_path.clone(), e))
59}
60
61pub fn process(crate_name: &str) -> Result<Vec<ItemEntry>, CoreError> {
62    let html_content = fetch_crate_all_items_html(crate_name)
63        .map_err(BackendError::from)
64        .map_err(CoreError::from)?;
65    parser::parse_html_to_items(crate_name, &html_content).map_err(CoreError::from)
66}
67
68pub fn get_crate_version(crate_name: &str) -> Option<String> {
69    let output = Command::new("cargo")
70        .args(["metadata", "--format-version", "1"])
71        .output()
72        .ok()?;
73
74    if !output.status.success() {
75        return None;
76    }
77
78    let metadata: serde_json::Value = serde_json::from_slice(&output.stdout).ok()?;
79    let packages = metadata.get("packages")?.as_array()?;
80
81    for package in packages {
82        // error[E0658]: `let` expressions in this position are unstable
83        #[allow(clippy::collapsible_if)]
84        if let Some(name) = package.get("name")?.as_str() {
85            if name == crate_name {
86                return package.get("version")?.as_str().map(|s| s.to_string());
87            }
88        }
89    }
90
91    None
92}