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 {package_req} found"))
31        }
32        RockMatches::Many(_package_ids) => Err(miette!(
33            help = "specify an exact package (<name>@<version>) or narrow the version requirement",
34            "found multiple packages matching {}",
35            &args.package
36        )),
37        RockMatches::Single(package_id) => Ok(package_id),
38    }?;
39    let lockfile = tree.lockfile()?;
40    let pkg = lockfile
41        .get(&package_id)
42        .ok_or_else(|| miette!("package is installed, but not found in the lockfile"))?
43        .clone();
44    if args.online {
45        open_homepage(pkg, &tree).await
46    } else {
47        open_local_docs(pkg, &tree, &config).await
48    }
49}
50
51async fn open_homepage(pkg: LocalPackage, tree: &Tree) -> Result<()> {
52    let homepage = match get_homepage(&pkg, tree)? {
53        Some(homepage) => Ok(homepage),
54        None => Err(miette!(
55            "package {} does not have a homepage in its RockSpec",
56            pkg.into_package_spec()
57        )),
58    }?;
59    open::that(homepage.to_string()).into_diagnostic()?;
60    Ok(())
61}
62
63fn get_homepage(pkg: &LocalPackage, tree: &Tree) -> Result<Option<Url>> {
64    let layout = tree.installed_rock_layout(pkg)?;
65    let rockspec_content = std::fs::read_to_string(layout.rockspec_path()).into_diagnostic()?;
66    let rockspec = RemoteLuaRockspec::new(&rockspec_content)?;
67    Ok(rockspec.description().homepage.clone())
68}
69
70async fn open_local_docs(pkg: LocalPackage, tree: &Tree, config: &Config) -> Result<()> {
71    let layout = tree.installed_rock_layout(&pkg)?;
72    let files: Vec<String> = WalkDir::new(&layout.doc)
73        .into_iter()
74        .filter_map_ok(|file| {
75            let path = file.into_path();
76            if path.is_file() {
77                path.file_name()
78                    .map(|file_name| file_name.to_string_lossy().to_string())
79            } else {
80                None
81            }
82        })
83        .try_collect()
84        .into_diagnostic()?;
85    match files.first() {
86        Some(file) if files.len() == 1 => {
87            edit::edit_file(layout.doc.join(file)).into_diagnostic()?;
88            Ok(())
89        }
90        Some(_) => {
91            let file = Select::new(
92                "Multiple documentation files found. Please select one to open.",
93                files,
94            )
95            .prompt()
96            .into_diagnostic()
97            .wrap_err("error selecting from multiple files")?;
98            edit::edit_file(layout.doc.join(file)).into_diagnostic()?;
99            Ok(())
100        }
101        None => match get_homepage(&pkg, tree)? {
102            None => Err(miette!(
103                "no documentation found for package '{}'",
104                pkg.into_package_spec()
105            )),
106            Some(homepage) => {
107                if config.no_prompt() {
108                    return Err(miette!(
109                        "no local documentation found for package '{}'",
110                        pkg.into_package_spec()
111                    ));
112                } else if Confirm::new("No local documentation found. Open homepage?")
113                    .with_default(false)
114                    .prompt()
115                    .into_diagnostic()
116                    .wrap_err("error prompting to open homepage")?
117                {
118                    open::that(homepage.to_string()).into_diagnostic()?;
119                }
120                Ok(())
121            }
122        },
123    }
124}