Skip to main content

lux_cli/
doc.rs

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