use std::{
collections::HashMap,
sync::{Arc, LazyLock},
};
use crate::{
ASCIIDOCTOR_VERSION,
document::InterpretedValue,
parser::{AllowableValue, AttributeValue, ModificationContext},
};
pub(crate) const DEFAULT_ICONSDIR: &str = "./images/icons";
static BUILT_IN_ATTRS: LazyLock<HashMap<String, AttributeValue>> =
LazyLock::new(build_built_in_attrs);
static DERIVED_FAMILY_FLAG: LazyLock<AttributeValue> = LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: true,
value: InterpretedValue::Value(String::new()),
});
static SAFE_MODE_ACTIVE_FLAG: LazyLock<AttributeValue> = LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: true,
value: InterpretedValue::Set,
});
static MAX_ATTRIBUTE_VALUE_SIZE_SECURE_DEFAULT: LazyLock<AttributeValue> =
LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: false,
value: InterpretedValue::Value("4096".to_owned()),
});
static MAX_ATTRIBUTE_VALUE_SIZE_RELAXED_DEFAULT: LazyLock<AttributeValue> =
LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: false,
value: InterpretedValue::Unset,
});
pub(crate) fn max_attribute_value_size_default(is_secure: bool) -> &'static AttributeValue {
if is_secure {
&MAX_ATTRIBUTE_VALUE_SIZE_SECURE_DEFAULT
} else {
&MAX_ATTRIBUTE_VALUE_SIZE_RELAXED_DEFAULT
}
}
static USER_HOME_MASKED: LazyLock<AttributeValue> = LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: false,
value: InterpretedValue::Value(".".to_owned()),
});
static USER_HOME_RESOLVED: LazyLock<AttributeValue> = LazyLock::new(|| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: false,
value: InterpretedValue::Value(resolve_user_home()),
});
fn resolve_user_home() -> String {
std::env::home_dir()
.filter(|p| !p.as_os_str().is_empty())
.or_else(|| std::env::current_dir().ok())
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| ".".to_owned())
}
pub(crate) fn user_home_default(is_below_server: bool) -> &'static AttributeValue {
if is_below_server {
&USER_HOME_RESOLVED
} else {
&USER_HOME_MASKED
}
}
static BUILT_IN_DEFAULT_VALUES: LazyLock<Arc<HashMap<String, String>>> =
LazyLock::new(|| Arc::new(build_built_in_default_values()));
pub(crate) fn built_in_attr(name: &str) -> Option<&'static AttributeValue> {
BUILT_IN_ATTRS.get(name)
}
pub(crate) fn built_in_attrs_iter()
-> impl Iterator<Item = (&'static String, &'static AttributeValue)> {
BUILT_IN_ATTRS.iter()
}
pub(crate) fn basebackend_of(backend: &str) -> &str {
backend.trim_end_matches(|c: char| c.is_ascii_digit())
}
pub(crate) fn filetype_of(backend: &str) -> String {
match basebackend_of(backend) {
"docbook" => "xml".to_owned(),
"manpage" => "man".to_owned(),
"asciidoc" => "adoc".to_owned(),
other => other.to_owned(),
}
}
pub(crate) fn is_derived_backend_value(name: &str) -> bool {
matches!(name, "basebackend" | "filetype")
}
fn stored_value(key: &str, overrides: &HashMap<String, AttributeValue>) -> Option<String> {
overrides
.get(key)
.or_else(|| BUILT_IN_ATTRS.get(key))
.and_then(|av| match &av.value {
InterpretedValue::Value(v) => Some(v.clone()),
_ => None,
})
}
pub(crate) fn derived_backend_value(
name: &str,
overrides: &HashMap<String, AttributeValue>,
) -> Option<InterpretedValue> {
let backend = stored_value("backend", overrides).filter(|b| !b.is_empty())?;
match name {
"basebackend" => Some(InterpretedValue::Value(basebackend_of(&backend).to_owned())),
"filetype" => Some(InterpretedValue::Value(filetype_of(&backend))),
_ => None,
}
}
pub(crate) fn synthesized_attr(
name: &str,
overrides: &HashMap<String, AttributeValue>,
) -> Option<&'static AttributeValue> {
if name.starts_with("backend-")
|| name.starts_with("basebackend-")
|| name.starts_with("filetype-")
|| name.starts_with("doctype-")
{
let backend = stored_value("backend", overrides).filter(|b| !b.is_empty());
let doctype = stored_value("doctype", overrides).filter(|d| !d.is_empty());
let mut is_active = false;
if let Some(doctype) = &doctype {
is_active = name == format!("doctype-{doctype}");
}
if !is_active && let Some(backend) = &backend {
let basebackend = basebackend_of(backend);
let filetype = filetype_of(backend);
is_active = name == format!("backend-{backend}")
|| name == format!("basebackend-{basebackend}")
|| name == format!("filetype-{filetype}");
if !is_active && let Some(doctype) = &doctype {
is_active = name == format!("backend-{backend}-doctype-{doctype}")
|| name == format!("basebackend-{basebackend}-doctype-{doctype}");
}
}
if is_active {
return Some(&*DERIVED_FAMILY_FLAG);
}
}
if let Some(suffix) = name.strip_prefix("safe-mode-")
&& matches!(suffix, "unsafe" | "safe" | "server" | "secure")
{
let active = stored_value("safe-mode-name", overrides).as_deref() == Some(suffix);
return active.then(|| &*SAFE_MODE_ACTIVE_FLAG);
}
None
}
pub(super) fn built_in_default_values() -> Arc<HashMap<String, String>> {
BUILT_IN_DEFAULT_VALUES.clone()
}
fn build_built_in_attrs() -> HashMap<String, AttributeValue> {
let mut attrs: HashMap<String, AttributeValue> = HashMap::new();
let char_replacement = |value: &str| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ModificationContext::ApiOnly,
silent_when_locked: false,
value: InterpretedValue::Value(value.into()),
};
attrs.insert("blank".to_owned(), char_replacement(""));
attrs.insert("empty".to_owned(), char_replacement(""));
attrs.insert("sp".to_owned(), char_replacement(" "));
attrs.insert("nbsp".to_owned(), char_replacement(" "));
attrs.insert("zwsp".to_owned(), char_replacement("​"));
attrs.insert("wj".to_owned(), char_replacement("⁠"));
attrs.insert("apos".to_owned(), char_replacement("'"));
attrs.insert("quot".to_owned(), char_replacement("""));
attrs.insert("lsquo".to_owned(), char_replacement("‘"));
attrs.insert("rsquo".to_owned(), char_replacement("’"));
attrs.insert("ldquo".to_owned(), char_replacement("“"));
attrs.insert("rdquo".to_owned(), char_replacement("”"));
attrs.insert("deg".to_owned(), char_replacement("°"));
attrs.insert("plus".to_owned(), char_replacement("+"));
attrs.insert("brvbar".to_owned(), char_replacement("¦"));
attrs.insert("vbar".to_owned(), char_replacement("|"));
attrs.insert("amp".to_owned(), char_replacement("&"));
attrs.insert("lt".to_owned(), char_replacement("<"));
attrs.insert("gt".to_owned(), char_replacement(">"));
attrs.insert("startsb".to_owned(), char_replacement("["));
attrs.insert("endsb".to_owned(), char_replacement("]"));
attrs.insert("caret".to_owned(), char_replacement("^"));
attrs.insert("asterisk".to_owned(), char_replacement("*"));
attrs.insert("tilde".to_owned(), char_replacement("~"));
attrs.insert("backslash".to_owned(), char_replacement("\\"));
attrs.insert("backtick".to_owned(), char_replacement("`"));
attrs.insert("two-colons".to_owned(), char_replacement("::"));
attrs.insert("two-semicolons".to_owned(), char_replacement(";;"));
attrs.insert("cpp".to_owned(), char_replacement("C++"));
attrs.insert("cxx".to_owned(), char_replacement("C++"));
attrs.insert("pp".to_owned(), char_replacement("++"));
use InterpretedValue::{Set, Unset, Value};
use ModificationContext::{Anywhere, ApiOnly, ApiOrHeader};
let any = |ctx, value| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ctx,
silent_when_locked: false,
value,
};
let set = |ctx, default: &str| AttributeValue {
allowable_value: AllowableValue::Any,
modification_context: ctx,
silent_when_locked: false,
value: Value(default.to_owned()),
};
let empty = |ctx, value| AttributeValue {
allowable_value: AllowableValue::Empty,
modification_context: ctx,
silent_when_locked: false,
value,
};
attrs.insert("attribute-missing".to_owned(), set(Anywhere, "skip"));
attrs.insert("attribute-undefined".to_owned(), set(Anywhere, "drop-line"));
attrs.insert("appendix-caption".to_owned(), set(Anywhere, "Appendix"));
attrs.insert("appendix-refsig".to_owned(), set(Anywhere, "Appendix"));
attrs.insert("caution-caption".to_owned(), set(Anywhere, "Caution"));
attrs.insert("chapter-refsig".to_owned(), set(Anywhere, "Chapter"));
attrs.insert("example-caption".to_owned(), set(Anywhere, "Example"));
attrs.insert("figure-caption".to_owned(), set(Anywhere, "Figure"));
attrs.insert("important-caption".to_owned(), set(Anywhere, "Important"));
attrs.insert(
"last-update-label".to_owned(),
set(ApiOrHeader, "Last updated"),
);
attrs.insert("note-caption".to_owned(), set(Anywhere, "Note"));
attrs.insert("part-refsig".to_owned(), set(Anywhere, "Part"));
attrs.insert("section-refsig".to_owned(), set(Anywhere, "Section"));
attrs.insert("table-caption".to_owned(), set(Anywhere, "Table"));
attrs.insert("tip-caption".to_owned(), set(Anywhere, "Tip"));
attrs.insert(
"toc-title".to_owned(),
set(ApiOrHeader, "Table of Contents"),
);
attrs.insert("untitled-label".to_owned(), set(ApiOrHeader, "Untitled"));
attrs.insert("version-label".to_owned(), set(ApiOrHeader, "Version"));
attrs.insert("warning-caption".to_owned(), set(Anywhere, "Warning"));
attrs.insert("idprefix".to_owned(), any(Anywhere, Value("_".into())));
attrs.insert("idseparator".to_owned(), any(Anywhere, Value("_".into())));
attrs.insert("sectids".to_owned(), empty(Anywhere, Set));
attrs.insert("sectnums".to_owned(), empty(Anywhere, Unset));
attrs.insert(
"sectnumlevels".to_owned(),
any(ApiOrHeader, Value("3".into())),
);
attrs.insert("toc".to_owned(), any(ApiOrHeader, Unset));
attrs.insert("authorcount".to_owned(), set(Anywhere, "0"));
attrs.insert("backend".to_owned(), any(Anywhere, Value("html5".into())));
attrs.insert(
"doctype".to_owned(),
any(ApiOrHeader, Value("article".into())),
);
attrs.insert(
"outfilesuffix".to_owned(),
any(ApiOrHeader, Value(".html".into())),
);
attrs.insert("webfonts".to_owned(), empty(ApiOrHeader, Set));
attrs.insert("iconfont-remote".to_owned(), empty(ApiOrHeader, Set));
attrs.insert("iconsdir".to_owned(), set(Anywhere, DEFAULT_ICONSDIR));
attrs.insert("imagesdir".to_owned(), any(Anywhere, Set));
attrs.insert("prewrap".to_owned(), empty(Anywhere, Set));
attrs.insert("copycss".to_owned(), any(ApiOrHeader, Set));
attrs.insert("stylesdir".to_owned(), set(ApiOrHeader, "."));
attrs.insert("stylesheet".to_owned(), any(ApiOrHeader, Set));
attrs.insert("max-include-depth".to_owned(), set(ApiOnly, "64"));
attrs.insert("max-block-nesting".to_owned(), set(ApiOnly, "32"));
attrs.insert(
"asciidoc-parser-version".to_owned(),
set(ApiOnly, env!("CARGO_PKG_VERSION")),
);
attrs.insert(
"asciidoctor-version".to_owned(),
set(ApiOnly, ASCIIDOCTOR_VERSION),
);
attrs.insert("asciidoctor".to_owned(), empty(ApiOnly, Set));
attrs.insert(
"safe-mode-level".to_owned(),
any(ApiOnly, Value("20".into())),
);
attrs.insert(
"safe-mode-name".to_owned(),
any(ApiOnly, Value("secure".into())),
);
attrs
}
fn build_built_in_default_values() -> HashMap<String, String> {
let mut defaults: HashMap<String, String> = HashMap::new();
defaults.insert("sectnums".to_owned(), "all".to_owned());
defaults.insert("toc".to_owned(), "auto".to_owned());
defaults.insert("toclevels".to_owned(), "2".to_owned());
defaults.insert("appendix-number".to_owned(), "@".to_owned());
defaults.insert("chapter-number".to_owned(), "0".to_owned());
defaults.insert("example-number".to_owned(), "0".to_owned());
defaults.insert("figure-number".to_owned(), "0".to_owned());
defaults.insert("footnote-number".to_owned(), "0".to_owned());
defaults.insert("listing-number".to_owned(), "0".to_owned());
defaults.insert("table-number".to_owned(), "0".to_owned());
defaults.insert("lang".to_owned(), "en".to_owned());
defaults.insert("manname-title".to_owned(), "Name".to_owned());
defaults.insert("asset-uri-scheme".to_owned(), "https".to_owned());
defaults.insert("docinfo".to_owned(), "private".to_owned());
defaults.insert("eqnums".to_owned(), "AMS".to_owned());
defaults.insert("media".to_owned(), "screen".to_owned());
defaults.insert("pagewidth".to_owned(), "425".to_owned());
defaults.insert("stem".to_owned(), "asciimath".to_owned());
defaults.insert("table-frame".to_owned(), "all".to_owned());
defaults.insert("table-grid".to_owned(), "all".to_owned());
defaults.insert("table-stripes".to_owned(), "none".to_owned());
defaults.insert("iconfont-name".to_owned(), "font-awesome".to_owned());
defaults.insert("icons".to_owned(), "image".to_owned());
defaults.insert("icontype".to_owned(), "png".to_owned());
defaults.insert("coderay-css".to_owned(), "class".to_owned());
defaults.insert("coderay-linenums-mode".to_owned(), "table".to_owned());
defaults.insert("highlightjs-theme".to_owned(), "github".to_owned());
defaults.insert("prettify-theme".to_owned(), "prettify".to_owned());
defaults.insert("pygments-css".to_owned(), "class".to_owned());
defaults.insert("pygments-linenums-mode".to_owned(), "table".to_owned());
defaults.insert("pygments-style".to_owned(), "default".to_owned());
defaults.insert("rouge-css".to_owned(), "class".to_owned());
defaults.insert("rouge-linenums-mode".to_owned(), "table".to_owned());
defaults.insert("rouge-style".to_owned(), "github".to_owned());
defaults.insert("toc-class".to_owned(), "toc".to_owned());
defaults.insert("man-linkstyle".to_owned(), "blue R <>".to_owned());
defaults
}