mirror of
https://github.com/inttter/md-badges.git
synced 2026-05-06 18:36:58 +02:00
♻️ Make playground parse README.md bulk-selectable and copyable using Rust for better performance and showcase
Rust was chosen for its speed, memory safety, and ability to handle large text efficiently. This change improves UX by enabling bulk selection and copying of README content, while also serving as a demonstration of Rust integration in the playground.
This commit is contained in:
parent
3c461b0923
commit
6406b47c9b
5 changed files with 1279 additions and 0 deletions
25
playground/rust/.gitignore
vendored
Normal file
25
playground/rust/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating libraries
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# IntelliJ project files
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# VS Code settings
|
||||
.vscode/
|
||||
|
||||
# Byebug
|
||||
.byebug_history
|
||||
|
||||
# MacOS files
|
||||
.DS_Store
|
||||
|
||||
# Node modules (if using node for frontend)
|
||||
node_modules/
|
||||
13
playground/rust/Cargo.toml
Normal file
13
playground/rust/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "md_badges_parser"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
pulldown-cmark = "0.9"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6"
|
||||
145
playground/rust/src/lib.rs
Normal file
145
playground/rust/src/lib.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
use pulldown_cmark::{Event, HeadingLevel, Parser, Tag, Options};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct BadgeItem {
|
||||
pub preview_md: String,
|
||||
pub code_md: String,
|
||||
pub preview_url_md: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Section {
|
||||
pub title: String,
|
||||
pub items: Vec<BadgeItem>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ParseResult {
|
||||
pub sections: Vec<Section>,
|
||||
}
|
||||
|
||||
fn is_separator_row(row: &[String]) -> bool {
|
||||
row.iter().all(|cell| {
|
||||
cell.chars().all(|c| c == '-' || c == ':' || c == ' ')
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_image_url(md: &str) -> Option<String> {
|
||||
let start = md.find("](")? + 2;
|
||||
let end = md[start..].find(')')? + start;
|
||||
Some(md[start..end].to_string())
|
||||
}
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_readme(_markdown: &str) -> JsValue {
|
||||
let mut options = Options::empty();
|
||||
options.insert(Options::ENABLE_TABLES);
|
||||
let parser = Parser::new_ext(_markdown, options);
|
||||
|
||||
let mut sections: Vec<Section> = Vec::new();
|
||||
let mut current_section: Option<Section> = None;
|
||||
|
||||
let mut in_heading = false;
|
||||
let mut heading_text = String::new();
|
||||
let mut heading_level: Option<HeadingLevel> = None;
|
||||
|
||||
let mut in_cell = false;
|
||||
let mut is_target_table = false;
|
||||
|
||||
let mut current_row: Vec<String> = Vec::new();
|
||||
let mut current_cell = String::new();
|
||||
|
||||
let mut header_cells: Vec<String> = Vec::new();
|
||||
let mut reading_header = false;
|
||||
|
||||
for event in parser {
|
||||
match event {
|
||||
Event::Start(Tag::Heading(level, ..)) => {
|
||||
heading_text.clear();
|
||||
heading_level = Some(level);
|
||||
in_heading = true;
|
||||
}
|
||||
Event::Text(text) if in_heading => {
|
||||
heading_text.push_str(&text);
|
||||
}
|
||||
Event::End(Tag::Heading(_, ..)) => {
|
||||
in_heading = false;
|
||||
|
||||
if heading_level == Some(HeadingLevel::H3)
|
||||
&& heading_text.trim() != "Table Of Contents" {
|
||||
if let Some(section) = current_section.take() {
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
current_section = Some(Section {
|
||||
title: heading_text.trim().to_string(),
|
||||
items: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Event::Start(Tag::Table(_)) => {
|
||||
header_cells.clear();
|
||||
reading_header = true;
|
||||
is_target_table = false;
|
||||
}
|
||||
Event::End(Tag::Table(_)) => {
|
||||
is_target_table = false;
|
||||
}
|
||||
Event::Start(Tag::TableRow) => {
|
||||
current_row.clear();
|
||||
}
|
||||
Event::Start(Tag::TableCell) => {
|
||||
current_cell.clear();
|
||||
in_cell = true;
|
||||
}
|
||||
Event::Text(text) if in_cell => {
|
||||
current_cell.push_str(&text);
|
||||
}
|
||||
Event::Code(code) if in_cell => {
|
||||
current_cell.push_str(&code);
|
||||
}
|
||||
|
||||
Event::End(Tag::TableCell) => {
|
||||
in_cell = false;
|
||||
current_row.push(current_cell.trim().to_string());
|
||||
}
|
||||
Event::End(Tag::TableRow) => {
|
||||
if is_separator_row(¤t_row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if reading_header {
|
||||
header_cells = current_row.clone();
|
||||
reading_header = false;
|
||||
is_target_table = header_cells.len() == 2;
|
||||
|
||||
}
|
||||
|
||||
if is_target_table {
|
||||
if let Some(section) = current_section.as_mut() {
|
||||
if current_row.len() == 2 {
|
||||
section.items.push(BadgeItem {
|
||||
preview_md: current_row[0].clone(),
|
||||
code_md: current_row[1].clone(),
|
||||
preview_url_md: extract_image_url(¤t_row[1]).unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(section) = current_section {
|
||||
sections.push(section);
|
||||
}
|
||||
let result = ParseResult {
|
||||
sections,
|
||||
};
|
||||
serde_wasm_bindgen::to_value(&result).unwrap()
|
||||
}
|
||||
23
playground/rust/src/main.rs
Normal file
23
playground/rust/src/main.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use std::fs;
|
||||
use pulldown_cmark::{Parser, Event, Tag, Options};
|
||||
|
||||
fn main() {
|
||||
let md = fs::read_to_string("./test/README.md")
|
||||
.expect("Failed to read README.md");
|
||||
|
||||
let mut options = Options::empty();
|
||||
options.insert(Options::ENABLE_TABLES);
|
||||
|
||||
let parser = Parser::new_ext(&md, options);
|
||||
|
||||
for event in parser {
|
||||
match &event {
|
||||
Event::Start(Tag::Table(_)) => println!("== TABLE START =="),
|
||||
Event::Start(Tag::TableRow) => println!("ROW START"),
|
||||
Event::Start(Tag::TableCell) => println!("CELL START"),
|
||||
Event::Code(code) => println!("CODE: {:?}", code),
|
||||
Event::Text(t) => println!("TEXT: {:?}", t),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue