1use std::{
2 collections::HashMap,
3 time::{Instant, SystemTime},
4};
5
6use camino::{Utf8Path, Utf8PathBuf};
7use ecow::EcoString;
8
9use crate::{cli, fs::ProjectIO, http::HttpClient};
10use gleam_core::{
11 Result,
12 analyse::TargetSupport,
13 build::{Codegen, Compile, Mode, Options, Package, Target},
14 config::{DocsPage, PackageConfig},
15 docs::{Dependency, DependencyKind, DocContext},
16 error::Error,
17 hex,
18 io::HttpClient as _,
19 manifest::ManifestPackageSource,
20 paths::ProjectPaths,
21 type_,
22};
23
24pub fn remove(package: String, version: String) -> Result<()> {
25 let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
26 let hex_config = hexpm::Config::new();
27 let api_key =
28 crate::hex::HexAuthentication::new(&runtime, hex_config.clone()).get_or_create_api_key()?;
29 let http = HttpClient::new();
30
31 // Remove docs from API
32 let request = hexpm::remove_docs_request(&package, &version, &api_key, &hex_config)
33 .map_err(Error::hex)?;
34 let response = runtime.block_on(http.send(request))?;
35 hexpm::remove_docs_response(response).map_err(Error::hex)?;
36
37 // Done!
38 println!("The docs for {package} {version} have been removed from HexDocs");
39 Ok(())
40}
41
42#[derive(Debug)]
43pub struct BuildOptions {
44 /// Whether to open the docs after building.
45 pub open: bool,
46 pub target: Option<Target>,
47}
48
49pub fn build(paths: &ProjectPaths, options: BuildOptions) -> Result<()> {
50 let config = crate::config::root_config(paths)?;
51
52 // Reset the build directory so we know the state of the project
53 crate::fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, config.target))?;
54
55 let out = paths.build_documentation_directory(&config.name);
56
57 let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?;
58 let dependencies = manifest
59 .packages
60 .iter()
61 .map(|package| {
62 (
63 package.name.clone(),
64 Dependency {
65 version: package.version.clone(),
66 kind: match &package.source {
67 ManifestPackageSource::Hex { .. } => DependencyKind::Hex,
68 ManifestPackageSource::Git { .. } => DependencyKind::Git,
69 ManifestPackageSource::Local { .. } => DependencyKind::Path,
70 },
71 },
72 )
73 })
74 .collect();
75
76 let mut built = crate::build::main(
77 paths,
78 Options {
79 mode: Mode::Prod,
80 target: options.target,
81 codegen: Codegen::All,
82 compile: Compile::All,
83 warnings_as_errors: false,
84 root_target_support: TargetSupport::Enforced,
85 no_print_progress: false,
86 },
87 manifest,
88 )?;
89 let outputs = build_documentation(
90 paths,
91 &config,
92 dependencies,
93 &mut built.root_package,
94 DocContext::Build,
95 &built.module_interfaces,
96 )?;
97
98 // Write
99 crate::fs::delete_directory(&out)?;
100 crate::fs::write_outputs_under(&outputs, &out)?;
101
102 let index_html = out.join("index.html");
103
104 println!(
105 "\nThe documentation for {package} has been rendered to \n{index_html}",
106 package = config.name,
107 index_html = index_html
108 );
109
110 if options.open {
111 open_docs(&index_html)?;
112 }
113
114 // We're done!
115 Ok(())
116}
117
118/// Opens the indicated path in the default program configured by the system.
119///
120/// For the docs this will generally be a browser (unless some other program is
121/// configured as the default for `.html` files).
122fn open_docs(path: &Utf8Path) -> Result<()> {
123 opener::open(path).map_err(|error| Error::FailedToOpenDocs {
124 path: path.to_path_buf(),
125 error: error.to_string(),
126 })?;
127
128 Ok(())
129}
130
131pub(crate) fn build_documentation(
132 paths: &ProjectPaths,
133 config: &PackageConfig,
134 dependencies: HashMap<EcoString, Dependency>,
135 compiled: &mut Package,
136 is_hex_publish: DocContext,
137 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
138) -> Result<Vec<gleam_core::io::OutputFile>, Error> {
139 compiled.attach_doc_and_module_comments();
140 cli::print_generating_documentation();
141 let mut pages = vec![DocsPage {
142 title: "README".into(),
143 path: "index.html".into(),
144 source: paths.readme(), // TODO: support non markdown READMEs. Or a default if there is none.
145 }];
146 pages.extend(config.documentation.pages.iter().cloned());
147 let mut outputs = gleam_core::docs::generate_html(
148 paths,
149 gleam_core::docs::DocumentationConfig {
150 package_config: config,
151 dependencies,
152 analysed: compiled.modules.as_slice(),
153 docs_pages: &pages,
154 rendering_timestamp: SystemTime::now(),
155 context: is_hex_publish,
156 },
157 ProjectIO::new(),
158 );
159
160 outputs.push(gleam_core::docs::generate_json_package_interface(
161 Utf8PathBuf::from("package-interface.json"),
162 compiled,
163 cached_modules,
164 ));
165 Ok(outputs)
166}
167
168pub fn publish(paths: &ProjectPaths) -> Result<()> {
169 let config = crate::config::root_config(paths)?;
170
171 let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
172 let hex_config = hexpm::Config::new();
173 let api_key =
174 crate::hex::HexAuthentication::new(&runtime, hex_config.clone()).get_or_create_api_key()?;
175
176 // Reset the build directory so we know the state of the project
177 crate::fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, config.target))?;
178
179 let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?;
180 let dependencies = manifest
181 .packages
182 .iter()
183 .map(|package| {
184 (
185 package.name.clone(),
186 Dependency {
187 version: package.version.clone(),
188 kind: match &package.source {
189 ManifestPackageSource::Hex { .. } => DependencyKind::Hex,
190 ManifestPackageSource::Git { .. } => DependencyKind::Git,
191 ManifestPackageSource::Local { .. } => DependencyKind::Path,
192 },
193 },
194 )
195 })
196 .collect();
197
198 let mut built = crate::build::main(
199 paths,
200 Options {
201 root_target_support: TargetSupport::Enforced,
202 warnings_as_errors: false,
203 codegen: Codegen::All,
204 compile: Compile::All,
205 mode: Mode::Prod,
206 target: None,
207 no_print_progress: false,
208 },
209 manifest,
210 )?;
211 let outputs = build_documentation(
212 paths,
213 &config,
214 dependencies,
215 &mut built.root_package,
216 DocContext::HexPublish,
217 &built.module_interfaces,
218 )?;
219 let archive = crate::fs::create_tar_archive(outputs)?;
220
221 let start = Instant::now();
222 cli::print_publishing_documentation();
223 runtime.block_on(hex::publish_documentation(
224 &config.name,
225 &config.version,
226 archive,
227 &api_key,
228 &hex_config,
229 &HttpClient::new(),
230 ))?;
231 cli::print_published(start.elapsed());
232 Ok(())
233}