Skip to content
Open
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
1 change: 1 addition & 0 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ <h1>cooklang-rs playground</h1>
<option value="ast">AST</option>
<option value="stdmeta">Standard metadata</option>
<option value="render2">New renderer</option>
<option value="debug">Debug info (for reporting)</option>
</select>
<div id="servingscontainer">
<label for="servings">Servings</label>
Expand Down
36 changes: 35 additions & 1 deletion playground/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,40 @@ async function run(): Promise<void> {
errors.innerHTML = report;
break;
}
case "debug": {
const debug = parser.debug_info(input);
const debugOutput = `# Debug Information

**Version:** ${debug.version}
**Extensions:** ${debug.extensions}
**Load units:** ${debug.load_units}

## Parser Configuration
- Extensions bitmask: ${debug.extensions}
- Unit conversion: ${debug.load_units ? 'enabled' : 'disabled'}

## Copy below for issue reports:

\`\`\`
Version: ${debug.version}
Extensions: ${debug.extensions}
Load Units: ${debug.load_units}
\`\`\`

## Events
${debug.events}

## Full Recipe
\`\`\`json
${debug.full_recipe}
\`\`\`

## Metadata
${debug.metadata}`;
output.textContent = debugOutput;
errors.innerHTML = debug.report;
break;
}
}
errorsDetails.open = errors.childElementCount !== 0;
}
Expand Down Expand Up @@ -215,7 +249,7 @@ async function run(): Promise<void> {
const servingsContainer = document.getElementById(
"servingscontainer"
) as HTMLDivElement;
jsonContainer.hidden = mode === "render" || mode === "render2" || mode === "events";
jsonContainer.hidden = mode === "render" || mode === "render2" || mode === "events" || mode === "debug";
servingsContainer.hidden = mode !== "render" && mode !== "render2";
localStorage.setItem("mode", mode);
parse();
Expand Down
9 changes: 6 additions & 3 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ pub fn build_ast<'i>(events: impl Iterator<Item = Event<'i>>) -> PassResult<Ast<
let mut items = Vec::new();
let mut ctx = SourceReport::empty();
for event in events {
match event {
Event::YAMLFrontMatter(_) => todo!(),
Event::Metadata { key, value } => blocks.push(Block::Metadata { key, value }),
match event {
Event::YAMLFrontMatter(_) => {
// YAML frontmatter is processed separately by the analysis pass
// and doesn't get included in the AST
}
Event::Metadata { key, value } => blocks.push(Block::Metadata { key, value }),
Event::Section { name } => blocks.push(Block::Section { name }),
Event::Start(_kind) => items.clear(),
Event::End(kind) => {
Expand Down
171 changes: 171 additions & 0 deletions typescript/DEBUG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Debug Information in cooklang-ts

The TypeScript library provides a consolidated debug API to access comprehensive debug and version information about the parser.

## Comprehensive Debug Information

Get all debug information in a single call:

```typescript
import { Parser, type DebugInfo } from "@cooklang/cooklang-ts";

const parser = new Parser();
const recipe = "-- title: My Recipe\n\n@ingredient{1}\nSome @step.";

const debug: DebugInfo = parser.debug_info(recipe);

console.log(JSON.stringify(debug, null, 2));
// {
// "version": "0.8.0",
// "extensions": 4095,
// "load_units": true,
// "ast": "{ ... AST as JSON ... }",
// "events": "[ ... parse events ... ]",
// "full_recipe": "{ ... full recipe as JSON ... }",
// "metadata": "{ ... parsed metadata ... }",
// "report": "<html error report>"
// }
```

The `DebugInfo` object contains:
- **version**: Library version
- **extensions**: Currently enabled extensions (bitmask)
- **load_units**: Whether unit loading is enabled
- **ast**: AST representation in JSON format
- **events**: Parse events in debug format
- **full_recipe**: Complete parsed recipe in JSON format
- **metadata**: Parsed metadata fields
- **report**: HTML-formatted parsing errors and warnings

## Version Information

Get just the current version:

```typescript
import { version } from "@cooklang/cooklang-ts";

const currentVersion = version();
console.log(currentVersion); // e.g., "0.8.0"
```

## Individual Parser Debug Methods

For more granular control, you can call individual debug methods:

The `Parser` class (imported from the WASM bindings) provides several debug methods to inspect the parsing process:

### Getting the AST (Abstract Syntax Tree)

```typescript
import { Parser } from "@cooklang/cooklang-ts";

const parser = new Parser();
const recipe = "-- title: My Recipe\n\n@ingredient{1}\nSome @step.";

// Get AST in pretty-printed debug format
const { value, error } = parser.parse_ast(recipe, false);
console.log(value); // Debug representation of the AST

// Get AST as JSON
const { value: jsonAst, error: jsonError } = parser.parse_ast(recipe, true);
console.log(JSON.parse(jsonAst)); // Parsed AST as JSON object
```

### Getting Parse Events

```typescript
// Get low-level parse events
const events = parser.parse_events(recipe);
console.log(events); // Raw parser events in debug format
```

### Getting Full Parsed Recipe

```typescript
// Get the full parsed recipe with all details
const { value, error } = parser.parse_full(recipe, false);
console.log(value); // Full recipe in debug format

// Get as JSON
const { value: jsonRecipe, error: jsonError } = parser.parse_full(recipe, true);
const fullData = JSON.parse(jsonRecipe); // Full recipe as JSON
```

### Getting Standard Metadata

```typescript
// Get parsed standard metadata fields
const { value, error } = parser.std_metadata(recipe);
console.log(value); // Parsed metadata in debug format
```

## Parser Configuration

You can configure the parser for different debug scenarios:

```typescript
const parser = new Parser();

// Enable/disable unit conversion
parser.load_units = false;

// Configure extensions
parser.extensions = 0; // Disable all extensions
parser.extensions = (1 << 1) | (1 << 3); // Enable specific extensions
```

## Parse Results

When using the main `parse()` method, you also get diagnostic information:

```typescript
import { CooklangParser } from "@cooklang/cooklang-ts";

const parser = new CooklangParser();
const [recipe, report] = parser.parse(input);

// The report contains parsing diagnostics and warnings
console.log(report); // HTML-formatted error and warning messages
```

## Example: Creating an Issue Report

```typescript
import { Parser } from "@cooklang/cooklang-ts";

function createIssueReport(recipeInput: string) {
const parser = new Parser();

// Get comprehensive debug info in a single call
const debug = parser.debug_info(recipeInput);

// Format for issue reporting
const report = `
## Debug Information

**Version:** ${debug.version}
**Extensions:** ${debug.extensions}
**Load Units:** ${debug.load_units}

### AST
\`\`\`json
${debug.ast}
\`\`\`

### Parse Report
${debug.report}

### Full Recipe
\`\`\`json
${debug.full_recipe}
\`\`\`
`;

return report;
}
```

## See Also

- [Playground](../playground) - Interactive playground with debug information display
- [main.ts](./src/lib.rs) - WASM bindings source with full API documentation
8 changes: 7 additions & 1 deletion typescript/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* @cooklang/cooklang-ts - TypeScript library for parsing Cooklang recipes
*
* For debug and version information, see {@link DEBUG.md}
*/

import {
version,
Parser,
Expand All @@ -9,7 +15,7 @@ import {
} from "./pkg/cooklang_wasm.js";

export {version, Parser};
export type {ScaledRecipeWithReport} from "./pkg/cooklang_wasm.js";
export type {ScaledRecipeWithReport, DebugInfo} from "./pkg/cooklang_wasm.js";


export class CooklangRecipe {
Expand Down
1 change: 1 addition & 0 deletions typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"main": "index.ts",
"files": [
"index.ts",
"DEBUG.md",
"pkg/cooklang_wasm_bg.wasm",
"pkg/cooklang_wasm_bg.wasm.d.ts",
"pkg/cooklang_wasm.d.ts",
Expand Down
78 changes: 78 additions & 0 deletions typescript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ pub struct ScaledRecipeWithReport {
report: String,
}

#[derive(Tsify, Serialize)]
#[tsify(into_wasm_abi)]
pub struct DebugInfo {
pub version: String,
pub extensions: u32,
pub load_units: bool,
pub ast: String,
pub events: String,
pub full_recipe: String,
pub metadata: String,
pub report: String,
}

#[wasm_bindgen]
impl Parser {
#[wasm_bindgen(constructor)]
Expand Down Expand Up @@ -252,6 +265,71 @@ impl Parser {
};
FallibleResult::new(value, report, input)
}

pub fn debug_info(&self, input: &str) -> DebugInfo {
// Parse AST
let events = PullParser::new(input, self.extensions);
let (ast, _ast_report) = build_ast(events).into_tuple();
let ast_value = match ast {
Some(ast) => serde_json::to_string_pretty(&ast).unwrap(),
None => "<no output>".to_string(),
};

// Parse events
let mut events_str = String::new();
let events = PullParser::new(input, self.extensions);
for e in events {
writeln!(events_str, "{e:#?}").unwrap();
}

// Parse full recipe
let (recipe, recipe_report) = self.parser.parse(input).into_tuple();
let full_recipe = match recipe {
Some(r) => serde_json::to_string_pretty(&r).unwrap(),
None => "<no output>".to_string(),
};

// Parse metadata
let (meta, _meta_report) = self.parser.parse_metadata(input).into_tuple();
let metadata_value = match meta {
Some(m) => {
#[derive(Debug)]
#[allow(dead_code)]
struct StdMeta<'a> {
tags: Option<Vec<std::borrow::Cow<'a, str>>>,
author: Option<NameAndUrl>,
source: Option<NameAndUrl>,
time: Option<RecipeTime>,
servings: Option<cooklang::metadata::Servings>,
locale: Option<(&'a str, Option<&'a str>)>,
}
let val = StdMeta {
tags: m.tags(),
author: m.author(),
source: m.source(),
time: m.time(self.parser.converter()),
servings: m.servings(),
locale: m.locale(),
};
format!("{val:#?}")
}
None => "<no output>".to_string(),
};

// Combine all reports (prefer recipe_report as most comprehensive)
let combined_report = display_report(recipe_report, input);

DebugInfo {
version: version(),
extensions: self.extensions.bits(),
load_units: self.load_units,
ast: ast_value,
events: events_str,
full_recipe,
metadata: metadata_value,
report: combined_report,
}
}
}

impl Parser {
Expand Down
Loading
Loading