1use std::path::Path;
2use std::sync::Arc;
3
4use pulldown_cmark::Event;
5use thiserror::Error;
6
7use crate::{
8 CMarkData, CMarkDataIter, DisallowUrlsWithPrefixError, File, FileFromPathError, Manifest,
9 Package,
10};
11
12#[derive(Clone, Debug, Default, PartialEq)]
14pub struct CMarkReadme<P, M> {
15 data: CMarkData,
16 package_path: P,
17 manifest: M,
18}
19
20impl<'a> CMarkReadme<&'a Path, &'a Manifest> {
21 pub fn from_package(package: &'a Package) -> Result<Self, CMarkReadmeFromPackageError> {
25 let path = package
26 .relative_readme_path()
27 .ok_or(CMarkReadmeFromPackageError::NotFound)?;
28 let file = Arc::new(File::from_path(path.to_path_buf(), Some(package.path()))?);
29 let package_path = package.path();
30 let manifest = package.manifest();
31 Ok(Self::from_file_and_package_path_and_manifest(
32 file,
33 package_path,
34 manifest,
35 ))
36 }
37}
38
39impl CMarkReadme<(), ()> {
40 pub fn from_file(file: Arc<File>) -> Self {
42 Self::from_file_and_package_path_and_manifest(file, (), ())
43 }
44}
45
46impl<'a, P, M> CMarkReadme<P, M> {
47 pub fn with_package_path(self, package_path: &'a Package) -> CMarkReadme<&'a Package, M> {
49 CMarkReadme {
50 data: self.data,
51 package_path,
52 manifest: self.manifest,
53 }
54 }
55
56 pub fn with_manifest(self, manifest: &'a Manifest) -> CMarkReadme<P, &'a Manifest> {
58 CMarkReadme {
59 data: self.data,
60 package_path: self.package_path,
61 manifest,
62 }
63 }
64
65 pub fn from_file_and_package_path_and_manifest(
67 file: Arc<File>,
68 package_path: P,
69 manifest: M,
70 ) -> Self {
71 let data = CMarkData::from_file(file);
72 Self::from_data_and_package_path_and_manifest(data, package_path, manifest)
73 }
74
75 pub fn from_data_and_package_path_and_manifest(
77 data: CMarkData,
78 package_path: P,
79 manifest: M,
80 ) -> Self {
81 Self {
82 data,
83 package_path,
84 manifest,
85 }
86 }
87
88 pub fn data(&self) -> &CMarkData {
90 &self.data
91 }
92
93 pub fn into_data(self) -> CMarkData {
95 self.data
96 }
97
98 pub fn package_path(&self) -> &P {
100 &self.package_path
101 }
102
103 pub fn manifest(&self) -> &M {
105 &self.manifest
106 }
107
108 pub fn iter(&self) -> CMarkDataIter<'_> {
110 self.data.iter()
111 }
112
113 pub fn iter_events(&self) -> impl Iterator<Item = &Event<'_>> {
115 self.data.iter().filter_map(|item| item.event())
116 }
117
118 fn map<F>(mut self, func: F) -> CMarkReadme<P, M>
119 where
120 F: FnOnce(CMarkData) -> CMarkData,
121 {
122 self.data = func(self.data);
123 self
124 }
125
126 fn map_result<F, E>(mut self, func: F) -> Result<CMarkReadme<P, M>, E>
127 where
128 F: FnOnce(CMarkData) -> Result<CMarkData, E>,
129 {
130 self.data = func(self.data)?;
131 Ok(self)
132 }
133
134 pub fn concat_texts(self) -> CMarkReadme<P, M> {
145 self.map(|data| data.concat_texts())
146 }
147
148 pub fn remove_images_only_paragraph<F>(self, predicate: F) -> CMarkReadme<P, M>
151 where
152 F: FnMut(&[&str]) -> bool,
153 {
154 self.map(|data| data.remove_images_only_paragraph(predicate))
155 }
156
157 pub fn remove_badges_paragraph(self) -> CMarkReadme<P, M> {
159 self.map(|data| data.remove_badges_paragraph())
160 }
161
162 pub fn remove_section(self, heading: &str, level: u32) -> Self {
164 self.map(|data| data.remove_section(heading, level))
165 }
166
167 pub fn remove_codeblock_tag(self, tag: &str) -> CMarkReadme<P, M> {
169 self.map(|data| data.remove_codeblock_tag(tag))
170 }
171
172 pub fn remove_codeblock_tags(self, tags: &[&str]) -> CMarkReadme<P, M> {
174 self.map(|data| data.remove_codeblock_tags(tags))
175 }
176
177 pub fn remove_documentation_section(self) -> Self {
179 self.map(|data| data.remove_documentation_section())
180 }
181
182 pub fn disallow_absolute_blob_links(
185 self,
186 repository_url: &str,
187 ) -> Result<CMarkReadme<P, M>, DisallowUrlsWithPrefixError> {
188 self.map_result(|data| data.disallow_absolute_blob_links(repository_url))
189 }
190
191 pub fn use_absolute_blob_urls(self, repository_url: &str) -> CMarkReadme<P, M> {
194 self.map(|data| data.use_absolute_blob_urls(repository_url))
195 }
196}
197
198impl<'a, P> CMarkReadme<P, &'a Manifest> {
199 pub fn disallow_absolute_repository_blob_links(
202 self,
203 ) -> Result<CMarkReadme<P, &'a Manifest>, DisallowAbsoluteRepositoryBlobLinksError> {
204 let repository = self
205 .manifest
206 .package
207 .repository
208 .clone()
209 .ok_or(DisallowAbsoluteRepositoryBlobLinksError::DocsUrlNotFound)?;
210 Ok(self.disallow_absolute_blob_links(&repository)?)
211 }
212
213 pub fn use_absolute_repository_blob_urls(
216 self,
217 ) -> Result<CMarkReadme<P, &'a Manifest>, UseAbsoluteRepositoryBlobUrlsError> {
218 let repository = self
219 .manifest
220 .package
221 .repository
222 .clone()
223 .ok_or(UseAbsoluteRepositoryBlobUrlsError::DocsUrlNotFound)?;
224 Ok(self.use_absolute_blob_urls(&repository))
225 }
226}
227
228#[derive(Debug, Error)]
230pub enum CMarkReadmeFromPackageError {
231 #[error(transparent)]
233 FileError(#[from] FileFromPathError),
234 #[error("CMarkReadme not found.")]
236 NotFound,
237}
238
239#[derive(Clone, Debug, Error)]
241pub enum DisallowAbsoluteRepositoryBlobLinksError {
242 #[error(transparent)]
244 DisallowUrlsWithPrefixError(#[from] DisallowUrlsWithPrefixError),
245 #[error("Manifest does not contain package.documentation field")]
247 DocsUrlNotFound,
248}
249
250#[derive(Clone, Copy, Debug, Error)]
253pub enum UseAbsoluteRepositoryBlobUrlsError {
254 #[error("Manifest does not contain package.documentation field")]
255 DocsUrlNotFound,
256}