Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "mdbook-angular"
version = "0.5.0"
edition = "2021"
edition = "2024"
authors = ["Bram Gotink <bram@bram.dev>"]
license = "EUPL-1.2"
description = "mdbook renderer to run angular code samples"
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('default value');
}
```

## Development

This project requires mdbook and angular to be installed
Expand Down
2 changes: 1 addition & 1 deletion src/angular/builder/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChapterWithCodeBlocks>) -> Result<()> {
let root = &config.angular_root_folder;
Expand Down
5 changes: 3 additions & 2 deletions src/angular/builder/background/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions src/angular/builder/default.rs
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -114,7 +114,7 @@ fn replace_load_angular_script_path(config: &Config, chapters: Vec<PathBuf>) ->
})
.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);
Expand Down
11 changes: 6 additions & 5 deletions src/angular/builder/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -45,7 +45,7 @@ impl Writer<'_> {
root: P,
index: usize,
chapter_path: &Path,
code_blocks: Vec<CodeBlock>,
code_blocks: Vec<CollectedCodeBlock>,
) -> Result<()> {
let root = root.as_ref();
let project_folder = format!("code_{index}");
Expand Down Expand Up @@ -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,
Expand All @@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion src/codeblock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 11 additions & 14 deletions src/codeblock/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Regex> = LazyLock::new(|| Regex::new(r"\.([cm]?)ts(x?)$").unwrap());
static START_OF_FILE: BytePos = BytePos(1);
Expand Down Expand Up @@ -60,18 +60,15 @@ 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!(
"Selector isn't a string literal in class {name}"
)));
};

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}"
)));
Expand Down Expand Up @@ -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}");
Expand Down
16 changes: 9 additions & 7 deletions src/codeblock/playground/evaluate_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,16 @@ fn i64_i64_to_i64_operator<F>(
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()
Expand Down
Loading