1use clap::Args;
2use eyre::{eyre, 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 #[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 .expect("malformed 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 Some(
79 path.file_name()
80 .expect("no file name")
81 .to_string_lossy()
82 .to_string(),
83 )
84 } else {
85 None
86 }
87 })
88 .try_collect()?;
89 if files.is_empty() {
90 match get_homepage(&pkg, tree)? {
91 None => Err(eyre!(
92 "No documentation found for package {}",
93 pkg.into_package_spec()
94 )),
95 Some(homepage) => {
96 if Confirm::new("No local documentation found. Open homepage?")
97 .with_default(false)
98 .prompt()
99 .expect("Error prompting to open homepage")
100 {
101 open::that(homepage.to_string())?;
102 }
103 Ok(())
104 }
105 }
106 } else if files.len() == 1 {
107 edit::edit_file(layout.doc.join(files.first().unwrap()))?;
108 Ok(())
109 } else {
110 let file = Select::new(
111 "Multiple documentation files found. Please select one to open.",
112 files,
113 )
114 .prompt()
115 .expect("error selecting from multiple files");
116 edit::edit_file(layout.doc.join(file))?;
117 Ok(())
118 }
119}