1use clap::Args;
2use eyre::{eyre, Context, OptionExt, Result};
3use inquire::{Confirm, Select};
4use itertools::Itertools;
5use lux_lib::{
6 config::Config,
7 lockfile::LocalPackage,
8 lua_rockspec::RemoteLuaRockspec,
9 lua_version::LuaVersion,
10 package::PackageReq,
11 rockspec::Rockspec,
12 tree::{RockMatches, Tree},
13};
14use url::Url;
15use walkdir::WalkDir;
16
17#[derive(Args)]
18pub struct Doc {
19 package: PackageReq,
20
21 #[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(eyre!("No package matching {} found.", package_req))
31 }
32 RockMatches::Many(_package_ids) => Err(eyre!(
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_eyre("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).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(eyre!(
57 "Package {} does not have a homepage in its RockSpec.",
58 pkg.into_package_spec()
59 )),
60 }?;
61 open::that(homepage.to_string())?;
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())?;
68 let rockspec = RemoteLuaRockspec::new(&rockspec_content)?;
69 Ok(rockspec.description().homepage.clone())
70}
71
72async fn open_local_docs(pkg: LocalPackage, tree: &Tree) -> 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 match files.first() {
87 Some(file) if files.len() == 1 => {
88 edit::edit_file(layout.doc.join(file))?;
89 Ok(())
90 }
91 Some(_) => {
92 let file = Select::new(
93 "Multiple documentation files found. Please select one to open.",
94 files,
95 )
96 .prompt()
97 .wrap_err("error selecting from multiple files")?;
98 edit::edit_file(layout.doc.join(file))?;
99 Ok(())
100 }
101 None => match get_homepage(&pkg, tree)? {
102 None => Err(eyre!(
103 "No documentation found for package {}",
104 pkg.into_package_spec()
105 )),
106 Some(homepage) => {
107 if Confirm::new("No local documentation found. Open homepage?")
108 .with_default(false)
109 .prompt()
110 .wrap_err("error prompting to open homepage")?
111 {
112 open::that(homepage.to_string())?;
113 }
114 Ok(())
115 }
116 },
117 }
118}