This commit is contained in:
2025-03-07 20:02:37 +01:00
parent 43f7bc7943
commit ca3bac1d76
26 changed files with 2353 additions and 10 deletions

View File

@@ -0,0 +1,29 @@
---
sidebar_position: 10
title: 'Dunia CyberCity'
description: 'Co-create the Future'
---
![alt text](img/cybercity2.png)
We are building a 700,000 m2 Regenerative Startup Cyber City
- 100% co-owned
- regenerative
- autonomous zone
a city for startups and its creators
- build a system for augmented collective intelligence
- operate business wise from a digital freezone
- (co)own assets (shares, digital currencies) safely and privately
## More Info
> see [https://friends.threefold.info/cybercity](https://friends.threefold.info/cybercity)
- login:```planet```
- passwd:```first```

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
// import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
// import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console
import log
import os
import markdown
import mlib
path2:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/links.md"
path1:="${os.home_dir()}/code/github/freeflowuniverse/herolib/examples/webtools/mdbook_markdown/content/test.md"
text := os.read_file(path1)!
// Example 1: Using the built-in plaintext renderer
println('=== PLAINTEXT RENDERING ===')
println(markdown.to_plain(text))
println('')
// Example 2: Using our custom structure renderer to show markdown structure
println('=== STRUCTURE RENDERING ===')
println(mlib.to_structure(text))
// // Example 3: Using a simple markdown example to demonstrate structure
// println('\n=== STRUCTURE OF A SIMPLE MARKDOWN EXAMPLE ===')
// simple_md := '# Heading 1\n\nThis is a paragraph with **bold** and *italic* text.\n\n- List item 1\n- List item 2\n\n```v\nfn main() {\n\tprintln("Hello, world!")\n}\n```\n\n[Link to V language](https://vlang.io)'
// println(markdown.to_structure(simple_md))

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env -S v -n -w -gc none run
import mlib2
fn main() {
// Sample markdown text
text := '# Heading 1
This is a paragraph with **bold** and *italic* text.
## Heading 2
- List item 1
- List item 2
- Nested item
- List item 3
```v
fn main() {
println("Hello, world!")
}
```
> This is a blockquote
> with multiple lines
| Column 1 | Column 2 | Column 3 |
|----------|:--------:|---------:|
| Left | Center | Right |
| Cell 1 | Cell 2 | Cell 3 |
[Link to V language](https://vlang.io)
![Image](https://vlang.io/img/v-logo.png)
Footnote reference[^1]
[^1]: This is a footnote.
'
// Example 1: Using the plain text renderer
println('=== PLAINTEXT RENDERING ===')
println(mlib2.to_plain(text))
println('')
// Example 2: Using the structure renderer to show markdown structure
println('=== STRUCTURE RENDERING ===')
println(mlib2.to_structure(text))
// Example 3: Using the navigator to find specific elements
println('\n=== NAVIGATION EXAMPLE ===')
// Parse the markdown text
doc := mlib2.parse(text)
// Create a navigator
mut nav := mlib2.new_navigator(doc)
// Find all headings
headings := nav.find_all_by_type(.heading)
println('Found ${headings.len} headings:')
for heading in headings {
level := heading.attributes['level']
println(' ${'#'.repeat(level.int())} ${heading.content}')
}
// Find all code blocks
code_blocks := nav.find_all_by_type(.code_block)
println('\nFound ${code_blocks.len} code blocks:')
for block in code_blocks {
language := block.attributes['language']
println(' Language: ${language}')
println(' Content length: ${block.content.len} characters')
}
// Find all list items
list_items := nav.find_all_by_type(.list_item)
println('\nFound ${list_items.len} list items:')
for item in list_items {
println(' - ${item.content}')
}
// Find content containing specific text
if element := nav.find_by_content('blockquote') {
println('\nFound element containing "blockquote":')
println(' Type: ${element.typ}')
println(' Content: ${element.content}')
}
// Find all footnotes
println('\nFootnotes:')
for id, footnote in nav.footnotes() {
println(' [^${id}]: ${footnote.content}')
}
}

View File

@@ -0,0 +1,194 @@
module mlib
import markdown
import strings
// Helper functions to extract information from C structs
fn get_md_attribute_string(attr C.MD_ATTRIBUTE) ?string {
unsafe {
if attr.text == nil || attr.size == 0 {
return none
}
return attr.text.vstring_with_len(int(attr.size))
}
}
fn get_heading_level(detail voidptr) int {
unsafe {
h_detail := &C.MD_BLOCK_H_DETAIL(detail)
return int(h_detail.level)
}
}
fn get_code_language(detail voidptr) ?string {
unsafe {
code_detail := &C.MD_BLOCK_CODE_DETAIL(detail)
return get_md_attribute_string(code_detail.lang)
}
}
fn get_ul_details(detail voidptr) (bool, string) {
unsafe {
ul_detail := &C.MD_BLOCK_UL_DETAIL(detail)
is_tight := ul_detail.is_tight != 0
mark := ul_detail.mark.ascii_str()
return is_tight, mark
}
}
fn get_ol_details(detail voidptr) (int, bool) {
unsafe {
ol_detail := &C.MD_BLOCK_OL_DETAIL(detail)
start := int(ol_detail.start)
is_tight := ol_detail.is_tight != 0
return start, is_tight
}
}
fn get_link_details(detail voidptr) (?string, ?string) {
unsafe {
a_detail := &C.MD_SPAN_A_DETAIL(detail)
href := get_md_attribute_string(a_detail.href)
title := get_md_attribute_string(a_detail.title)
return href, title
}
}
fn get_image_details(detail voidptr) (?string, ?string) {
unsafe {
img_detail := &C.MD_SPAN_IMG_DETAIL(detail)
src := get_md_attribute_string(img_detail.src)
title := get_md_attribute_string(img_detail.title)
return src, title
}
}
fn get_wikilink_target(detail voidptr) ?string {
unsafe {
wl_detail := &C.MD_SPAN_WIKILINK_DETAIL(detail)
return get_md_attribute_string(wl_detail.target)
}
}
// StructureRenderer is a custom renderer that outputs the structure of a markdown document
pub struct StructureRenderer {
mut:
writer strings.Builder = strings.new_builder(200)
indent int // Track indentation level for nested elements
}
pub fn (mut sr StructureRenderer) str() string {
return sr.writer.str()
}
fn (mut sr StructureRenderer) enter_block(typ markdown.MD_BLOCKTYPE, detail voidptr) ? {
// Add indentation based on current level
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
// Output the block type
sr.writer.write_string('BLOCK[${typ}]: ')
// Add specific details based on block type
match typ {
.md_block_h {
level := get_heading_level(detail)
sr.writer.write_string('Level ${level}')
}
.md_block_code {
if lang := get_code_language(detail) {
sr.writer.write_string('Language: ${lang}')
} else {
sr.writer.write_string('No language specified')
}
}
.md_block_ul {
is_tight, mark := get_ul_details(detail)
sr.writer.write_string('Tight: ${is_tight}, Mark: ${mark}')
}
.md_block_ol {
start, is_tight := get_ol_details(detail)
sr.writer.write_string('Start: ${start}, Tight: ${is_tight}')
}
else {}
}
sr.writer.write_u8(`\n`)
sr.indent++
}
fn (mut sr StructureRenderer) leave_block(typ markdown.MD_BLOCKTYPE, _ voidptr) ? {
sr.indent--
}
fn (mut sr StructureRenderer) enter_span(typ markdown.MD_SPANTYPE, detail voidptr) ? {
// Add indentation based on current level
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
// Output the span type
sr.writer.write_string('SPAN[${typ}]: ')
// Add specific details based on span type
match typ {
.md_span_a {
href, title := get_link_details(detail)
if href != none {
sr.writer.write_string('Link: ${href}')
}
if title != none {
sr.writer.write_string(', Title: ${title}')
}
}
.md_span_img {
src, title := get_image_details(detail)
if src != none {
sr.writer.write_string('Source: ${src}')
}
if title != none {
sr.writer.write_string(', Title: ${title}')
}
}
.md_span_wikilink {
if target := get_wikilink_target(detail) {
sr.writer.write_string('Target: ${target}')
}
}
else {}
}
sr.writer.write_u8(`\n`)
sr.indent++
}
fn (mut sr StructureRenderer) leave_span(typ markdown.MD_SPANTYPE, _ voidptr) ? {
sr.indent--
}
fn (mut sr StructureRenderer) text(typ markdown.MD_TEXTTYPE, text string) ? {
if text.trim_space() == '' {
return
}
// Add indentation based on current level
sr.writer.write_string(strings.repeat(` `, sr.indent * 2))
// Output the text type
sr.writer.write_string('TEXT[${typ}]: ')
// Add the text content (truncate if too long)
content := if text.len > 50 { text[..50] + '...' } else { text }
sr.writer.write_string(content.replace('\n', '\\n'))
sr.writer.write_u8(`\n`)
}
fn (mut sr StructureRenderer) debug_log(msg string) {
println(msg)
}
// to_structure renders a markdown string and returns its structure
pub fn to_structure(input string) string {
mut structure_renderer := StructureRenderer{}
out := markdown.render(input, mut structure_renderer) or { '' }
return out
}

View File

@@ -0,0 +1,133 @@
# V Markdown Parser
A pure V implementation of a Markdown parser that supports extended Markdown syntax and provides an easy way to navigate through the document structure.
## Features
- Parses Markdown text into a structured representation
- Supports both basic and extended Markdown syntax
- Provides an easy way to navigate through the document structure
- Includes renderers for different output formats
- No external dependencies
## Supported Markdown Syntax
- Headings (# to ######)
- Paragraphs
- Blockquotes
- Lists (ordered and unordered)
- Task lists
- Code blocks (fenced with language support)
- Tables with alignment
- Horizontal rules
- Footnotes
- Basic text elements (currently as plain text, with planned support for inline formatting)
## Usage
### Parsing Markdown
```v
import mlib2
// Parse Markdown text
md_text := '# Hello World\n\nThis is a paragraph.'
doc := mlib2.parse(md_text)
// Access the document structure
root := doc.root
for child in root.children {
println(child.typ)
}
```
### Navigating the Document
```v
import mlib2
// Parse Markdown text
md_text := '# Hello World\n\nThis is a paragraph.'
doc := mlib2.parse(md_text)
// Create a navigator
mut nav := mlib2.new_navigator(doc)
// Find elements by type
headings := nav.find_all_by_type(.heading)
for heading in headings {
level := heading.attributes['level']
println('Heading level ${level}: ${heading.content}')
}
// Find elements by content
if para := nav.find_by_content('paragraph') {
println('Found paragraph: ${para.content}')
}
// Navigate through the document
if first_heading := nav.find_by_type(.heading) {
println('First heading: ${first_heading.content}')
// Move to next sibling
if next := nav.next_sibling() {
println('Next element after heading: ${next.typ}')
}
}
```
### Rendering the Document
```v
import mlib2
// Parse Markdown text
md_text := '# Hello World\n\nThis is a paragraph.'
// Render as structure (for debugging)
structure := mlib2.to_structure(md_text)
println(structure)
// Render as plain text
plain_text := mlib2.to_plain(md_text)
println(plain_text)
```
## Element Types
The parser recognizes the following element types:
- `document`: The root element of the document
- `heading`: A heading element (h1-h6)
- `paragraph`: A paragraph of text
- `blockquote`: A blockquote
- `code_block`: A code block
- `list`: A list (ordered or unordered)
- `list_item`: An item in a list
- `task_list_item`: A task list item with checkbox
- `table`: A table
- `table_row`: A row in a table
- `table_cell`: A cell in a table
- `horizontal_rule`: A horizontal rule
- `footnote`: A footnote definition
- `footnote_ref`: A reference to a footnote
- `text`: A text element
- `link`, `image`, `emphasis`, `strong`, `strikethrough`, `inline_code`: Inline formatting elements (planned for future implementation)
## Element Structure
Each Markdown element has the following properties:
- `typ`: The type of the element
- `content`: The text content of the element
- `children`: Child elements
- `attributes`: Additional attributes specific to the element type
- `line_number`: The line number where the element starts in the source
- `column`: The column number where the element starts in the source
## Future Improvements
- Implement parsing of inline elements (bold, italic, links, etc.)
- Add HTML renderer
- Support for more extended Markdown syntax
- Performance optimizations

View File

@@ -0,0 +1,143 @@
module mlib2
// This file contains examples of how to use the Markdown parser
// Example of parsing and navigating a markdown document
pub fn example_navigation() {
md_text := '# Heading 1
This is a paragraph with **bold** and *italic* text.
## Heading 2
- List item 1
- List item 2
- Nested item
- List item 3
```v
fn main() {
println("Hello, world!")
}
```
> This is a blockquote
> with multiple lines
| Column 1 | Column 2 | Column 3 |
|----------|:--------:|---------:|
| Left | Center | Right |
| Cell 1 | Cell 2 | Cell 3 |
[Link to V language](https://vlang.io)
![Image](https://vlang.io/img/v-logo.png)
Footnote reference[^1]
[^1]: This is a footnote.
'
// Parse the markdown text
doc := parse(md_text)
// Create a navigator
mut nav := new_navigator(doc)
// Find all headings
headings := nav.find_all_by_type(.heading)
println('Found ${headings.len} headings:')
for heading in headings {
level := heading.attributes['level']
println(' ${'#'.repeat(level.int())} ${heading.content}')
}
// Find the first code block
if code_block := nav.find_by_type(.code_block) {
language := code_block.attributes['language']
println('\nFound code block in language: ${language}')
println('```${language}\n${code_block.content}```')
}
// Find all list items
list_items := nav.find_all_by_type(.list_item)
println('\nFound ${list_items.len} list items:')
for item in list_items {
println(' - ${item.content}')
}
// Find content containing specific text
if element := nav.find_by_content('blockquote') {
println('\nFound element containing "blockquote":')
println(' Type: ${element.typ}')
println(' Content: ${element.content}')
}
// Find table cells
table_cells := nav.find_all_by_type(.table_cell)
println('\nFound ${table_cells.len} table cells:')
for cell in table_cells {
alignment := cell.attributes['align'] or { 'left' }
is_header := cell.attributes['is_header'] or { 'false' }
println(' Cell: "${cell.content}" (align: ${alignment}, header: ${is_header})')
}
// Find footnotes
println('\nFootnotes:')
for id, footnote in nav.footnotes() {
println(' [^${id}]: ${footnote.content}')
}
}
// Example of rendering a markdown document
pub fn example_rendering() {
md_text := '# Heading 1
This is a paragraph with **bold** and *italic* text.
## Heading 2
- List item 1
- List item 2
- Nested item
- List item 3
```v
fn main() {
println("Hello, world!")
}
```
> This is a blockquote
> with multiple lines
'
// Parse the markdown text
doc := parse(md_text)
// Render as structure
mut structure_renderer := new_structure_renderer()
structure := structure_renderer.render(doc)
println('=== STRUCTURE RENDERING ===')
println(structure)
// Render as plain text
mut plain_text_renderer := new_plain_text_renderer()
plain_text := plain_text_renderer.render(doc)
println('=== PLAIN TEXT RENDERING ===')
println(plain_text)
// Using convenience functions
println('=== USING CONVENIENCE FUNCTIONS ===')
println(to_structure(md_text))
println(to_plain(md_text))
}
// Main function to run the examples
pub fn main() {
println('=== NAVIGATION EXAMPLE ===')
example_navigation()
println('\n=== RENDERING EXAMPLE ===')
example_rendering()
}

View File

@@ -0,0 +1,70 @@
module mlib2
// MarkdownElement represents a single element in a markdown document
pub struct MarkdownElement {
pub:
typ ElementType
content string
children []&MarkdownElement
attributes map[string]string
line_number int
column int
}
// ElementType represents the type of a markdown element
pub enum ElementType {
document
heading
paragraph
blockquote
code_block
list
list_item
table
table_row
table_cell
horizontal_rule
link
image
emphasis
strong
strikethrough
inline_code
html
text
footnote
footnote_ref
task_list_item
}
// MarkdownDocument represents a parsed markdown document
pub struct MarkdownDocument {
pub mut:
root &MarkdownElement
footnotes map[string]&MarkdownElement
}
// Creates a new markdown document
pub fn new_document() MarkdownDocument {
root := &MarkdownElement{
typ: .document
content: ''
children: []
}
return MarkdownDocument{
root: root
footnotes: map[string]&MarkdownElement{}
}
}
// Parses markdown text and returns a MarkdownDocument
pub fn parse(text string) MarkdownDocument {
mut parser := Parser{
text: text
pos: 0
line: 1
column: 1
}
return parser.parse()
}

View File

@@ -0,0 +1,269 @@
module mlib2
// Navigator provides an easy way to navigate through the document structure
@[heap]
pub struct Navigator {
pub:
doc MarkdownDocument
pub mut:
current_element &MarkdownElement
}
// Creates a new navigator for a markdown document
pub fn new_navigator(doc MarkdownDocument) Navigator {
return Navigator{
doc: doc
current_element: doc.root
}
}
// Reset the navigator to the root element
pub fn (mut n Navigator) reset() {
n.current_element = n.doc.root
}
// Find an element by type
pub fn (mut n Navigator) find_by_type(typ ElementType) ?&MarkdownElement {
return n.find_by_type_from(n.doc.root, typ)
}
// Find an element by type starting from a specific element
fn (mut n Navigator) find_by_type_from(element &MarkdownElement, typ ElementType) ?&MarkdownElement {
if element.typ == typ {
n.current_element = element
return element
}
for child in element.children {
if child.typ == typ {
n.current_element = child
return child
}
if result := n.find_by_type_from(child, typ) {
return result
}
}
return none
}
// Find all elements by type
pub fn (mut n Navigator) find_all_by_type(typ ElementType) []&MarkdownElement {
return n.find_all_by_type_from(n.doc.root, typ)
}
// Find all elements by type starting from a specific element
fn (mut n Navigator) find_all_by_type_from(element &MarkdownElement, typ ElementType) []&MarkdownElement {
mut results := []&MarkdownElement{}
if element.typ == typ {
results << element
}
for child in element.children {
if child.typ == typ {
results << child
}
results << n.find_all_by_type_from(child, typ)
}
return results
}
// Find an element by content
pub fn (mut n Navigator) find_by_content(text string) ?&MarkdownElement {
return n.find_by_content_from(n.doc.root, text)
}
// Find an element by content starting from a specific element
fn (mut n Navigator) find_by_content_from(element &MarkdownElement, text string) ?&MarkdownElement {
if element.content.contains(text) {
n.current_element = element
return element
}
for child in element.children {
if child.content.contains(text) {
n.current_element = child
return child
}
if result := n.find_by_content_from(child, text) {
return result
}
}
return none
}
// Find all elements by content
pub fn (mut n Navigator) find_all_by_content(text string) []&MarkdownElement {
return n.find_all_by_content_from(n.doc.root, text)
}
// Find all elements by content starting from a specific element
fn (mut n Navigator) find_all_by_content_from(element &MarkdownElement, text string) []&MarkdownElement {
mut results := []&MarkdownElement{}
if element.content.contains(text) {
results << element
}
for child in element.children {
if child.content.contains(text) {
results << child
}
results << n.find_all_by_content_from(child, text)
}
return results
}
// Find an element by attribute
pub fn (mut n Navigator) find_by_attribute(key string, value string) ?&MarkdownElement {
return n.find_by_attribute_from(n.doc.root, key, value)
}
// Find an element by attribute starting from a specific element
fn (mut n Navigator) find_by_attribute_from(element &MarkdownElement, key string, value string) ?&MarkdownElement {
if element.attributes[key] == value {
n.current_element = element
return element
}
for child in element.children {
if child.attributes[key] == value {
n.current_element = child
return child
}
if result := n.find_by_attribute_from(child, key, value) {
return result
}
}
return none
}
// Find all elements by attribute
pub fn (mut n Navigator) find_all_by_attribute(key string, value string) []&MarkdownElement {
return n.find_all_by_attribute_from(n.doc.root, key, value)
}
// Find all elements by attribute starting from a specific element
fn (mut n Navigator) find_all_by_attribute_from(element &MarkdownElement, key string, value string) []&MarkdownElement {
mut results := []&MarkdownElement{}
if element.attributes[key] == value {
results << element
}
for child in element.children {
if child.attributes[key] == value {
results << child
}
results << n.find_all_by_attribute_from(child, key, value)
}
return results
}
// Find the parent of an element
pub fn (mut n Navigator) find_parent(target &MarkdownElement) ?&MarkdownElement {
return n.find_parent_from(n.doc.root, target)
}
// Find the parent of an element starting from a specific element
fn (mut n Navigator) find_parent_from(root &MarkdownElement, target &MarkdownElement) ?&MarkdownElement {
for child in root.children {
if child == target {
n.current_element = root
return root
}
if result := n.find_parent_from(child, target) {
return result
}
}
return none
}
// Get the parent of the current element
pub fn (mut n Navigator) parent() ?&MarkdownElement {
return n.find_parent(n.current_element)
}
// Get the next sibling of the current element
pub fn (mut n Navigator) next_sibling() ?&MarkdownElement {
parent := n.parent() or { return none }
mut found := false
for child in parent.children {
if found {
n.current_element = child
return child
}
if child == n.current_element {
found = true
}
}
return none
}
// Get the previous sibling of the current element
pub fn (mut n Navigator) prev_sibling() ?&MarkdownElement {
parent := n.parent() or { return none }
mut prev := &MarkdownElement(0)
for child in parent.children {
if child == n.current_element && prev != 0 {
n.current_element = prev
return prev
}
prev = child
}
return none
}
// Get the first child of the current element
pub fn (mut n Navigator) first_child() ?&MarkdownElement {
if n.current_element.children.len == 0 {
return none
}
n.current_element = n.current_element.children[0]
return n.current_element
}
// Get the last child of the current element
pub fn (mut n Navigator) last_child() ?&MarkdownElement {
if n.current_element.children.len == 0 {
return none
}
n.current_element = n.current_element.children[n.current_element.children.len - 1]
return n.current_element
}
// Get all footnotes in the document
pub fn (n Navigator) footnotes() map[string]&MarkdownElement {
return n.doc.footnotes
}
// Get a footnote by identifier
pub fn (n Navigator) footnote(id string) ?&MarkdownElement {
if id in n.doc.footnotes {
return n.doc.footnotes[id]
}
return none
}

View File

@@ -0,0 +1,31 @@
module mlib2
// Parse a block-level element
fn (mut p Parser) parse_block() ?&MarkdownElement {
// Skip whitespace at the beginning of a line
p.skip_whitespace()
// Check for end of input
if p.pos >= p.text.len {
return none
}
// Check for different block types
if p.text[p.pos] == `#` {
return p.parse_heading()
} else if p.text[p.pos] == `>` {
return p.parse_blockquote()
} else if p.text[p.pos] == `-` && p.peek(1) == `-` && p.peek(2) == `-` {
return p.parse_horizontal_rule()
} else if p.text[p.pos] == '`' && p.peek(1) == '`' && p.peek(2) == '`' {
return p.parse_fenced_code_block()
} else if p.is_list_start() {
return p.parse_list()
} else if p.is_table_start() {
return p.parse_table()
} else if p.is_footnote_definition() {
return p.parse_footnote_definition()
} else {
return p.parse_paragraph()
}
}

View File

@@ -0,0 +1,97 @@
module mlib2
// Parse a blockquote element
fn (mut p Parser) parse_blockquote() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Skip the > character
p.pos++
p.column++
// Skip whitespace after >
p.skip_whitespace()
mut content := ''
mut lines := []string{}
// Read the first line
for p.pos < p.text.len && p.text[p.pos] != `\n` {
content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << content
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Read additional lines of the blockquote
for p.pos < p.text.len {
// Check if the line starts with >
if p.text[p.pos] == `>` {
p.pos++
p.column++
p.skip_whitespace()
mut line := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
line += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << line
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
} else if p.text[p.pos] == `\n` {
// Empty line - could be a continuation or the end of the blockquote
p.pos++
p.line++
p.column = 1
// Check if the next line is part of the blockquote
if p.pos < p.text.len && p.text[p.pos] == `>` {
lines << ''
} else {
break
}
} else {
// Not a blockquote line, end of blockquote
break
}
}
// Join the lines with newlines
content = lines.join('\n')
// Create the blockquote element
mut blockquote := &MarkdownElement{
typ: .blockquote
content: content
line_number: start_line
column: start_column
}
// Parse nested blocks within the blockquote
mut nested_parser := Parser{
text: content
pos: 0
line: start_line
column: start_column
}
nested_doc := nested_parser.parse()
blockquote.children = nested_doc.root.children
return blockquote
}

View File

@@ -0,0 +1,117 @@
module mlib2
// Parse a fenced code block element
fn (mut p Parser) parse_fenced_code_block() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Check for opening fence (``` or ~~~)
fence_char := p.text[p.pos]
if fence_char != '`' && fence_char != '~' {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Count fence characters
mut fence_len := 0
for p.pos < p.text.len && p.text[p.pos] == fence_char {
fence_len++
p.pos++
p.column++
}
// Must have at least 3 characters
if fence_len < 3 {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Read language identifier
mut language := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
language += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
language = language.trim_space()
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Read code content until closing fence
mut content := ''
mut found_closing_fence := false
for p.pos < p.text.len {
// Check for closing fence
if p.text[p.pos] == fence_char {
mut i := p.pos
mut count := 0
// Count fence characters
for i < p.text.len && p.text[i] == fence_char {
count++
i++
}
// Check if it's a valid closing fence
if count >= fence_len {
// Skip to end of line
for i < p.text.len && p.text[i] != `\n` {
i++
}
// Update position
p.pos = i
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
found_closing_fence = true
break
}
}
// Add character to content
content += p.text[p.pos].ascii_str()
// Move to next character
if p.text[p.pos] == `\n` {
p.line++
p.column = 1
} else {
p.column++
}
p.pos++
}
// If no closing fence was found, treat as paragraph
if !found_closing_fence {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Create the code block element
return &MarkdownElement{
typ: .code_block
content: content
line_number: start_line
column: start_column
attributes: {
'language': language
}
}
}

View File

@@ -0,0 +1,140 @@
module mlib2
// Parse a footnote definition
fn (mut p Parser) parse_footnote_definition() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Skip the [ character
p.pos++
p.column++
// Skip the ^ character
p.pos++
p.column++
// Read the footnote identifier
mut identifier := ''
for p.pos < p.text.len && p.text[p.pos] != `]` {
identifier += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
// Skip the ] character
if p.pos < p.text.len && p.text[p.pos] == `]` {
p.pos++
p.column++
} else {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Skip the : character
if p.pos < p.text.len && p.text[p.pos] == `:` {
p.pos++
p.column++
} else {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Skip whitespace
p.skip_whitespace()
// Read the footnote content
mut content := ''
mut lines := []string{}
// Read the first line
for p.pos < p.text.len && p.text[p.pos] != `\n` {
content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << content
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Read additional lines of the footnote
for p.pos < p.text.len {
// Check if the line is indented (part of the current footnote)
if p.text[p.pos] == ` ` || p.text[p.pos] == `\t` {
// Count indentation
mut indent := 0
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
indent++
p.pos++
p.column++
}
// If indented enough, it's part of the current footnote
if indent >= 2 {
mut line := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
line += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << line
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
} else {
// Not indented enough, end of footnote
break
}
} else if p.text[p.pos] == `\n` {
// Empty line - could be a continuation or the end of the footnote
p.pos++
p.line++
p.column = 1
// Check if the next line is indented
if p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
lines << ''
} else {
break
}
} else {
// Not an indented line, end of footnote
break
}
}
// Join the lines with newlines
content = lines.join('\n')
// Create the footnote element
mut footnote := &MarkdownElement{
typ: .footnote
content: content
line_number: start_line
column: start_column
attributes: {
'identifier': identifier
}
}
// Parse inline elements within the footnote
footnote.children = p.parse_inline(content)
// Add the footnote to the document
p.doc.footnotes[identifier] = footnote
return footnote
}

View File

@@ -0,0 +1,64 @@
module mlib2
// Parse a heading element
fn (mut p Parser) parse_heading() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Count the number of # characters
mut level := 0
for p.pos < p.text.len && p.text[p.pos] == `#` && level < 6 {
level++
p.pos++
p.column++
}
// Must be followed by a space
if p.pos >= p.text.len || (p.text[p.pos] != ` ` && p.text[p.pos] != `\t`) {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Skip whitespace after #
p.skip_whitespace()
// Read the heading text until end of line
mut content := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Trim trailing whitespace and optional closing #s
content = content.trim_right()
for content.ends_with('#') {
content = content.trim_right('#').trim_right()
}
// Create the heading element
mut heading := &MarkdownElement{
typ: .heading
content: content
line_number: start_line
column: start_column
attributes: {
'level': level.str()
}
}
// Parse inline elements within the heading
heading.children = p.parse_inline(content)
return heading
}

View File

@@ -0,0 +1,119 @@
module mlib2
// Helper function to peek ahead in the text
fn (p Parser) peek(offset int) byte {
if p.pos + offset >= p.text.len {
return 0
}
return p.text[p.pos + offset]
}
// Skip whitespace characters
fn (mut p Parser) skip_whitespace() {
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
p.pos++
p.column++
}
}
// Check if current position is the start of a list
fn (p Parser) is_list_start() bool {
if p.pos >= p.text.len {
return false
}
// Unordered list: *, -, +
if (p.text[p.pos] == `*` || p.text[p.pos] == `-` || p.text[p.pos] == `+`) &&
(p.peek(1) == ` ` || p.peek(1) == `\t`) {
return true
}
// Ordered list: 1., 2., etc.
if p.pos + 2 < p.text.len && p.text[p.pos].is_digit() {
mut i := p.pos + 1
for i < p.text.len && p.text[i].is_digit() {
i++
}
if i < p.text.len && p.text[i] == `.` && i + 1 < p.text.len && (p.text[i + 1] == ` ` || p.text[i + 1] == `\t`) {
return true
}
}
// Task list: - [ ], - [x], etc.
if p.pos + 4 < p.text.len &&
(p.text[p.pos] == `-` || p.text[p.pos] == `*` || p.text[p.pos] == `+`) &&
p.text[p.pos + 1] == ` ` && p.text[p.pos + 2] == `[` &&
(p.text[p.pos + 3] == ` ` || p.text[p.pos + 3] == `x` || p.text[p.pos + 3] == `X`) &&
p.text[p.pos + 4] == `]` {
return true
}
return false
}
// Check if current position is the start of a table
fn (p Parser) is_table_start() bool {
if p.pos >= p.text.len || p.text[p.pos] != `|` {
return false
}
// Look for a pipe character at the beginning of the line
// and check if there's at least one more pipe in the line
mut has_second_pipe := false
mut i := p.pos + 1
for i < p.text.len && p.text[i] != `\n` {
if p.text[i] == `|` {
has_second_pipe = true
break
}
i++
}
if !has_second_pipe {
return false
}
// Check if the next line has a header separator (---|---|...)
mut next_line_start := i + 1
if next_line_start >= p.text.len {
return false
}
// Skip whitespace at the beginning of the next line
for next_line_start < p.text.len && (p.text[next_line_start] == ` ` || p.text[next_line_start] == `\t`) {
next_line_start++
}
if next_line_start >= p.text.len || p.text[next_line_start] != `|` {
return false
}
// Check for pattern like |---|---|...
mut has_separator := false
mut j := next_line_start + 1
for j < p.text.len && p.text[j] != `\n` {
if p.text[j] == `-` {
has_separator = true
} else if p.text[j] == `|` {
// Reset for next column
has_separator = false
} else if p.text[j] != `:` && p.text[j] != ` ` && p.text[j] != `\t` {
// Only allow :, space, or tab besides - and |
return false
}
j++
}
return true
}
// Check if current position is a footnote definition
fn (p Parser) is_footnote_definition() bool {
if p.pos + 3 >= p.text.len {
return false
}
// Check for pattern like [^id]:
return p.text[p.pos] == `[` && p.text[p.pos + 1] == `^` &&
p.text[p.pos + 2] != `]` && p.text.index_after(']:', p.pos + 2) > p.pos + 2
}

View File

@@ -0,0 +1,58 @@
module mlib2
// Parse a horizontal rule element
fn (mut p Parser) parse_horizontal_rule() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Check for at least 3 of the same character (-, *, _)
char := p.text[p.pos]
if char != `-` && char != `*` && char != `_` {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
mut count := 0
for p.pos < p.text.len && p.text[p.pos] == char {
count++
p.pos++
p.column++
}
// Must have at least 3 characters
if count < 3 {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Skip whitespace
p.skip_whitespace()
// Must be at end of line
if p.pos < p.text.len && p.text[p.pos] != `\n` {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Create the horizontal rule element
return &MarkdownElement{
typ: .horizontal_rule
content: ''
line_number: start_line
column: start_column
}
}

View File

@@ -0,0 +1,22 @@
module mlib2
// Parse inline elements within a block
fn (mut p Parser) parse_inline(text string) []&MarkdownElement {
mut elements := []&MarkdownElement{}
// Simple implementation for now - just create a text element
if text.trim_space() != '' {
elements << &MarkdownElement{
typ: .text
content: text
line_number: 0
column: 0
}
}
// TODO: Implement parsing of inline elements like bold, italic, links, etc.
// This would involve scanning the text for markers like *, _, **, __, [, !, etc.
// and creating appropriate elements for each.
return elements
}

View File

@@ -0,0 +1,96 @@
module mlib2
// Parse a list element
fn (mut p Parser) parse_list() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Determine list type (ordered or unordered)
mut is_ordered := false
mut start_number := 1
mut marker := ''
if p.text[p.pos].is_digit() {
// Ordered list
is_ordered = true
// Parse start number
mut num_str := ''
for p.pos < p.text.len && p.text[p.pos].is_digit() {
num_str += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
start_number = num_str.int()
// Must be followed by a period
if p.pos >= p.text.len || p.text[p.pos] != `.` {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
marker = '.'
p.pos++
p.column++
} else {
// Unordered list
marker = p.text[p.pos].ascii_str()
p.pos++
p.column++
}
// Must be followed by whitespace
if p.pos >= p.text.len || (p.text[p.pos] != ` ` && p.text[p.pos] != `\t`) {
p.pos = start_pos
p.line = start_line
p.column = start_column
return p.parse_paragraph()
}
// Create the list element
mut list := &MarkdownElement{
typ: .list
content: ''
line_number: start_line
column: start_column
attributes: {
'ordered': is_ordered.str()
'start': start_number.str()
'marker': marker
}
}
// Parse list items
for {
// Parse list item
if item := p.parse_list_item(is_ordered, marker) {
list.children << item
} else {
break
}
// Check if we're at the end of the list
p.skip_whitespace()
if p.pos >= p.text.len {
break
}
// Check for next list item
if is_ordered {
if !p.text[p.pos].is_digit() {
break
}
} else {
if p.text[p.pos] != marker[0] {
break
}
}
}
return list
}

View File

@@ -0,0 +1,117 @@
module mlib2
// Parse a list item
fn (mut p Parser) parse_list_item(is_ordered bool, marker string) ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Skip whitespace
p.skip_whitespace()
// Check for task list item
mut is_task := false
mut is_completed := false
if p.pos + 3 < p.text.len && p.text[p.pos] == `[` &&
(p.text[p.pos + 1] == ` ` || p.text[p.pos + 1] == `x` || p.text[p.pos + 1] == `X`) &&
p.text[p.pos + 2] == `]` && (p.text[p.pos + 3] == ` ` || p.text[p.pos + 3] == `\t`) {
is_task = true
is_completed = p.text[p.pos + 1] == `x` || p.text[p.pos + 1] == `X`
p.pos += 3
p.column += 3
p.skip_whitespace()
}
// Read item content until end of line or next list item
mut content := ''
mut lines := []string{}
// Read the first line
for p.pos < p.text.len && p.text[p.pos] != `\n` {
content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << content
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Read additional lines of the list item
for p.pos < p.text.len {
// Check if the line is indented (part of the current item)
if p.text[p.pos] == ` ` || p.text[p.pos] == `\t` {
// Count indentation
mut indent := 0
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
indent++
p.pos++
p.column++
}
// If indented enough, it's part of the current item
if indent >= 2 {
mut line := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
line += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << line
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
} else {
// Not indented enough, end of list item
break
}
} else if p.text[p.pos] == `\n` {
// Empty line - could be a continuation or the end of the list item
p.pos++
p.line++
p.column = 1
// Check if the next line is indented
if p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
lines << ''
} else {
break
}
} else {
// Not an indented line, end of list item
break
}
}
// Join the lines with newlines
content = lines.join('\n')
// Create the list item element
mut item := &MarkdownElement{
typ: if is_task { .task_list_item } else { .list_item }
content: content
line_number: start_line
column: start_column
attributes: if is_task {
{
'completed': is_completed.str()
}
} else {
map[string]string{}
}
}
// Parse inline elements within the list item
item.children = p.parse_inline(content)
return item
}

View File

@@ -0,0 +1,49 @@
module mlib2
// Parser is responsible for parsing markdown text
struct Parser {
mut:
text string
pos int
line int
column int
doc MarkdownDocument
}
// Main parsing function
fn (mut p Parser) parse() MarkdownDocument {
p.doc = new_document()
// Parse blocks until end of input
for p.pos < p.text.len {
element := p.parse_block() or { break }
p.doc.root.children << element
}
// Process footnotes
p.process_footnotes()
return p.doc
}
// Process footnotes and add them to the document
fn (mut p Parser) process_footnotes() {
// Nothing to do if no footnotes
if p.doc.footnotes.len == 0 {
return
}
// Add a horizontal rule before footnotes
hr := &MarkdownElement{
typ: .horizontal_rule
content: ''
line_number: p.line
column: p.column
}
p.doc.root.children << hr
// Add footnotes section
for key, footnote in p.doc.footnotes {
p.doc.root.children << footnote
}
}

View File

@@ -0,0 +1,77 @@
module mlib2
// Parse a paragraph element
fn (mut p Parser) parse_paragraph() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
mut content := ''
mut lines := []string{}
// Read the first line
for p.pos < p.text.len && p.text[p.pos] != `\n` {
content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << content
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Read additional lines of the paragraph
for p.pos < p.text.len {
// Check if the line is empty (end of paragraph)
if p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
break
}
// Check if the line starts with a block element
if p.text[p.pos] == `#` || p.text[p.pos] == `>` ||
(p.text[p.pos] == `-` && p.peek(1) == `-` && p.peek(2) == `-`) ||
(p.text[p.pos] == `\`` && p.peek(1) == `\`` && p.peek(2) == `\``) ||
p.is_list_start() || p.is_table_start() || p.is_footnote_definition() {
break
}
// Read the line
mut line := ''
for p.pos < p.text.len && p.text[p.pos] != `\n` {
line += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
lines << line
// Skip the newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
}
// Join the lines with spaces
content = lines.join(' ')
// Create the paragraph element
mut paragraph := &MarkdownElement{
typ: .paragraph
content: content
line_number: start_line
column: start_column
}
// Parse inline elements within the paragraph
paragraph.children = p.parse_inline(content)
return paragraph
}

View File

@@ -0,0 +1,225 @@
module mlib2
// Parse a table element
fn (mut p Parser) parse_table() ?&MarkdownElement {
start_pos := p.pos
start_line := p.line
start_column := p.column
// Create the table element
mut table := &MarkdownElement{
typ: .table
content: ''
line_number: start_line
column: start_column
}
// Parse header row
mut header_row := &MarkdownElement{
typ: .table_row
content: ''
line_number: p.line
column: p.column
attributes: {
'is_header': 'true'
}
}
// Skip initial pipe if present
if p.text[p.pos] == `|` {
p.pos++
p.column++
}
// Parse header cells
for p.pos < p.text.len && p.text[p.pos] != `\n` {
// Parse cell content
mut cell_content := ''
for p.pos < p.text.len && p.text[p.pos] != `|` && p.text[p.pos] != `\n` {
cell_content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
// Create cell element
cell := &MarkdownElement{
typ: .table_cell
content: cell_content.trim_space()
line_number: p.line
column: p.column - cell_content.len
attributes: {
'is_header': 'true'
}
}
// Add cell to row
header_row.children << cell
// Skip pipe
if p.pos < p.text.len && p.text[p.pos] == `|` {
p.pos++
p.column++
} else {
break
}
}
// Skip newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Add header row to table
table.children << header_row
// Parse separator row (---|---|...)
// Skip initial pipe if present
if p.pos < p.text.len && p.text[p.pos] == `|` {
p.pos++
p.column++
}
// Parse alignment information
mut alignments := []string{}
for p.pos < p.text.len && p.text[p.pos] != `\n` {
// Skip whitespace
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
p.pos++
p.column++
}
// Check alignment
mut left_colon := false
mut right_colon := false
if p.pos < p.text.len && p.text[p.pos] == `:` {
left_colon = true
p.pos++
p.column++
}
// Skip dashes
for p.pos < p.text.len && p.text[p.pos] == `-` {
p.pos++
p.column++
}
if p.pos < p.text.len && p.text[p.pos] == `:` {
right_colon = true
p.pos++
p.column++
}
// Determine alignment
mut alignment := 'left' // default
if left_colon && right_colon {
alignment = 'center'
} else if right_colon {
alignment = 'right'
}
alignments << alignment
// Skip whitespace
for p.pos < p.text.len && (p.text[p.pos] == ` ` || p.text[p.pos] == `\t`) {
p.pos++
p.column++
}
// Skip pipe
if p.pos < p.text.len && p.text[p.pos] == `|` {
p.pos++
p.column++
} else {
break
}
}
// Skip newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Set alignment for header cells
for i, cell in header_row.children {
if i < alignments.len {
cell.attributes['align'] = alignments[i]
}
}
// Parse data rows
for p.pos < p.text.len && p.text[p.pos] != `\n` {
// Create row element
mut row := &MarkdownElement{
typ: .table_row
content: ''
line_number: p.line
column: p.column
}
// Skip initial pipe if present
if p.text[p.pos] == `|` {
p.pos++
p.column++
}
// Parse cells
mut cell_index := 0
for p.pos < p.text.len && p.text[p.pos] != `\n` {
// Parse cell content
mut cell_content := ''
for p.pos < p.text.len && p.text[p.pos] != `|` && p.text[p.pos] != `\n` {
cell_content += p.text[p.pos].ascii_str()
p.pos++
p.column++
}
// Create cell element
mut cell := &MarkdownElement{
typ: .table_cell
content: cell_content.trim_space()
line_number: p.line
column: p.column - cell_content.len
}
// Set alignment
if cell_index < alignments.len {
cell.attributes['align'] = alignments[cell_index]
}
// Add cell to row
row.children << cell
cell_index++
// Skip pipe
if p.pos < p.text.len && p.text[p.pos] == `|` {
p.pos++
p.column++
} else {
break
}
}
// Add row to table
table.children << row
// Skip newline
if p.pos < p.text.len && p.text[p.pos] == `\n` {
p.pos++
p.line++
p.column = 1
}
// Check if we're at the end of the table
if p.pos >= p.text.len || p.text[p.pos] != `|` {
break
}
}
return table
}

View File

@@ -0,0 +1,169 @@
module mlib2
// Renderer is the interface for all renderers
pub interface Renderer {
render(doc MarkdownDocument) string
}
// StructureRenderer renders a markdown document as a structure
pub struct StructureRenderer {
indent string = ' '
}
// Creates a new structure renderer
pub fn new_structure_renderer() StructureRenderer {
return StructureRenderer{}
}
// Render a markdown document as a structure
pub fn (r StructureRenderer) render(doc MarkdownDocument) string {
return r.render_element(doc.root, 0)
}
// Render an element as a structure
fn (r StructureRenderer) render_element(element &MarkdownElement, level int) string {
mut result := r.indent.repeat(level) + '${element.typ}'
if element.content.len > 0 {
// Truncate long content
mut content := element.content
if content.len > 50 {
content = content[0..47] + '...'
}
// Escape newlines
content = content.replace('\n', '\\n')
result += ': "${content}"'
}
if element.attributes.len > 0 {
result += ' {'
mut first := true
for key, value in element.attributes {
if !first {
result += ', '
}
result += '${key}: "${value}"'
first = false
}
result += '}'
}
result += '\n'
for child in element.children {
result += r.render_element(child, level + 1)
}
return result
}
// PlainTextRenderer renders a markdown document as plain text
pub struct PlainTextRenderer {}
// Creates a new plain text renderer
pub fn new_plain_text_renderer() PlainTextRenderer {
return PlainTextRenderer{}
}
// Render a markdown document as plain text
pub fn (r PlainTextRenderer) render(doc MarkdownDocument) string {
return r.render_element(doc.root)
}
// Render an element as plain text
fn (r PlainTextRenderer) render_element(element &MarkdownElement) string {
mut result := ''
match element.typ {
.document {
for child in element.children {
result += r.render_element(child)
if child.typ != .horizontal_rule {
result += '\n\n'
}
}
// Trim trailing newlines
result = result.trim_right('\n')
}
.heading {
level := element.attributes['level'].int()
result += '#'.repeat(level) + ' ' + element.content
}
.paragraph {
result += element.content
}
.blockquote {
lines := element.content.split('\n')
for line in lines {
result += '> ' + line + '\n'
}
result = result.trim_right('\n')
}
.code_block {
language := element.attributes['language']
result += '```${language}\n'
result += element.content
result += '```'
}
.list {
is_ordered := element.attributes['ordered'] == 'true'
start_number := element.attributes['start'].int()
mut i := start_number
for child in element.children {
if is_ordered {
result += '${i}. '
i++
} else {
result += '- '
}
result += r.render_element(child) + '\n'
}
result = result.trim_right('\n')
}
.list_item, .task_list_item {
if element.typ == .task_list_item {
is_completed := element.attributes['completed'] == 'true'
if is_completed {
result += '[x] '
} else {
result += '[ ] '
}
}
result += element.content
}
.table {
// TODO: Implement table rendering
result += '[Table with ${element.children.len} rows]'
}
.horizontal_rule {
result += '---'
}
.footnote {
identifier := element.attributes['identifier']
result += '[^${identifier}]: ${element.content}'
}
.text {
result += element.content
}
else {
result += element.content
}
}
return result
}
// Convenience function to render markdown text as a structure
pub fn to_structure(text string) string {
doc := parse(text)
mut renderer := new_structure_renderer()
return renderer.render(doc)
}
// Convenience function to render markdown text as plain text
pub fn to_plain(text string) string {
doc := parse(text)
mut renderer := new_plain_text_renderer()
return renderer.render(doc)
}

View File

@@ -37,7 +37,7 @@ fn (mut self DocBase) parent_doc() &Doc {
fn (mut self DocBase) remove_empty_children() {
self.children = self.children.filter(!(it.content == '' && it.children.len == 0
&& it.type_name in ['text', 'empty']))
&& it.type_name in ['paragraph','text', 'empty']))
}
pub fn (mut self DocBase) process() !int {

View File

@@ -1,7 +1,7 @@
module elements
import freeflowuniverse.herolib.core.texttools
// import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.ui.console
// DO NOT CHANGE THE WAY HOW THIS WORKS, THIS HAS BEEN DONE AS A STATEFUL PARSER BY DESIGN
// THIS ALLOWS FOR EASY ADOPTIONS TO DIFFERENT REALITIES
@@ -19,7 +19,7 @@ fn (mut paragraph Paragraph) paragraph_parse() ! {
mut llast := paragraph.children.last()
mut char_ := parser.char_current()
// console.print_debug("[[[${char_}]]]")
//console.print_debug("[[[${char_}]]]")
// char == '' means end of file
if mut llast is Def {

View File

@@ -186,7 +186,7 @@ fn (mut site DocSite) process_md(mut path pathlib.Path, args MyImport)!{
mypatho_img.copy(dest:dest,rsync:false)!
}
mut pathlist:=path.list()!
mut pathlist:=path.list(regex: [r'.*\.md$'],recursive:true)!
for mut mypatho2 in pathlist.paths{
site.process_md(mut mypatho2,args)!
}
@@ -195,14 +195,16 @@ fn (mut site DocSite) process_md(mut path pathlib.Path, args MyImport)!{
mydest:='${site.path_build.path}/docs/${args.dest}/${texttools.name_fix(path.name())}'
mut mydesto:=pathlib.get_file(path:mydest,create:true)!
println(path.path)
mut mymd:=markdownparser.new(path:path.path)!
mut myfm:=mymd.frontmatter2()!
if ! args.visible{
myfm.args["draft"]= 'true'
}
println(2)
// mut myfm:=mymd.frontmatter2()!
// if ! args.visible{
// myfm.args["draft"]= 'true'
// }
//println(myfm)
//println(mymd.markdown()!)
mydesto.write(mymd.markdown()!)!
// mydesto.write(mymd.markdown()!)!
//exit(0)
}