const DOC: &str = include_str!("../magic-lux.md");
pub struct Spell {
pub id: String,
pub title: String,
pub question: String,
pub example: String,
pub trail: Vec<String>,
}
const WIDTH: usize = 76;
pub fn spells() -> Vec<Spell> {
let marker = "<!-- spell:";
let mut out = Vec::new();
let mut rest = DOC;
while let Some(pos) = rest.find(marker) {
let after = &rest[pos + marker.len()..];
let id_end = after.find("-->").unwrap_or(0);
let id = after[..id_end].trim().to_string();
let body_start = after.find('\n').map(|i| i + 1).unwrap_or(after.len());
let body = &after[body_start..];
let next = body.find(marker).unwrap_or(body.len());
out.push(parse_spell(id, &body[..next]));
rest = &body[next..];
}
out
}
fn parse_spell(id: String, body: &str) -> Spell {
let mut lines = body.lines().peekable();
let mut title = String::new();
for line in lines.by_ref() {
if let Some(rest) = line.trim_start().strip_prefix("## ") {
title = rest.trim().to_string();
break;
}
}
let mut question = Vec::new();
while let Some(line) = lines.peek() {
if line.trim_start().starts_with("```") {
break;
}
let l = line.trim();
if !l.is_empty() {
question.push(l.to_string());
}
lines.next();
}
lines.next(); let mut example = Vec::new();
for line in lines.by_ref() {
if line.trim_start().starts_with("```") {
break;
}
example.push(line);
}
let mut trail = Vec::new();
for line in lines.by_ref() {
let l = line.trim();
if let Some(rest) = l.strip_prefix("> ") {
if let Some(ids) = rest.trim().strip_prefix("trail:") {
for piece in ids.split('·') {
let p = piece.trim();
if !p.is_empty() {
trail.push(p.to_string());
}
}
}
break;
}
}
Spell {
id,
title,
question: question.join(" "),
example: example.join("\n"),
trail,
}
}
fn summary(title: &str) -> String {
match title.split_once('—') {
Some((_, rest)) => rest.trim().to_string(),
None => title.to_string(),
}
}
fn intro() -> String {
let after_comment = match DOC.split_once("-->") {
Some((_, rest)) => rest,
None => DOC,
};
after_comment
.split("<!-- spell:")
.next()
.unwrap_or_default()
.trim()
.to_string()
}
fn render(s: &Spell) -> String {
let mut out = String::new();
out.push_str(&plain(&s.title));
out.push_str("\n\n");
out.push_str(&wrap(&plain(&s.question), WIDTH));
out.push_str("\n\n");
for line in s.example.lines() {
if line.is_empty() {
out.push('\n');
} else {
out.push_str(" ");
out.push_str(line);
out.push('\n');
}
}
if !s.trail.is_empty() {
let follow: Vec<String> = s.trail.iter().map(|t| format!("lux learn {}", t)).collect();
out.push('\n');
out.push_str(&wrap(
&format!("how it works: {}", follow.join(", ")),
WIDTH,
));
out.push('\n');
}
out
}
pub fn menu() -> String {
let spells = spells();
let mut out = String::new();
out.push_str("lux magic — spells for things you want to do now\n\n");
let tagline = tagline();
if !tagline.is_empty() {
out.push_str(&wrap(&tagline, WIDTH));
out.push_str("\n\n");
}
out.push_str("cast a spell:\n");
let width = spells.iter().map(|s| s.id.len()).max().unwrap_or(0);
for s in &spells {
out.push_str(&format!(
" lux magic {:<width$} {}\n",
s.id,
summary(&s.title),
width = width
));
}
out.push_str("\neach spell ends with how it works — a trail into `lux learn`,\n");
out.push_str("where the ideas it uses are explained.\n");
out
}
fn tagline() -> String {
let para = intro().split("\n\n").next().unwrap_or_default().to_string();
let unwrapped = para.split_whitespace().collect::<Vec<_>>().join(" ");
let plain = plain(&unwrapped);
match plain.find(". ") {
Some(i) => plain[..=i].trim().to_string(),
None => plain,
}
}
pub fn lookup(name: &str) -> Option<String> {
spells().iter().find(|s| s.id == name).map(render)
}
fn plain(s: &str) -> String {
s.chars().filter(|c| *c != '`' && *c != '*').collect()
}
fn wrap(text: &str, width: usize) -> String {
let mut out = String::new();
let mut line = String::new();
let mut has_word = false;
for word in text.split_whitespace() {
if has_word && line.len() + 1 + word.len() > width {
out.push_str(&line);
out.push('\n');
line = String::new();
has_word = false;
}
if has_word {
line.push(' ');
}
line.push_str(word);
has_word = true;
}
out.push_str(&line);
out
}