feat(config): custom_wiki2html external converter + base_url for RSS (P2)
Add three per-wiki HtmlConfig keys mirroring vimwiki globals: - custom_wiki2html / custom_wiki2html_args: when set, export shells out to the external converter via `sh -c` with vimwiki's exact arg order (<cmd> <force> <syntax> <ext> <out_dir> <in_file> <css> <tpl_path> <tpl_default> <tpl_ext> <root_path> <args>, `-` for empty optionals) instead of the built-in renderer. - base_url: when set, diary RSS item + channel links become <base_url><diary_rel_path>/<date>.html instead of file:// URIs. write_page() gains a `force` flag threaded through export_current (false), export_all (its own flag) and the did_save auto-export path (false). Tests: write_page_invokes_custom_wiki2html_converter, write_rss_uses_base_url_for_public_links (html_export.rs), config round-trip + defaults (index_and_config.rs). Docs: README, doc/nuwiki.txt, lua config comment, vimwiki-gap.md item ticked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -229,6 +229,9 @@ require('nuwiki').setup({
|
|||||||
template_default = 'default',
|
template_default = 'default',
|
||||||
template_ext = '.tpl',
|
template_ext = '.tpl',
|
||||||
css_name = 'style.css',
|
css_name = 'style.css',
|
||||||
|
custom_wiki2html = '', -- external converter; empty = built-in renderer
|
||||||
|
custom_wiki2html_args = '', -- extra args appended to the converter call
|
||||||
|
base_url = '', -- public URL prefix for RSS item links
|
||||||
auto_export = false, -- export on save
|
auto_export = false, -- export on save
|
||||||
auto_toc = false, -- auto-refresh TOC on save
|
auto_toc = false, -- auto-refresh TOC on save
|
||||||
auto_generate_links = false, -- regen Generated Links on save
|
auto_generate_links = false, -- regen Generated Links on save
|
||||||
@@ -358,6 +361,9 @@ Each is a boolean. All default to `true` except `mouse`.
|
|||||||
| `css_name` | string | `'style.css'` | stylesheet filename |
|
| `css_name` | string | `'style.css'` | stylesheet filename |
|
||||||
| `auto_export` | bool | `false` | `true` \| `false` (export on save) |
|
| `auto_export` | bool | `false` | `true` \| `false` (export on save) |
|
||||||
| `html_filename_parameterization` | bool | `false` | `true` \| `false` |
|
| `html_filename_parameterization` | bool | `false` | `true` \| `false` |
|
||||||
|
| `custom_wiki2html` | string | `''` | external converter command; empty = built-in renderer. Called as `<cmd> <force> <syntax> <ext> <out_dir> <in_file> <css> <tpl_path> <tpl_default> <tpl_ext> <root_path> <args>` (`-` for empty optionals), matching vimwiki |
|
||||||
|
| `custom_wiki2html_args` | string | `''` | extra args appended to the `custom_wiki2html` call |
|
||||||
|
| `base_url` | string | `''` | public URL prefix; when set, diary RSS item links become `<base_url><diary_rel_path>/<date>.html` instead of `file://` |
|
||||||
| `exclude_files` | list | `{}` | glob patterns |
|
| `exclude_files` | list | `{}` | glob patterns |
|
||||||
| `color_dic` | table | `{}` | `{ name = 'color', … }` |
|
| `color_dic` | table | `{}` | `{ name = 'color', … }` |
|
||||||
|
|
||||||
|
|||||||
@@ -1143,7 +1143,7 @@ fn export_current(
|
|||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
let name = crate::index::page_name_from_uri(&p.uri, Some(&cfg.root));
|
let name = crate::index::page_name_from_uri(&p.uri, Some(&cfg.root));
|
||||||
let outcome = match crate::commands::export_ops::write_page(&doc.ast, &name, cfg) {
|
let outcome = match crate::commands::export_ops::write_page(&doc.ast, &name, cfg, false) {
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(e) => return Err(format!("nuwiki.export.currentToHtml: {e}")),
|
Err(e) => return Err(format!("nuwiki.export.currentToHtml: {e}")),
|
||||||
};
|
};
|
||||||
@@ -1222,7 +1222,7 @@ fn export_all(backend: &Backend, args: Vec<Value>, force: bool) -> Result<Option
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match crate::commands::export_ops::write_page(&ast, &name, &cfg) {
|
match crate::commands::export_ops::write_page(&ast, &name, &cfg, force) {
|
||||||
Ok(outcome) => exported.push(outcome.to_json()),
|
Ok(outcome) => exported.push(outcome.to_json()),
|
||||||
Err(e) => skipped.push(serde_json::json!({"uri": uri, "reason": e.to_string()})),
|
Err(e) => skipped.push(serde_json::json!({"uri": uri, "reason": e.to_string()})),
|
||||||
}
|
}
|
||||||
@@ -3342,6 +3342,7 @@ pub mod export_ops {
|
|||||||
doc: &DocumentNode,
|
doc: &DocumentNode,
|
||||||
name: &str,
|
name: &str,
|
||||||
cfg: &WikiConfig,
|
cfg: &WikiConfig,
|
||||||
|
force: bool,
|
||||||
) -> std::io::Result<PageExport> {
|
) -> std::io::Result<PageExport> {
|
||||||
if is_excluded(&cfg.html.exclude_files, name) {
|
if is_excluded(&cfg.html.exclude_files, name) {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
@@ -3349,6 +3350,11 @@ pub mod export_ops {
|
|||||||
format!("excluded by exclude_files: {name}"),
|
format!("excluded by exclude_files: {name}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// When a `custom_wiki2html` converter is configured, hand the page off
|
||||||
|
// to it instead of the built-in renderer (vimwiki parity).
|
||||||
|
if !cfg.html.custom_wiki2html.trim().is_empty() {
|
||||||
|
return run_custom_wiki2html(name, cfg, force);
|
||||||
|
}
|
||||||
let template_source = template_for(&cfg.html, doc);
|
let template_source = template_for(&cfg.html, doc);
|
||||||
let template = load_template(&template_source)
|
let template = load_template(&template_source)
|
||||||
.unwrap_or_else(|| fallback_template(&cfg.html.css_name));
|
.unwrap_or_else(|| fallback_template(&cfg.html.css_name));
|
||||||
@@ -3378,6 +3384,86 @@ pub mod export_ops {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `sh`-quote a path/string for the `custom_wiki2html` command line.
|
||||||
|
fn sh_quote(s: &str) -> String {
|
||||||
|
format!("'{}'", s.replace('\'', "'\\''"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run an external `custom_wiki2html` converter for `name`, mirroring
|
||||||
|
/// vimwiki's argument order:
|
||||||
|
/// script force syntax ext output_dir input_file css_file
|
||||||
|
/// template_path template_default template_ext root_path args
|
||||||
|
/// Empty optional args are passed as `-` (vimwiki convention). The script
|
||||||
|
/// is responsible for writing the output file; we return the path it is
|
||||||
|
/// expected to produce (`output_path_for`).
|
||||||
|
fn run_custom_wiki2html(
|
||||||
|
name: &str,
|
||||||
|
cfg: &WikiConfig,
|
||||||
|
force: bool,
|
||||||
|
) -> std::io::Result<PageExport> {
|
||||||
|
let h = &cfg.html;
|
||||||
|
let input = cfg.root.join(format!("{name}{}", cfg.file_extension));
|
||||||
|
std::fs::create_dir_all(&h.html_path)?;
|
||||||
|
let css = crate::export::css_path(h);
|
||||||
|
let ext = cfg.file_extension.trim_start_matches('.');
|
||||||
|
let dash = |s: &str| {
|
||||||
|
if s.is_empty() {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
|
s.to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tpl_path = if h.template_path.as_os_str().is_empty() {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
|
sh_quote(&h.template_path.display().to_string())
|
||||||
|
};
|
||||||
|
let root_path = {
|
||||||
|
let rp = crate::export::compute_root_path(name);
|
||||||
|
if rp.is_empty() {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
|
sh_quote(&rp)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let cmd = format!(
|
||||||
|
"{} {} {} {} {} {} {} {} {} {} {} {}",
|
||||||
|
h.custom_wiki2html,
|
||||||
|
if force { 1 } else { 0 },
|
||||||
|
cfg.syntax,
|
||||||
|
ext,
|
||||||
|
sh_quote(&h.html_path.display().to_string()),
|
||||||
|
sh_quote(&input.display().to_string()),
|
||||||
|
sh_quote(&css.display().to_string()),
|
||||||
|
tpl_path,
|
||||||
|
dash(&h.template_default),
|
||||||
|
dash(&h.template_ext),
|
||||||
|
root_path,
|
||||||
|
dash(h.custom_wiki2html_args.trim()),
|
||||||
|
);
|
||||||
|
let status = std::process::Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&cmd)
|
||||||
|
.status()
|
||||||
|
.map_err(|e| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("custom_wiki2html spawn failed: {e}"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("custom_wiki2html exited with {status}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(PageExport {
|
||||||
|
page: name.to_string(),
|
||||||
|
output_path: output_path_for(&cfg.html, name),
|
||||||
|
source_uri: tower_lsp::lsp_types::Url::from_file_path(&input).ok(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Two-tier template load. Returns the resolved template body, or
|
/// Two-tier template load. Returns the resolved template body, or
|
||||||
/// `None` if neither file exists — caller falls back to the built-in
|
/// `None` if neither file exists — caller falls back to the built-in
|
||||||
/// template.
|
/// template.
|
||||||
@@ -3423,12 +3509,29 @@ pub mod export_ops {
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
xml.push_str(&format!("<title>{}</title>\n", xml_escape(&cfg.name)));
|
xml.push_str(&format!("<title>{}</title>\n", xml_escape(&cfg.name)));
|
||||||
|
// `base_url` (per-wiki) prefixes the feed + item links so a hosted
|
||||||
|
// feed points at public URLs rather than local `file://` paths.
|
||||||
|
let base_url = cfg.html.base_url.trim();
|
||||||
|
if !base_url.is_empty() {
|
||||||
|
xml.push_str(&format!("<link>{}</link>\n", xml_escape(base_url)));
|
||||||
|
}
|
||||||
xml.push_str("<description>nuwiki diary</description>\n");
|
xml.push_str("<description>nuwiki diary</description>\n");
|
||||||
for e in sorted {
|
for e in sorted {
|
||||||
|
// With base_url set, build a public URL from the diary path + date;
|
||||||
|
// otherwise fall back to the entry's `file://` URI.
|
||||||
|
let link = if base_url.is_empty() {
|
||||||
|
e.uri.as_str().to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{base_url}{}/{}.html",
|
||||||
|
cfg.diary_rel_path.trim_matches('/'),
|
||||||
|
e.date.format()
|
||||||
|
)
|
||||||
|
};
|
||||||
xml.push_str("<item>");
|
xml.push_str("<item>");
|
||||||
xml.push_str(&format!("<title>{}</title>", e.date.format()));
|
xml.push_str(&format!("<title>{}</title>", e.date.format()));
|
||||||
xml.push_str(&format!("<link>{}</link>", e.uri.as_str()));
|
xml.push_str(&format!("<link>{}</link>", xml_escape(&link)));
|
||||||
xml.push_str(&format!("<guid>{}</guid>", e.uri.as_str()));
|
xml.push_str(&format!("<guid>{}</guid>", xml_escape(&link)));
|
||||||
xml.push_str("</item>\n");
|
xml.push_str("</item>\n");
|
||||||
}
|
}
|
||||||
xml.push_str("</channel></rss>\n");
|
xml.push_str("</channel></rss>\n");
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ pub struct HtmlConfig {
|
|||||||
/// renderer falls back to `class="color-<name>"` when a name
|
/// renderer falls back to `class="color-<name>"` when a name
|
||||||
/// isn't in the dict.
|
/// isn't in the dict.
|
||||||
pub color_dic: std::collections::HashMap<String, String>,
|
pub color_dic: std::collections::HashMap<String, String>,
|
||||||
|
/// External wiki→HTML converter (vimwiki's `custom_wiki2html`). When
|
||||||
|
/// non-empty, export shells out to it instead of the built-in renderer.
|
||||||
|
pub custom_wiki2html: String,
|
||||||
|
/// Extra args appended to the `custom_wiki2html` invocation.
|
||||||
|
pub custom_wiki2html_args: String,
|
||||||
|
/// URL prefix prepended to RSS feed / diary item links (`base_url`).
|
||||||
|
pub base_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HtmlConfig {
|
impl Default for HtmlConfig {
|
||||||
@@ -145,6 +152,9 @@ impl Default for HtmlConfig {
|
|||||||
html_filename_parameterization: false,
|
html_filename_parameterization: false,
|
||||||
exclude_files: Vec::new(),
|
exclude_files: Vec::new(),
|
||||||
color_dic: std::collections::HashMap::new(),
|
color_dic: std::collections::HashMap::new(),
|
||||||
|
custom_wiki2html: String::new(),
|
||||||
|
custom_wiki2html_args: String::new(),
|
||||||
|
base_url: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,6 +644,12 @@ struct RawWiki {
|
|||||||
exclude_files: Option<Vec<String>>,
|
exclude_files: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
color_dic: Option<std::collections::HashMap<String, String>>,
|
color_dic: Option<std::collections::HashMap<String, String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
custom_wiki2html: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
custom_wiki2html_args: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
base_url: Option<String>,
|
||||||
// per-wiki keys mirroring vimwiki globals. All optional so
|
// per-wiki keys mirroring vimwiki globals. All optional so
|
||||||
// existing single-wiki configs migrate without touching anything.
|
// existing single-wiki configs migrate without touching anything.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -720,6 +736,15 @@ impl From<RawWiki> for WikiConfig {
|
|||||||
if let Some(v) = r.color_dic {
|
if let Some(v) = r.color_dic {
|
||||||
html.color_dic = v;
|
html.color_dic = v;
|
||||||
}
|
}
|
||||||
|
if let Some(s) = r.custom_wiki2html {
|
||||||
|
html.custom_wiki2html = s;
|
||||||
|
}
|
||||||
|
if let Some(s) = r.custom_wiki2html_args {
|
||||||
|
html.custom_wiki2html_args = s;
|
||||||
|
}
|
||||||
|
if let Some(s) = r.base_url {
|
||||||
|
html.base_url = s;
|
||||||
|
}
|
||||||
let d = wiki_defaults();
|
let d = wiki_defaults();
|
||||||
WikiConfig {
|
WikiConfig {
|
||||||
name: r.name.unwrap_or(derived_name),
|
name: r.name.unwrap_or(derived_name),
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ impl LanguageServer for Backend {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let name = index::page_name_from_uri(&uri, Some(&wiki.config.root));
|
let name = index::page_name_from_uri(&uri, Some(&wiki.config.root));
|
||||||
let outcome = commands::export_ops::write_page(&doc.ast, &name, &wiki.config);
|
let outcome = commands::export_ops::write_page(&doc.ast, &name, &wiki.config, false);
|
||||||
drop(doc);
|
drop(doc);
|
||||||
match outcome {
|
match outcome {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ fn write_page_writes_html_to_disk() {
|
|||||||
let mut c = WikiConfig::from_root(root.clone());
|
let mut c = WikiConfig::from_root(root.clone());
|
||||||
c.html = HtmlConfig::from_root(&root);
|
c.html = HtmlConfig::from_root(&root);
|
||||||
let ast = parse("= Hello =\nworld\n");
|
let ast = parse("= Hello =\nworld\n");
|
||||||
let outcome = nuwiki_lsp::commands::export_ops::write_page(&ast, "Hello", &c).unwrap();
|
let outcome = nuwiki_lsp::commands::export_ops::write_page(&ast, "Hello", &c, false).unwrap();
|
||||||
assert_eq!(outcome.page, "Hello");
|
assert_eq!(outcome.page, "Hello");
|
||||||
assert!(outcome.output_path.exists());
|
assert!(outcome.output_path.exists());
|
||||||
let body = std::fs::read_to_string(&outcome.output_path).unwrap();
|
let body = std::fs::read_to_string(&outcome.output_path).unwrap();
|
||||||
@@ -442,7 +442,7 @@ fn write_page_creates_subdirs() {
|
|||||||
c.html = HtmlConfig::from_root(&root);
|
c.html = HtmlConfig::from_root(&root);
|
||||||
let ast = parse("= Daily =\n");
|
let ast = parse("= Daily =\n");
|
||||||
let outcome =
|
let outcome =
|
||||||
nuwiki_lsp::commands::export_ops::write_page(&ast, "diary/2026-05-11", &c).unwrap();
|
nuwiki_lsp::commands::export_ops::write_page(&ast, "diary/2026-05-11", &c, false).unwrap();
|
||||||
assert!(outcome.output_path.exists());
|
assert!(outcome.output_path.exists());
|
||||||
assert!(outcome
|
assert!(outcome
|
||||||
.output_path
|
.output_path
|
||||||
@@ -463,7 +463,7 @@ fn write_page_uses_template_file_when_present() {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ast = parse("%title T\n= H =\nbody\n");
|
let ast = parse("%title T\n= H =\nbody\n");
|
||||||
let outcome = nuwiki_lsp::commands::export_ops::write_page(&ast, "P", &c).unwrap();
|
let outcome = nuwiki_lsp::commands::export_ops::write_page(&ast, "P", &c, false).unwrap();
|
||||||
let body = std::fs::read_to_string(outcome.output_path).unwrap();
|
let body = std::fs::read_to_string(outcome.output_path).unwrap();
|
||||||
assert!(body.starts_with("<x>T|"));
|
assert!(body.starts_with("<x>T|"));
|
||||||
assert!(body.ends_with("</x>"));
|
assert!(body.ends_with("</x>"));
|
||||||
@@ -518,6 +518,60 @@ fn write_rss_emits_valid_xml_with_entries() {
|
|||||||
let i11 = xml.find("2026-05-11").unwrap();
|
let i11 = xml.find("2026-05-11").unwrap();
|
||||||
let i10 = xml.find("2026-05-10").unwrap();
|
let i10 = xml.find("2026-05-10").unwrap();
|
||||||
assert!(i11 < i10);
|
assert!(i11 < i10);
|
||||||
|
// No base_url → item links keep the file:// URI.
|
||||||
|
assert!(xml.contains("<link>file:///tmp/diary/2026-05-11.wiki</link>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_rss_uses_base_url_for_public_links() {
|
||||||
|
use nuwiki_lsp::diary::DiaryEntry;
|
||||||
|
use tower_lsp::lsp_types::Url;
|
||||||
|
|
||||||
|
let root = temp_root("rss_base");
|
||||||
|
let mut c = WikiConfig::from_root(root.clone());
|
||||||
|
c.html = HtmlConfig::from_root(&root);
|
||||||
|
c.html.base_url = "https://example.com/wiki/".into();
|
||||||
|
c.diary_rel_path = "diary".into();
|
||||||
|
let entries = vec![DiaryEntry {
|
||||||
|
date: DiaryDate::from_ymd(2026, 5, 11).unwrap(),
|
||||||
|
uri: Url::parse("file:///tmp/diary/2026-05-11.wiki").unwrap(),
|
||||||
|
}];
|
||||||
|
let path = nuwiki_lsp::commands::export_ops::write_rss(&c, &entries).unwrap();
|
||||||
|
let xml = std::fs::read_to_string(&path).unwrap();
|
||||||
|
// Channel + item links use the public base URL, not file://.
|
||||||
|
assert!(xml.contains("<link>https://example.com/wiki/</link>"));
|
||||||
|
assert!(xml.contains("<link>https://example.com/wiki/diary/2026-05-11.html</link>"));
|
||||||
|
assert!(xml.contains("<guid>https://example.com/wiki/diary/2026-05-11.html</guid>"));
|
||||||
|
assert!(!xml.contains("file:///tmp/diary"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_page_invokes_custom_wiki2html_converter() {
|
||||||
|
let root = temp_root("custom_w2h");
|
||||||
|
let mut c = WikiConfig::from_root(root.clone());
|
||||||
|
c.html = HtmlConfig::from_root(&root);
|
||||||
|
// Source file must exist on disk — the converter receives its path.
|
||||||
|
std::fs::write(root.join("Hello.wiki"), "= Hello =\nworld\n").unwrap();
|
||||||
|
// A tiny shell "converter": writes a marker into the output path it is told
|
||||||
|
// to produce. nuwiki appends args positionally after `_` ($0), so
|
||||||
|
// $1=force, $2=syntax, $3=ext, $4=output_dir, $5=input_file, …
|
||||||
|
let script = "mkdir -p \"$4\" && printf 'CUSTOM:%s' \"$5\" > \"$4\"/Hello.html";
|
||||||
|
c.html.custom_wiki2html = format!("sh -c {} _", shell_words_quote(script));
|
||||||
|
let outcome =
|
||||||
|
nuwiki_lsp::commands::export_ops::write_page(&parse("= Hello =\n"), "Hello", &c, true)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(outcome.page, "Hello");
|
||||||
|
assert!(outcome.output_path.exists(), "converter must produce output");
|
||||||
|
let body = std::fs::read_to_string(&outcome.output_path).unwrap();
|
||||||
|
assert!(
|
||||||
|
body.starts_with("CUSTOM:"),
|
||||||
|
"marker from converter, got {body}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal single-quote shell escaping for the test command above.
|
||||||
|
fn shell_words_quote(s: &str) -> String {
|
||||||
|
format!("'{}'", s.replace('\'', "'\\''"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== COMMANDS list completeness =====
|
// ===== COMMANDS list completeness =====
|
||||||
|
|||||||
@@ -284,6 +284,10 @@ fn defaults_carry_vimwiki_per_wiki_keys() {
|
|||||||
assert_eq!(cfg.links_header, "Generated Links");
|
assert_eq!(cfg.links_header, "Generated Links");
|
||||||
assert_eq!(cfg.tags_header, "Generated Tags");
|
assert_eq!(cfg.tags_header, "Generated Tags");
|
||||||
assert_eq!(cfg.listsym_rejected, "-");
|
assert_eq!(cfg.listsym_rejected, "-");
|
||||||
|
// No external converter / public base URL by default.
|
||||||
|
assert_eq!(cfg.html.custom_wiki2html, "");
|
||||||
|
assert_eq!(cfg.html.custom_wiki2html_args, "");
|
||||||
|
assert_eq!(cfg.html.base_url, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -312,6 +316,9 @@ fn raw_wiki_parses_every_new_key() {
|
|||||||
"links_header": "All Pages",
|
"links_header": "All Pages",
|
||||||
"tags_header": "Tag Index",
|
"tags_header": "Tag Index",
|
||||||
"tags_header_level": 3,
|
"tags_header_level": 3,
|
||||||
|
"custom_wiki2html": "~/bin/my_wiki2html.sh",
|
||||||
|
"custom_wiki2html_args": "--flag",
|
||||||
|
"base_url": "https://example.com/wiki/",
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
let w = &cfg.wikis[0];
|
let w = &cfg.wikis[0];
|
||||||
@@ -338,6 +345,11 @@ fn raw_wiki_parses_every_new_key() {
|
|||||||
assert_eq!(w.links_header_level, 1); // unspecified → default
|
assert_eq!(w.links_header_level, 1); // unspecified → default
|
||||||
assert_eq!(w.tags_header, "Tag Index");
|
assert_eq!(w.tags_header, "Tag Index");
|
||||||
assert_eq!(w.tags_header_level, 3);
|
assert_eq!(w.tags_header_level, 3);
|
||||||
|
// custom_wiki2html keeps the literal command string (tilde-expansion is
|
||||||
|
// the converter's job, not ours); args + base_url round-trip verbatim.
|
||||||
|
assert_eq!(w.html.custom_wiki2html, "~/bin/my_wiki2html.sh");
|
||||||
|
assert_eq!(w.html.custom_wiki2html_args, "--flag");
|
||||||
|
assert_eq!(w.html.base_url, "https://example.com/wiki/");
|
||||||
|
|
||||||
// The parsed keys drive a date-mode, Sunday-start diary calendar:
|
// The parsed keys drive a date-mode, Sunday-start diary calendar:
|
||||||
// a weekly note is the week-start date (YYYY-MM-DD), stepping ±7 days.
|
// a weekly note is the week-start date (YYYY-MM-DD), stepping ±7 days.
|
||||||
|
|||||||
@@ -118,8 +118,22 @@ fix site.
|
|||||||
custom rejected glyph both lexes and round-trips. Tests:
|
custom rejected glyph both lexes and round-trips. Tests:
|
||||||
`listsyms::custom_rejected_glyph_is_honoured` + config round-trip in
|
`listsyms::custom_rejected_glyph_is_honoured` + config round-trip in
|
||||||
`index_and_config.rs`.
|
`index_and_config.rs`.
|
||||||
- [ ] **`custom_wiki2html` (+`_args`)** and **`base_url`** — external HTML
|
- [x] **`custom_wiki2html` (+`_args`)** and **`base_url`** — external HTML
|
||||||
converter hook + export URL prefix. _Fix:_ `config.rs` `HtmlConfig`.
|
converter hook + export URL prefix. _Fix:_ `HtmlConfig` (`config.rs`) gained
|
||||||
|
`custom_wiki2html`, `custom_wiki2html_args`, `base_url` (all default `""`,
|
||||||
|
round-tripped through `RawWiki`/`From`/defaults). `write_page`
|
||||||
|
(`commands.rs`) now takes a `force` flag and, when `custom_wiki2html` is set,
|
||||||
|
hands the page to `run_custom_wiki2html()` — which shells out via `sh -c`
|
||||||
|
with vimwiki's exact arg order (`<cmd> <force> <syntax> <ext> <out_dir>
|
||||||
|
<in_file> <css> <tpl_path> <tpl_default> <tpl_ext> <root_path> <args>`, `-`
|
||||||
|
for empty optionals) instead of the built-in renderer. `force` is threaded
|
||||||
|
through `export_current` (false), `export_all` (its own flag), and the
|
||||||
|
`did_save` auto-export path (false). `write_rss` now prefixes the channel +
|
||||||
|
item links with `base_url` (`<base_url><diary_rel_path>/<date>.html`) when
|
||||||
|
set, falling back to the entry's `file://` URI otherwise. Tests:
|
||||||
|
`write_page_invokes_custom_wiki2html_converter`,
|
||||||
|
`write_rss_uses_base_url_for_public_links` (`html_export.rs`), config
|
||||||
|
round-trip + defaults (`index_and_config.rs`).
|
||||||
- [ ] **`map_prefix`** — `<Leader>w` hardcoded across the map layer.
|
- [ ] **`map_prefix`** — `<Leader>w` hardcoded across the map layer.
|
||||||
_Fix:_ `plugin/nuwiki.vim`, `lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`.
|
_Fix:_ `plugin/nuwiki.vim`, `lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`.
|
||||||
- [x] **`<M-CR>` badd-link** — was mouse-only (`<MiddleMouse>`) / command-only
|
- [x] **`<M-CR>` badd-link** — was mouse-only (`<MiddleMouse>`) / command-only
|
||||||
|
|||||||
@@ -194,6 +194,16 @@ match upstream vimwiki.
|
|||||||
`template_default` `'default'`
|
`template_default` `'default'`
|
||||||
`template_ext` `'.tpl'`
|
`template_ext` `'.tpl'`
|
||||||
`css_name` `'style.css'`
|
`css_name` `'style.css'`
|
||||||
|
`custom_wiki2html` `''` — external converter command. When set, export
|
||||||
|
shells out to it instead of the built-in renderer,
|
||||||
|
called as: <cmd> <force> <syntax> <ext> <out_dir>
|
||||||
|
<in_file> <css> <tpl_path> <tpl_default> <tpl_ext>
|
||||||
|
<root_path> <args> (`-` for empty optionals), matching
|
||||||
|
vimwiki's `g:vimwiki_custom_wiki2html` contract.
|
||||||
|
`custom_wiki2html_args` `''` — extra args appended to the converter call.
|
||||||
|
`base_url` `''` — public URL prefix. When set, diary RSS item
|
||||||
|
links become <base_url><diary_rel_path>/<date>.html
|
||||||
|
instead of local `file://` paths.
|
||||||
`auto_export` `false` — export to HTML on save.
|
`auto_export` `false` — export to HTML on save.
|
||||||
`auto_toc` `false` — refresh TOC on save.
|
`auto_toc` `false` — refresh TOC on save.
|
||||||
`auto_generate_links` `false` — regen an existing Generated Links section
|
`auto_generate_links` `false` — regen an existing Generated Links section
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ M.defaults = {
|
|||||||
-- html_path, template_path, template_default, template_ext,
|
-- html_path, template_path, template_default, template_ext,
|
||||||
-- template_date_format, css_name, auto_export, auto_toc,
|
-- template_date_format, css_name, auto_export, auto_toc,
|
||||||
-- auto_generate_links, auto_generate_tags, auto_diary_index,
|
-- auto_generate_links, auto_generate_tags, auto_diary_index,
|
||||||
-- html_filename_parameterization, exclude_files, color_dic
|
-- html_filename_parameterization, exclude_files, color_dic,
|
||||||
|
-- custom_wiki2html, custom_wiki2html_args, base_url,
|
||||||
-- toc_header, toc_header_level, links_header, links_header_level,
|
-- toc_header, toc_header_level, links_header, links_header_level,
|
||||||
-- tags_header, tags_header_level,
|
-- tags_header, tags_header_level,
|
||||||
-- listsyms, listsym_rejected, listsyms_propagate, list_margin,
|
-- listsyms, listsym_rejected, listsyms_propagate, list_margin,
|
||||||
|
|||||||
Reference in New Issue
Block a user