Skip to main content

lux_cli/
doc.rs

1use clap::Args;
2use inquire::{Confirm, Select};
3use itertools::Itertools;
4use lux_lib::{
5    config::Config,
6    lockfile::LocalPackage,
7    lua_rockspec::RemoteLuaRockspec,
8    lua_version::LuaVersion,
9    package::PackageReq,
10    rockspec::Rockspec,
11    tree::{InstallTree, RockMatches, Tree},
12};
13use miette::{miette, Context, IntoDiagnostic, Result};
14use url::Url;
15use walkdir::WalkDir;
16
17#[derive(Args)]
18pub struct Doc {
19    package: PackageReq,
20
21    /// Ignore local docs and open the package's homepage in a browser.
22    #[arg(long)]
23    online: bool,
24}
25
26pub async fn doc(args: Doc, config: Config) -> Result<()> {
27    let tree = config.user_tree(LuaVersion::from(&config)?.clone())?;
28    let package_id = match tree.match_rocks(&args.package)? {
29        RockMatches::NotFound(package_req) => {
30            Err(miette!("No package matching {} found.", package_req))
31        }
32        RockMatches::Many(_package_ids) => Err(miette!(
33            "
34Found multiple packages matching {}.
35Please specify an exact package (<name>@<version>) or narrow the version requirement.
36",
37            &args.package
38        )),
39        RockMatches::Single(package_id) => Ok(package_id),
40    }?;
41    let lockfile = tree.lockfile()?;
42    let pkg = lockfile
43        .get(&package_id)
44        .ok_or_else(|| miette!("package is installed, but not found in the lockfile"))?
45        .clone();
46    if args.online {
47        open_homepage(pkg, &tree).await
48    } else {
49        open_local_docs(pkg, &tree, &config).await
50    }
51}
52
53async fn open_homepage(pkg: LocalPackage, tree: &Tree) -> Result<()> {
54    let homepage = match get_homepage(&pkg, tree)? {
55        Some(homepage) => Ok(homepage),
56        None => Err(miette!(
57            "Package {} does not have a homepage in its RockSpec.",
58            pkg.into_package_spec()
59        )),
60    }?;
61    open::that(homepage.to_string()).into_diagnostic()?;
62    Ok(())
63}
64
65fn get_homepage(pkg: &LocalPackage, tree: &Tree) -> Result<Option<Url>> {
66    let layout = tree.installed_rock_layout(pkg)?;
67    let rockspec_content = std::fs::read_to_string(layout.rockspec_path()).into_diagnostic()?;
68    let rockspec = RemoteLuaRockspec::new(&rockspec_content)?;
69    Ok(rockspec.description().homepage.clone())
70}
71
72async fn open_local_docs(pkg: LocalPackage, tree: &Tree, config: &Config) -> Result<()> {
73    let layout = tree.installed_rock_layout(&pkg)?;
74    let files: Vec<String> = WalkDir::new(&layout.doc)
75        .into_iter()
76        .filter_map_ok(|file| {
77            let path = file.into_path();
78            if path.is_file() {
79                path.file_name()
80                    .map(|file_name| file_name.to_string_lossy().to_string())
81            } else {
82                None
83            }
84        })
85        .try_collect()
86        .into_diagnostic()?;
87    match files.first() {
88        Some(file) if files.len() == 1 => {
89            edit::edit_file(layout.doc.join(file)).into_diagnostic()?;
90            Ok(())
91        }
92        Some(_) => {
93            let file = Select::new(
94                "Multiple documentation files found. Please select one to open.",
95                files,
96            )
97            .prompt()
98            .into_diagnostic()
99            .wrap_err("error selecting from multiple files")?;
100            edit::edit_file(layout.doc.join(file)).into_diagnostic()?;
101            Ok(())
102        }
103        None => match get_homepage(&pkg, tree)? {
104            None => Err(miette!(
105                "No documentation found for package {}",
106                pkg.into_package_spec()
107            )),
108            Some(homepage) => {
109                if config.no_prompt() {
110                    return Err(miette!(
111                        "No local documentation found for package {}",
112                        pkg.into_package_spec()
113                    ));
114                } else if Confirm::new("No local documentation found. Open homepage?")
115                    .with_default(false)
116                    .prompt()
117                    .into_diagnostic()
118                    .wrap_err("error prompting to open homepage")?
119                {
120                    open::that(homepage.to_string()).into_diagnostic()?;
121                }
122                Ok(())
123            }
124        },
125    }
126}