Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita50d276

Browse files
Dan gitbook fix 1 (#1247)
1 parentf6eda7a commita50d276

File tree

4 files changed

+272
-119
lines changed

4 files changed

+272
-119
lines changed

‎pgml-dashboard/src/api/cms.rs

Lines changed: 174 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,94 @@ use crate::{
1919
utils::config,
2020
};
2121

22+
use serde::{Deserialize,Serialize};
23+
2224
lazy_static!{
2325
static refBLOG:Collection =Collection::new("Blog",true);
2426
static refCAREERS:Collection =Collection::new("Careers",true);
2527
static refDOCS:Collection =Collection::new("Docs",false);
2628
}
2729

30+
#[derive(Debug,Serialize,Deserialize)]
31+
pubstructDocument{
32+
/// The absolute path on disk
33+
pubpath:PathBuf,
34+
pubdescription:Option<String>,
35+
pubimage:Option<String>,
36+
pubtitle:String,
37+
pubtoc_links:Vec<TocLink>,
38+
pubhtml:String,
39+
}
40+
41+
implDocument{
42+
pubasyncfnfrom_path(path:&PathBuf) -> anyhow::Result<Document>{
43+
let contents = tokio::fs::read_to_string(&path).await?;
44+
45+
let parts = contents.split("---").collect::<Vec<&str>>();
46+
47+
let(description, contents) =if parts.len() >1{
48+
matchYamlLoader::load_from_str(parts[1]){
49+
Ok(meta) =>{
50+
if meta.len() ==0 || meta[0].as_hash().is_none(){
51+
(None, contents)
52+
}else{
53+
let description:Option<String> =match meta[0]["description"].is_badvalue()
54+
{
55+
true =>None,
56+
false =>Some(meta[0]["description"].as_str().unwrap().to_string()),
57+
};
58+
(description, parts[2..].join("---").to_string())
59+
}
60+
}
61+
Err(_) =>(None, contents),
62+
}
63+
}else{
64+
(None, contents)
65+
};
66+
67+
// Parse Markdown
68+
let arena =Arena::new();
69+
let spaced_contents =crate::utils::markdown::gitbook_preprocess(&contents);
70+
let root =parse_document(&arena,&spaced_contents,&crate::utils::markdown::options());
71+
72+
// Title of the document is the first (and typically only) <h1>
73+
let title =crate::utils::markdown::get_title(root).unwrap();
74+
let toc_links =crate::utils::markdown::get_toc(root).unwrap();
75+
let image =crate::utils::markdown::get_image(root);
76+
crate::utils::markdown::wrap_tables(root,&arena).unwrap();
77+
78+
// MkDocs, gitbook syntax support, e.g. tabs, notes, alerts, etc.
79+
crate::utils::markdown::mkdocs(root,&arena).unwrap();
80+
81+
// Style headings like we like them
82+
letmut plugins =ComrakPlugins::default();
83+
let headings =crate::utils::markdown::MarkdownHeadings::new();
84+
plugins.render.heading_adapter =Some(&headings);
85+
plugins.render.codefence_syntax_highlighter =
86+
Some(&crate::utils::markdown::SyntaxHighlighter{});
87+
88+
letmut html =vec![];
89+
format_html_with_plugins(
90+
root,
91+
&crate::utils::markdown::options(),
92+
&mut html,
93+
&plugins,
94+
)
95+
.unwrap();
96+
let html =String::from_utf8(html).unwrap();
97+
98+
let document =Document{
99+
path: path.to_owned(),
100+
description,
101+
image,
102+
title,
103+
toc_links,
104+
html,
105+
};
106+
Ok(document)
107+
}
108+
}
109+
28110
/// A Gitbook collection of documents
29111
#[derive(Default)]
30112
structCollection{
@@ -62,6 +144,7 @@ impl Collection {
62144

63145
pubasyncfnget_asset(&self,path:&str) ->Option<NamedFile>{
64146
info!("get_asset: {} {path}",self.name);
147+
65148
NamedFile::open(self.asset_dir.join(path)).await.ok()
66149
}
67150

@@ -79,7 +162,7 @@ impl Collection {
79162

80163
let path =self.root_dir.join(format!("{}.md", path.to_string_lossy()));
81164

82-
self.render(&path, cluster,self).await
165+
self.render(&path, cluster).await
83166
}
84167

85168
/// Create an index of the Collection based on the SUMMARY.md from Gitbook.
@@ -173,109 +256,35 @@ impl Collection {
173256
Ok(links)
174257
}
175258

176-
asyncfnrender<'a>(
177-
&self,
178-
path:&'aPathBuf,
179-
cluster:&Cluster,
180-
collection:&Collection,
181-
) ->Result<ResponseOk,Status>{
182-
// Read to string0
183-
let contents =match tokio::fs::read_to_string(&path).await{
184-
Ok(contents) =>{
185-
info!("loading markdown file: '{:?}", path);
186-
contents
187-
}
188-
Err(err) =>{
189-
warn!("Error parsing markdown file: '{:?}' {:?}", path, err);
190-
returnErr(Status::NotFound);
191-
}
192-
};
193-
let parts = contents.split("---").collect::<Vec<&str>>();
194-
let(description, contents) =if parts.len() >1{
195-
matchYamlLoader::load_from_str(parts[1]){
196-
Ok(meta) =>{
197-
if !meta.is_empty(){
198-
let meta = meta[0].clone();
199-
if meta.as_hash().is_none(){
200-
(None, contents.to_string())
201-
}else{
202-
let description:Option<String> =match meta["description"]
203-
.is_badvalue()
204-
{
205-
true =>None,
206-
false =>Some(meta["description"].as_str().unwrap().to_string()),
207-
};
208-
209-
(description, parts[2..].join("---").to_string())
210-
}
211-
}else{
212-
(None, contents.to_string())
213-
}
214-
}
215-
Err(_) =>(None, contents.to_string()),
216-
}
217-
}else{
218-
(None, contents.to_string())
219-
};
220-
221-
// Parse Markdown
222-
let arena =Arena::new();
223-
let root =parse_document(&arena,&contents,&crate::utils::markdown::options());
224-
225-
// Title of the document is the first (and typically only) <h1>
226-
let title =crate::utils::markdown::get_title(root).unwrap();
227-
let toc_links =crate::utils::markdown::get_toc(root).unwrap();
228-
let image =crate::utils::markdown::get_image(root);
229-
crate::utils::markdown::wrap_tables(root,&arena).unwrap();
230-
231-
// MkDocs syntax support, e.g. tabs, notes, alerts, etc.
232-
crate::utils::markdown::mkdocs(root,&arena).unwrap();
233-
234-
// Style headings like we like them
235-
letmut plugins =ComrakPlugins::default();
236-
let headings =crate::utils::markdown::MarkdownHeadings::new();
237-
plugins.render.heading_adapter =Some(&headings);
238-
plugins.render.codefence_syntax_highlighter =
239-
Some(&crate::utils::markdown::SyntaxHighlighter{});
240-
241-
// Render
242-
letmut html =vec![];
243-
format_html_with_plugins(
244-
root,
245-
&crate::utils::markdown::options(),
246-
&mut html,
247-
&plugins,
248-
)
249-
.unwrap();
250-
let html =String::from_utf8(html).unwrap();
251-
252-
// Handle navigation
253-
// TODO organize this functionality in the collection to cleanup
254-
let index:Vec<IndexLink> =self
255-
.index
259+
// Sets specified index as currently viewed.
260+
fnopen_index(&self,path:PathBuf) ->Vec<IndexLink>{
261+
self.index
256262
.clone()
257263
.iter_mut()
258264
.map(|nav_link|{
259265
letmut nav_link = nav_link.clone();
260-
nav_link.should_open(path);
266+
nav_link.should_open(&path);
261267
nav_link
262268
})
263-
.collect();
269+
.collect()
270+
}
271+
272+
// renders document in layout
273+
asyncfnrender<'a>(&self,path:&'aPathBuf,cluster:&Cluster) ->Result<ResponseOk,Status>{
274+
let doc =Document::from_path(&path).await.unwrap();
275+
let index =self.open_index(doc.path);
264276

265277
let user =if cluster.context.user.is_anonymous(){
266278
None
267279
}else{
268280
Some(cluster.context.user.clone())
269281
};
270282

271-
letmut layout =crate::templates::Layout::new(&title,Some(cluster));
272-
ifletSome(image) = image{
273-
// translate relative url into absolute for head social sharing
274-
let parts = image.split(".gitbook/assets/").collect::<Vec<&str>>();
275-
let image_path = collection.url_root.join(".gitbook/assets").join(parts[1]);
276-
layout.image(config::asset_url(image_path.to_string_lossy()).as_ref());
283+
letmut layout =crate::templates::Layout::new(&doc.title,Some(cluster));
284+
ifletSome(image) = doc.image{
285+
layout.image(&config::asset_url(image.into()));
277286
}
278-
ifletSome(description) =&description{
287+
ifletSome(description) =&doc.description{
279288
layout.description(description);
280289
}
281290
ifletSome(user) =&user{
@@ -285,11 +294,11 @@ impl Collection {
285294
let layout = layout
286295
.nav_title(&self.name)
287296
.nav_links(&index)
288-
.toc_links(&toc_links)
297+
.toc_links(&doc.toc_links)
289298
.footer(cluster.context.marketing_footer.to_string());
290299

291300
Ok(ResponseOk(
292-
layout.render(crate::templates::Article{content: html}),
301+
layout.render(crate::templates::Article{content:doc.html}),
293302
))
294303
}
295304
}
@@ -365,6 +374,10 @@ pub fn routes() -> Vec<Route> {
365374
mod test{
366375
usesuper::*;
367376
usecrate::utils::markdown::{options,MarkdownHeadings,SyntaxHighlighter};
377+
use regex::Regex;
378+
use rocket::http::{ContentType,Cookie,Status};
379+
use rocket::local::asynchronous::Client;
380+
use rocket::{Build,Rocket};
368381

369382
#[test]
370383
fntest_syntax_highlighting(){
@@ -452,4 +465,73 @@ This is the end of the markdown
452465
!html.contains(r#"<div class="overflow-auto w-100">"#) || !html.contains(r#"</div>"#)
453466
);
454467
}
468+
469+
asyncfnrocket() ->Rocket<Build>{
470+
dotenv::dotenv().ok();
471+
rocket::build()
472+
.manage(crate::utils::markdown::SearchIndex::open().unwrap())
473+
.mount("/",crate::api::cms::routes())
474+
}
475+
476+
fngitbook_test(html:String) ->Option<String>{
477+
// all gitbook expresions should be removed, this catches {% %} nonsupported expressions.
478+
let re =Regex::new(r"[{][%][^{]*[%][}]").unwrap();
479+
let rsp = re.find(&html);
480+
if rsp.is_some(){
481+
returnSome(rsp.unwrap().as_str().to_string());
482+
}
483+
484+
// gitbook TeX block not supported yet
485+
let re =Regex::new(r"(\$\$).*(\$\$)").unwrap();
486+
let rsp = re.find(&html);
487+
if rsp.is_some(){
488+
returnSome(rsp.unwrap().as_str().to_string());
489+
}
490+
491+
None
492+
}
493+
494+
// Ensure blogs render and there are no unparsed gitbook components.
495+
#[sqlx::test]
496+
asyncfnrender_blogs_test(){
497+
let client =Client::tracked(rocket().await).await.unwrap();
498+
let blog:Collection =Collection::new("Blog",true);
499+
500+
for pathin blog.index{
501+
let req = client.get(path.clone().href);
502+
let rsp = req.dispatch().await;
503+
let body = rsp.into_string().await.unwrap();
504+
505+
let test =gitbook_test(body);
506+
507+
assert!(
508+
test.is_none(),
509+
"bad html parse in {:?}. This feature is not supported {:?}",
510+
path.href,
511+
test.unwrap()
512+
)
513+
}
514+
}
515+
516+
// Ensure Docs render and ther are no unparsed gitbook compnents.
517+
#[sqlx::test]
518+
asyncfnrender_guides_test(){
519+
let client =Client::tracked(rocket().await).await.unwrap();
520+
let docs:Collection =Collection::new("Docs",true);
521+
522+
for pathin docs.index{
523+
let req = client.get(path.clone().href);
524+
let rsp = req.dispatch().await;
525+
let body = rsp.into_string().await.unwrap();
526+
527+
let test =gitbook_test(body);
528+
529+
assert!(
530+
test.is_none(),
531+
"bad html parse in {:?}. This feature is not supported {:?}",
532+
path.href,
533+
test.unwrap()
534+
)
535+
}
536+
}
455537
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp