use std::collections::HashMap;
use anyhow::{Result, anyhow};
use easy_error::Error as BundError;
use rust_dynamic::value::Value;
use rust_multistackvm::multistackvm::VM;
use super::helpers::{active_store, pull, push, require_depth, value_to_string};
use crate::prose::ProseLanguage;
pub fn register(vm: &mut VM) -> Result<()> {
let words: &[(&str, fn(&mut VM) -> std::result::Result<&mut VM, BundError>)] = &[
("ink.poem.syllable_count", w_syllable_count),
("ink.poem.scan_line", w_scan_line),
("ink.poem.rhyme", w_rhyme),
("ink.poem.status", w_status),
("ink.poem.findings", w_findings),
("ink.poem.suppress", w_suppress),
];
for (name, f) in words {
vm.register_inline(name.to_string(), *f).map_err(|e| anyhow!("register {name}: {e}"))?;
}
for (name, _) in words {
if let Some(short) = name.strip_prefix("ink.") {
let _ = vm.register_alias(short.to_string(), name.to_string());
}
}
Ok(())
}
fn to_bund_err(e: anyhow::Error) -> BundError {
easy_error::err_msg(e.to_string())
}
fn w_syllable_count(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_syllable_count(vm).map_err(to_bund_err)
}
fn do_syllable_count(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.poem.syllable_count";
require_depth(vm, 2, tag)?;
let lang = value_to_string(pull(vm, tag)?, "lang", tag)?;
let word = value_to_string(pull(vm, tag)?, "word", tag)?;
let n = crate::poetry::syllabify::syllable_count(&word, ProseLanguage::from_label(&lang));
push(vm, Value::from_int(n as i64));
Ok(vm)
}
fn w_scan_line(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_scan_line(vm).map_err(to_bund_err)
}
fn do_scan_line(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.poem.scan_line";
require_depth(vm, 2, tag)?;
let lang = value_to_string(pull(vm, tag)?, "lang", tag)?;
let line = value_to_string(pull(vm, tag)?, "line", tag)?;
let beats = crate::poetry::metre::line_to_beats(&line, ProseLanguage::from_label(&lang));
let pattern: String = beats.iter().map(|b| b.glyph()).collect();
let metre = crate::poetry::metre::detect(&beats)
.map(|m| m.name)
.unwrap_or_else(|| "irregular".to_string());
let mut h = HashMap::new();
h.insert("pattern".to_string(), Value::from_string(pattern));
h.insert("syllables".to_string(), Value::from_int(beats.len() as i64));
h.insert("metre".to_string(), Value::from_string(metre));
push(vm, Value::from_dict(h));
Ok(vm)
}
fn w_rhyme(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_rhyme(vm).map_err(to_bund_err)
}
fn do_rhyme(vm: &mut VM) -> Result<&mut VM> {
use crate::poetry::rhyme::{RhymeQuality, RhymeType, analyse_rhyme};
let tag = "ink.poem.rhyme";
require_depth(vm, 3, tag)?;
let lang = value_to_string(pull(vm, tag)?, "lang", tag)?;
let w2 = value_to_string(pull(vm, tag)?, "word2", tag)?;
let w1 = value_to_string(pull(vm, tag)?, "word1", tag)?;
let r = analyse_rhyme(&w1, &w2, ProseLanguage::from_label(&lang));
let quality = match r.quality {
RhymeQuality::Perfect => "perfect",
RhymeQuality::Near => "near",
RhymeQuality::Eye => "eye",
RhymeQuality::None => "none",
};
let rtype = match r.rhyme_type {
RhymeType::Masculine => "masculine",
RhymeType::Feminine => "feminine",
RhymeType::Dactylic => "dactylic",
};
let mut h = HashMap::new();
h.insert("quality".to_string(), Value::from_string(quality.to_string()));
h.insert("type".to_string(), Value::from_string(rtype.to_string()));
h.insert("shared".to_string(), Value::from_string(r.shared));
if let Some(note) = r.note {
h.insert("note".to_string(), Value::from_string(note));
}
push(vm, Value::from_dict(h));
Ok(vm)
}
fn w_status(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_status(vm).map_err(to_bund_err)
}
fn do_status(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.poem.status";
require_depth(vm, 3, tag)?;
let lang = value_to_string(pull(vm, tag)?, "lang", tag)?;
let form_name = value_to_string(pull(vm, tag)?, "form", tag)?;
let text = value_to_string(pull(vm, tag)?, "text", tag)?;
let form = crate::poetry::form::FormsLibrary::builtin()
.localized(&form_name, &lang)
.ok_or_else(|| anyhow!("{tag}: unknown form `{form_name}`"))?;
let st = crate::poetry::form_check::check_form(&text, &form);
let mut h = HashMap::new();
h.insert("lines".to_string(), Value::from_int(st.lines_written as i64));
if let Some(e) = st.expected_lines {
h.insert("expected".to_string(), Value::from_int(e as i64));
}
h.insert("complete".to_string(), Value::from_bool(st.complete));
let issues: Vec<Value> = st.issues.into_iter().map(Value::from_string).collect();
h.insert("issues".to_string(), Value::from_list(issues));
push(vm, Value::from_dict(h));
Ok(vm)
}
fn w_findings(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_findings(vm).map_err(to_bund_err)
}
fn do_findings(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.poem.findings";
require_depth(vm, 1, tag)?;
let pid = value_to_string(pull(vm, tag)?, "paragraph_id", tag)?;
let pid = uuid::Uuid::parse_str(pid.trim())
.map_err(|_| anyhow!("{tag}: paragraph_id must be a UUID"))?;
let store = crate::inner_poet::storage::InnerPoetStore::open_for_project(
active_store(tag)?.project_root(),
)
.map_err(|e| anyhow!("{tag}: {e}"))?;
let items: Vec<Value> = store
.findings_for(pid)
.unwrap_or_default()
.iter()
.map(|f| {
let mut h = HashMap::new();
h.insert("severity".to_string(), Value::from_string(f.severity.clone()));
h.insert("kind".to_string(), Value::from_string(f.kind.clone()));
h.insert("line".to_string(), Value::from_int(f.line as i64));
h.insert("message".to_string(), Value::from_string(f.message.clone()));
h.insert("key".to_string(), Value::from_string(f.key()));
Value::from_dict(h)
})
.collect();
push(vm, Value::from_list(items));
Ok(vm)
}
fn w_suppress(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_suppress(vm).map_err(to_bund_err)
}
fn do_suppress(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.poem.suppress";
require_depth(vm, 2, tag)?;
let key = value_to_string(pull(vm, tag)?, "finding_key", tag)?;
let pid = value_to_string(pull(vm, tag)?, "paragraph_id", tag)?;
let pid = uuid::Uuid::parse_str(pid.trim())
.map_err(|_| anyhow!("{tag}: paragraph_id must be a UUID"))?;
let store = crate::inner_poet::storage::InnerPoetStore::open_for_project(
active_store(tag)?.project_root(),
)
.map_err(|e| anyhow!("{tag}: {e}"))?;
store.suppress(pid, key.trim()).map_err(|e| anyhow!("{tag}: {e}"))?;
push(vm, Value::from_bool(true));
Ok(vm)
}