diff --git a/Cargo.toml b/Cargo.toml index 45117db..00be5d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mdbook-angular" version = "0.5.0" -edition = "2021" +edition = "2024" authors = ["Bram Gotink "] license = "EUPL-1.2" description = "mdbook renderer to run angular code samples" diff --git a/README.md b/README.md index b204be0..eb81857 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,38 @@ polyfills = [] None of these settings are required, the default values are shown in the code above. +### Custom Template + +The default template can be found in a file `src/default_template.hbs` in the source repository. +You can provide your own template by adding a `angular-playground.hbs` file in your book's `theme` folder. + +Inside the template you have access to the following data: + +- `code` is the code to print, if the playground should show code +- `flags` are the flags present on the code block, take not of `flags.collapsed` especially +- `inputs` is an array of objects: + - `name` is the name of the input + - `description` is its description, possibly empty + - `value` is a HTML block to render an input element so the reader can change the value + - `extra` is extra info, see below +- `actions` is an array of objects: + - `description` is its description, possibly empty + - `action` is a HTML block to render a button so the reader can trigger the action + - `extra` is extra info, see below + +Both actions and inputs can provide extra info. That's a JSON object provided in a comment on the action or input using the `@extra` tag. +As example: the code block below would make `{{ extra.lorem }}` have value `ipsum` for the input named `myInput`. + +```ts +class MyPlayground { + /** + * This is the description + * @extra {"lorem": "ipsum"} + */ + myInput = signal('default value'); +} +``` + ## Development This project requires mdbook and angular to be installed diff --git a/src/angular/builder/background.rs b/src/angular/builder/background.rs index 70497a3..fddf7f3 100644 --- a/src/angular/builder/background.rs +++ b/src/angular/builder/background.rs @@ -7,7 +7,7 @@ pub(crate) use utils::stop as stop_background_process; use crate::{ChapterWithCodeBlocks, Config, Result}; -use super::{default::write_angular_workspace, Writer}; +use super::{Writer, default::write_angular_workspace}; pub(super) fn build(config: &Config, chapters: Vec) -> Result<()> { let root = &config.angular_root_folder; diff --git a/src/angular/builder/background/utils.rs b/src/angular/builder/background/utils.rs index 8dfa653..88326bb 100644 --- a/src/angular/builder/background/utils.rs +++ b/src/angular/builder/background/utils.rs @@ -13,14 +13,15 @@ use std::{ io::{self, Read}, os::unix::process::CommandExt, path::{Path, PathBuf}, - process::{self, exit, Command}, + process::{self, Command, exit}, }; use log::{debug, error, info}; use crate::{ + Config, Context, Result, angular::builder::utils::{ANGULAR_CLI_CMD, PROJECT_NAME, TARGET_NAME}, - bail, Config, Context, Result, + bail, }; fn open_pid_file( diff --git a/src/angular/builder/default.rs b/src/angular/builder/default.rs index a5cd895..d2d3d1e 100644 --- a/src/angular/builder/default.rs +++ b/src/angular/builder/default.rs @@ -1,11 +1,11 @@ use std::{fs, path::PathBuf}; use pathdiff::diff_paths; -use serde_json::{json, Value}; +use serde_json::{Value, json}; -use crate::{angular::AngularWorkspace, ChapterWithCodeBlocks, Config, Error, Result}; +use crate::{ChapterWithCodeBlocks, Config, Error, Result, angular::AngularWorkspace}; -use super::{ng_build, utils::PROJECT_NAME, utils::TARGET_NAME, Writer}; +use super::{Writer, ng_build, utils::PROJECT_NAME, utils::TARGET_NAME}; pub(super) const BUILDER_NAME: &str = "@angular/build:application"; pub(super) const MAIN_FILENAME: &str = "load-angular.ts"; @@ -114,7 +114,7 @@ fn replace_load_angular_script_path(config: &Config, chapters: Vec) -> }) .ok_or_else(|| Error::msg("Failed to find main file in stats.json"))?; - let main_file = format!("browser/{main_file}",); + let main_file = format!("browser/{main_file}"); for chapter_path in chapters { let mut chapter_path = config.target_folder.join(chapter_path); diff --git a/src/angular/builder/writer.rs b/src/angular/builder/writer.rs index e3d0897..585154e 100644 --- a/src/angular/builder/writer.rs +++ b/src/angular/builder/writer.rs @@ -2,7 +2,7 @@ use std::{fs, path::Path}; use serde_json::json; -use crate::{codeblock::CodeBlock, Config, Context, Result}; +use crate::{Config, Context, Result, markdown::CollectedCodeBlock}; pub(super) struct Writer<'a> { changed_only: bool, @@ -45,7 +45,7 @@ impl Writer<'_> { root: P, index: usize, chapter_path: &Path, - code_blocks: Vec, + code_blocks: Vec, ) -> Result<()> { let root = root.as_ref(); let project_folder = format!("code_{index}"); @@ -85,7 +85,8 @@ impl Writer<'_> { ); } - for (code_block_index, code_block) in code_blocks.into_iter().enumerate() { + for code_block in code_blocks { + let code_block_index = code_block.index; self.write( absolute_project_folder.join(format!("codeblock_{code_block_index}.ts")), &code_block.code_to_run, @@ -97,13 +98,13 @@ impl Writer<'_> { import {{{} as CodeBlock_{code_block_index}}} from './codeblock_{code_block_index}.js';\n\ applications.push(bootstrapApplication(CodeBlock_{code_block_index}, {{providers: makeProviders(CodeBlock_{code_block_index})}}));\n\ ", - &code_block.class_name + code_block.class_name )); } let script_basename = project_folder.clone(); - let angular_main = format!("./{}/{}", &project_folder, &script_basename); + let angular_main = format!("./{project_folder}/{script_basename}"); self.write( root.join(format!("{angular_main}.ts")), &main_script.join("\n"), diff --git a/src/codeblock/mod.rs b/src/codeblock/mod.rs index 464ed64..21618e2 100644 --- a/src/codeblock/mod.rs +++ b/src/codeblock/mod.rs @@ -11,7 +11,7 @@ use crate::{Config, Result}; use self::{ flags::get_flags, - parser::{parse_codeblock, ParsedCodeBlock}, + parser::{ParsedCodeBlock, parse_codeblock}, }; pub(crate) fn is_angular_codeblock(language: &str) -> bool { diff --git a/src/codeblock/parser.rs b/src/codeblock/parser.rs index 4d595ff..1f7bc82 100644 --- a/src/codeblock/parser.rs +++ b/src/codeblock/parser.rs @@ -5,20 +5,20 @@ use log::debug; use regex::Regex; use swc_core::{ common::{ + BytePos, FileName, SourceFile, Span, Spanned, comments::SingleThreadedComments, - errors::{Handler, HANDLER}, + errors::{HANDLER, Handler}, source_map::SmallPos, - BytePos, FileName, SourceFile, Span, Spanned, }, ecma::{ - ast::{self, EsVersion}, + ast::{self, EsVersion, Lit}, parser::{self, Syntax, TsSyntax}, }, }; -use crate::{utils::swc::get_decorator, Error, Result}; +use crate::{Error, Result, utils::swc::get_decorator}; -use super::playground::{parse_playground, Playground}; +use super::playground::{Playground, parse_playground}; static TS_EXT: LazyLock = LazyLock::new(|| Regex::new(r"\.([cm]?)ts(x?)$").unwrap()); static START_OF_FILE: BytePos = BytePos(1); @@ -60,10 +60,7 @@ impl CodeBlockVisitor { }); if let Some(selector) = selector { - let selector = selector.value.as_lit().and_then(|lit| match lit { - ast::Lit::Str(ref selector) => Some(&selector.value), - _ => None, - }); + let selector = selector.value.as_lit().and_then(Lit::as_str); let Some(selector) = selector else { return Err(Error::msg(format!( @@ -71,7 +68,7 @@ impl CodeBlockVisitor { ))); }; - let Some(selector) = selector.as_str() else { + let Some(selector) = selector.value.as_str() else { return Err(Error::msg(format!( "Selector is not a valid string in class {name}" ))); @@ -124,10 +121,10 @@ impl CodeBlockVisitor { } fn visit_exported_class(&mut self, name: &str, node: &ast::Class) -> Result<()> { - if let Some(expected_name) = &self.class_name { - if name.ne(expected_name) { - return Ok(()); - } + if let Some(expected_name) = &self.class_name + && name != expected_name + { + return Ok(()); } debug!("Visiting class {name}"); diff --git a/src/codeblock/playground/evaluate_expression.rs b/src/codeblock/playground/evaluate_expression.rs index 39a6be1..3603524 100644 --- a/src/codeblock/playground/evaluate_expression.rs +++ b/src/codeblock/playground/evaluate_expression.rs @@ -218,14 +218,16 @@ fn i64_i64_to_i64_operator( where F: Fn(i64, i64) -> i64, { - if let (Some(Value::Number(left)), Some(Value::Number(right))) = - (left.get_default(), right.get_default()) + if let Some(left) = left + .get_default() + .and_then(Value::as_number) + .and_then(Number::as_i64) + && let Some(right) = right + .get_default() + .and_then(Value::as_number) + .and_then(Number::as_i64) { - if let Some(left) = left.as_i64() { - if let Some(right) = right.as_i64() { - return PlaygroundInputConfig::from_default(f(left, right)); - } - } + return PlaygroundInputConfig::from_default(f(left, right)); } PlaygroundInputConfig::number() diff --git a/src/codeblock/playground/parser.rs b/src/codeblock/playground/parser.rs index 807cfaf..fc37a82 100644 --- a/src/codeblock/playground/parser.rs +++ b/src/codeblock/playground/parser.rs @@ -1,8 +1,9 @@ use swc_core::{common::comments, ecma::ast}; use crate::{ - utils::swc::{clean_comment, get_decorator}, Result, + codeblock::playground::ExtraValues, + utils::swc::{clean_comment, get_decorator}, }; use super::{ @@ -18,7 +19,7 @@ pub(crate) fn parse_playground( comments: &C, ) -> Result> { let inputs = extract_inputs(node, comments)?; - let actions = extract_actions(node, comments); + let actions = extract_actions(node, comments)?; if actions.is_empty() && inputs.is_empty() { Ok(None) @@ -77,15 +78,45 @@ fn extract_inputs( let mut description: Option = None; let mut config: Option = None; + let mut extra: Option = None; - if let Some(comment) = get_leading_comment(comments, member) { - let comment = clean_comment(&comment); + if let Some(mut comment) = get_leading_comment(comments, member).as_deref() { + enum State { + Description, + Config, + Extra, + } + + let mut state = State::Description; + + while !comment.is_empty() { + let input_marker_idx = comment.find("@input").unwrap_or(usize::MAX); + let extra_marker_idx = comment.find("@extra").unwrap_or(usize::MAX); + + let current_part; + let marker; + let marker_idx = input_marker_idx.min(extra_marker_idx); + if marker_idx < comment.len() { + (current_part, comment) = comment.split_at(marker_idx); + // @input and @extra are the same length, yay + (marker, comment) = comment.split_at(6); + } else { + current_part = comment; + comment = ""; + marker = ""; + } - let mut parts = comment.splitn(2, "@input"); - description = parts.next().map(ToString::to_string); + match state { + State::Description => description = Some(current_part.to_owned()), + State::Config => config = Some(serde_json::from_str(current_part)?), + State::Extra => extra = Some(serde_json::from_str(current_part)?), + } - if let Some(default) = parts.next() { - config = Some(serde_json::from_str(default)?); + state = match marker { + "@input" => State::Config, + "@extra" => State::Extra, + _ => State::Description, + }; } } @@ -111,38 +142,41 @@ fn extract_inputs( name, description, config, + extra, }); - } else if let Some(call) = value.as_ref().and_then(|value| value.as_call()) { - if call.callee.is_expr() && call.callee.as_expr().unwrap().is_ident_ref_to("input") { - let value = call.args.first().map(|v| &v.expr); - - let Some(name) = get_name_from_input_signal(call) - .or_else(|| to_name(key).map(ToOwned::to_owned)) - else { - continue; - }; - - if let Some(type_) = call - .type_args - .as_ref() - .and_then(|type_args| type_args.params.first()) - .and_then(ts_type_to_input_type) - { - config = Some(config.extend(PlaygroundInputConfig::from_type(type_))); - } + } else if let Some(call) = value.as_ref().and_then(|value| value.as_call()) + && call.callee.is_expr() + && call.callee.as_expr().unwrap().is_ident_ref_to("input") + { + let value = call.args.first().map(|v| &v.expr); + + let Some(name) = + get_name_from_input_signal(call).or_else(|| to_name(key).map(ToOwned::to_owned)) + else { + continue; + }; - let config = config.extend( - value - .and_then(evaluate) - .unwrap_or(PlaygroundInputConfig::default()), - ); - - result.push(PlaygroundInput { - name, - description, - config, - }); + if let Some(type_) = call + .type_args + .as_ref() + .and_then(|type_args| type_args.params.first()) + .and_then(ts_type_to_input_type) + { + config = Some(config.extend(PlaygroundInputConfig::from_type(type_))); } + + let config = config.extend( + value + .and_then(evaluate) + .unwrap_or(PlaygroundInputConfig::default()), + ); + + result.push(PlaygroundInput { + name, + description, + config, + extra, + }); } } @@ -211,24 +245,33 @@ fn extract_type_from_pat(pat: &ast::Pat) -> Option { fn extract_actions( node: &ast::Class, comments: &C, -) -> Vec { - node.body - .iter() - .filter_map(ast::ClassMember::as_method) - .filter_map(|method| -> Option { - let comment = get_leading_comment(comments, method)?; - - if comment.text.contains("@action") { - let name = to_name(&method.key)?.to_owned(); - Some(PlaygroundAction { - name, - description: clean_comment(&comment).replace("@action", ""), - }) - } else { - None +) -> Result> { + let mut result = Vec::new(); + + for member in &node.body { + if let Some(method) = member.as_method() + && let Some(name) = to_name(&method.key) + && let Some(comment) = get_leading_comment(comments, method) + && comment.contains("@action") + { + let comment = comment.replace("@action", ""); + let mut parts = comment.splitn(2, "@extra"); + + let description = parts.next().unwrap().to_owned(); + let mut extra = None; + if let Some(extra_str) = parts.next() { + extra = Some(serde_json::from_str::(extra_str)?); } - }) - .collect() + + result.push(PlaygroundAction { + name: name.to_owned(), + description, + extra, + }); + } + } + + Ok(result) } fn to_name(prop_name: &ast::PropName) -> Option<&str> { @@ -242,8 +285,12 @@ fn to_name(prop_name: &ast::PropName) -> Option<&str> { fn get_leading_comment( comments: &T, node: &N, -) -> Option { - comments - .get_leading(node.span_lo()) - .and_then(|comments| comments.into_iter().next()) +) -> Option { + comments.with_leading(node.span_lo(), |c| { + if c.is_empty() { + None + } else { + Some(clean_comment(&c[0])) + } + }) } diff --git a/src/codeblock/playground/types.rs b/src/codeblock/playground/types.rs index 8644924..c1b8cd4 100644 --- a/src/codeblock/playground/types.rs +++ b/src/codeblock/playground/types.rs @@ -29,6 +29,8 @@ impl PlaygroundInputType { } } +pub(crate) type ExtraValues = serde_json::Map; + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] pub(crate) struct PlaygroundInputConfig { #[serde(rename = "type", default)] @@ -146,7 +148,7 @@ impl PlaygroundInputConfig { pub(super) fn boolean() -> PlaygroundInputConfig { PlaygroundInputConfig { type_: PlaygroundInputType::Boolean, - default_: None, + ..Default::default() } } @@ -154,7 +156,7 @@ impl PlaygroundInputConfig { pub(super) fn number() -> PlaygroundInputConfig { PlaygroundInputConfig { type_: PlaygroundInputType::Number, - default_: None, + ..Default::default() } } @@ -162,7 +164,7 @@ impl PlaygroundInputConfig { pub(super) fn string() -> PlaygroundInputConfig { PlaygroundInputConfig { type_: PlaygroundInputType::String, - default_: None, + ..Default::default() } } @@ -170,7 +172,7 @@ impl PlaygroundInputConfig { pub(super) fn from_type(type_: PlaygroundInputType) -> PlaygroundInputConfig { PlaygroundInputConfig { type_, - default_: None, + ..Default::default() } } @@ -198,11 +200,13 @@ pub(crate) struct PlaygroundInput { pub(crate) name: String, pub(crate) description: Option, pub(crate) config: PlaygroundInputConfig, + pub(crate) extra: Option, } pub(crate) struct PlaygroundAction { pub(crate) name: String, pub(crate) description: String, + pub(crate) extra: Option, } pub(crate) struct Playground { diff --git a/src/config.rs b/src/config.rs index 7296a70..d1eaaa7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use mdbook_renderer::RenderContext; use serde::Deserialize; use toml::value::Table; diff --git a/src/lib.rs b/src/lib.rs index 7dc474f..376772d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,8 +37,8 @@ pub use config::{Builder, Config}; use angular::build; use log::debug; use log::warn; -use markdown::process_markdown; use markdown::ChapterWithCodeBlocks; +use markdown::process_markdown; use mdbook_html::HtmlHandlebars; use mdbook_renderer::{RenderContext, Renderer}; @@ -48,11 +48,11 @@ fn validate_version(ctx: &RenderContext) -> Result<()> { if semver::Version::parse(&ctx.version).is_ok_and(|version| req.matches(&version)) { Ok(()) } else { - bail!("Invalid mdbook version {}, expected {}", &ctx.version, req); + bail!("Invalid mdbook version {}, expected {}", ctx.version, req); } } -pub(crate) use anyhow::{bail, Context, Error, Result}; +pub(crate) use anyhow::{Context, Error, Result, bail}; /// An mdbook [`Renderer`] for including live angular code samples pub struct AngularRenderer {} @@ -96,10 +96,10 @@ impl AngularRenderer { return; } - debug!("Processing chapter {}", &chapter.name); + debug!("Processing chapter {}", chapter.name); match process_markdown(&config, chapter) { Ok(processed) => { - debug!("Processed chapter {}", &chapter.name); + debug!("Processed chapter {}", chapter.name); if let Some(processed) = processed { chapters_with_codeblocks.push(processed); } diff --git a/src/main.rs b/src/main.rs index 3bf44e9..0763bf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,8 @@ use std::{ }; use anyhow::{Context, Result}; -use log::{warn, LevelFilter}; -use mdbook_angular::{stop_background_process, AngularRenderer, Config}; +use log::{LevelFilter, warn}; +use mdbook_angular::{AngularRenderer, Config, stop_background_process}; use mdbook_renderer::RenderContext; fn main() -> Result<()> { diff --git a/src/markdown.rs b/src/markdown.rs index c6d7a78..5aafb34 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -19,9 +19,9 @@ use regex::Regex; use serde::Serialize; use crate::{ - codeblock::{is_angular_codeblock, to_codeblock, CodeBlock}, - utils::path_to_root, Config, Error, Result, + codeblock::{CodeBlock, is_angular_codeblock, playground::ExtraValues, to_codeblock}, + utils::path_to_root, }; #[derive(Serialize)] @@ -31,6 +31,8 @@ struct CodeBlockTemplateInput { description: Option, value: String, + + extra: ExtraValues, } #[derive(Serialize)] @@ -38,6 +40,8 @@ struct CodeBlockTemplateAction { button: String, description: String, + + extra: ExtraValues, } #[derive(Serialize)] @@ -58,71 +62,85 @@ struct CodeBlockTemplateData { flags: CodeBlockTemplateFlags, } -impl CodeBlockTemplateData { - fn new(index: usize, code_block: &CodeBlock) -> Self { - let mut flags = CodeBlockTemplateFlags { collapsed: false }; - let mut code = None; +fn from_code_block( + index: usize, + code_block: CodeBlock, +) -> (CodeBlockTemplateData, CollectedCodeBlock) { + let mut flags = CodeBlockTemplateFlags { collapsed: false }; + let mut code = None; - if let Some(printed_code) = &code_block.code_to_print { - code = Some(Rc::deref(&printed_code.code).clone()); - flags.collapsed = printed_code.collapsed; - } + if let Some(printed_code) = code_block.code_to_print { + code = Some(Rc::deref(&printed_code.code).clone()); + flags.collapsed = printed_code.collapsed; + } - let playground = if code_block.insert { - format!("<{0}>\n", code_block.tag) - } else { - String::new() - }; + let playground = if code_block.insert { + format!("<{0}>\n", code_block.tag) + } else { + String::new() + }; - let mut inputs = Vec::new(); - let mut actions = Vec::new(); - - if let Some(playground) = &code_block.playground { - for input in &playground.inputs { - let value = format!( - "{}", - input.name, - index, - serde_json::to_string(&input.config) - .unwrap() - .replace('<', "<") - ); + let mut inputs = Vec::new(); + let mut actions = Vec::new(); + let mut has_playground = false; + + if let Some(playground) = code_block.playground { + has_playground = true; + + for input in playground.inputs { + let value = format!( + "{}", + input.name, + index, + serde_json::to_string(&input.config) + .unwrap() + .replace('<', "<") + ); - inputs.push(CodeBlockTemplateInput { - name: input.name.clone(), - description: input.description.clone(), - value, - }); - } + inputs.push(CodeBlockTemplateInput { + name: input.name, + description: input.description, + extra: input.extra.unwrap_or_default(), + value, + }); + } - for action in &playground.actions { - let button = format!( - "", - action.name, index - ); + for action in playground.actions { + let button = format!( + "", + action.name, index + ); - actions.push(CodeBlockTemplateAction { - button, - description: action.description.clone(), - }); - } + actions.push(CodeBlockTemplateAction { + button, + description: action.description, + extra: action.extra.unwrap_or_default(), + }); } + } - Self { + ( + CodeBlockTemplateData { playground, code, inputs, actions, flags, - } - } + }, + CollectedCodeBlock { + index, + class_name: code_block.class_name, + code_to_run: code_block.code_to_run, + has_playground, + }, + ) } struct CodeBlockCollector<'a, 'b> { config: &'a Config, chapter: &'a Chapter, - code_blocks: Vec, + code_blocks: Vec, current_code: Option<(String, Option)>, @@ -131,6 +149,16 @@ struct CodeBlockCollector<'a, 'b> { handlebars: Handlebars<'b>, } +pub(crate) struct CollectedCodeBlock { + pub(crate) index: usize, + + pub(crate) has_playground: bool, + + pub(crate) class_name: String, + + pub(crate) code_to_run: Rc, +} + impl<'a> CodeBlockCollector<'a, '_> { fn new(config: &'a Config, chapter: &'a Chapter) -> Result { let mut handlebars = Handlebars::new(); @@ -169,11 +197,11 @@ impl<'a> CodeBlockCollector<'a, '_> { return ProcessedEvent::empty(); } - if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = &event { - if is_angular_codeblock(language) { - self.current_code = Some((language.as_ref().into(), None)); - return ProcessedEvent::empty(); - } + if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = &event + && is_angular_codeblock(language) + { + self.current_code = Some((language.as_ref().into(), None)); + return ProcessedEvent::empty(); } if let Some((language, code)) = self.current_code.take() { @@ -292,17 +320,14 @@ impl<'a> CodeBlockCollector<'a, '_> { code_to_print, ) { Ok(code_block) => { - let data = CodeBlockTemplateData::new(index, &code_block); + let (data, code_block) = from_code_block(index, code_block); self.code_blocks.push(code_block); match self.handlebars.render("playground", &data) { - Ok(rendered) => { - // println!("got here, {rendered}"); - ProcessedEvent::multiple(vec![ - Event::HardBreak, - Event::Html(rendered.into()), - ]) - } + Ok(rendered) => ProcessedEvent::multiple(vec![ + Event::HardBreak, + Event::Html(rendered.into()), + ]), Err(error) => { self.error(error); ProcessedEvent::empty() @@ -361,7 +386,7 @@ impl<'a> Iterator for ProcessedEvent<'a> { pub(crate) struct ChapterWithCodeBlocks { pub(crate) source_path: PathBuf, - pub(crate) code_blocks: Vec, + pub(crate) code_blocks: Vec, } pub(crate) fn process_markdown( @@ -397,10 +422,10 @@ pub(crate) fn process_markdown( r#"{}"#, "\n\n", serde_json::to_string(&source_path)?, - &ptr, + ptr, )?; - if code_blocks.iter().any(|b| b.playground.is_some()) { + if code_blocks.iter().any(|b| b.has_playground) { write!( new_content, r#""#, diff --git a/src/utils/swc.rs b/src/utils/swc.rs index e612877..8ccbfee 100644 --- a/src/utils/swc.rs +++ b/src/utils/swc.rs @@ -31,16 +31,16 @@ pub(crate) fn clean_comment(comment: &comments::Comment) -> String { }) .collect::>(); - if let Some(first) = lines.front() { - if first.is_empty() { - lines.pop_front(); - } + if let Some(first) = lines.front() + && first.is_empty() + { + lines.pop_front(); } - if let Some(last) = lines.back() { - if last.is_empty() { - lines.pop_back(); - } + if let Some(last) = lines.back() + && last.is_empty() + { + lines.pop_back(); } lines.into_iter().collect::>().join("\n")