Fix links to AoN

This commit is contained in:
2026-05-17 22:42:37 +02:00
parent 5bd0efe95a
commit 2b095b0301
8 changed files with 747 additions and 10 deletions

View File

@@ -46,7 +46,7 @@ pub struct Creature {
pub legacy_id: Option<Vec<String>>,
}
fn fix_action_icons(input: String) -> String {
fn fix_action_icons(input: &str) -> String {
input
.replace(r#"<actions string="Free Action" />"#, "`[free-action]`")
.replace(r#"<actions string="Single Action" />"#, "`[one-action]`")
@@ -55,10 +55,30 @@ fn fix_action_icons(input: String) -> String {
.replace(r#"<actions string="Reaction" />"#, "`[reaction]`")
}
fn fix_html(s: String) -> String {
/// Replace relative links with absolute links to Archives of Nethys (<https://2e.aonprd.com/>).
fn fix_links(s: &str) -> String {
let markdown_link_ro = Regex::new(
r#"(?x)
\[
(?P<title>[^\[\]]*)
\]
\(
(?P<link>[^\(\)]*)
\)
"#,
)
.unwrap();
markdown_link_ro
.replace_all(&s, "[$title](https://2e.aonprd.com$link)")
.to_string()
}
fn fix_html(s: &str) -> String {
let html_tags_re = Regex::new(r"<[^<>]*>").unwrap();
let s = fix_action_icons(s);
let s = fix_links(&s);
let s = s.replace("<br />", "\n");
let s = html_tags_re.replace_all(&s, "");
@@ -66,7 +86,7 @@ fn fix_html(s: String) -> String {
}
fn split_sections(s: &str) -> Vec<String> {
let s = fix_html(s.to_string());
let s = fix_html(s);
let re_split = Regex::new(r"\r?\n\s*\r?\n").unwrap();
re_split.split(&s).map(String::from).collect()
}
@@ -88,7 +108,7 @@ fn parse_ability_section(
let processed: Vec<String> = relevant_sections
.map(|s| s.replace("\r", ""))
.map(|s| fix_action_icons(s.to_string()))
.map(|s| fix_action_icons(&s))
.map(|s| s.replace('\n', " "))
.collect();
@@ -117,7 +137,7 @@ fn parse_defensive_abilities(markdown: &str) -> String {
let processed: Vec<String> = relevant_sections
.map(|s| s.replace("\r", ""))
.map(|s| fix_action_icons(s.to_string()))
.map(|s| fix_action_icons(&s))
.map(|s| s.replace('\n', " "))
.collect();
@@ -128,7 +148,7 @@ fn parse_general_abilities(markdown: &str) -> String {
parse_ability_section(
markdown,
|s| s.starts_with("**Cha**"),
|s| s.starts_with("**Fort**"),
|s| s.starts_with("**AC**"),
)
}
@@ -166,7 +186,7 @@ pub fn creature_to_obsidian(sb: &Creature) -> String {
]
.into_iter()
.filter_map(|(prefix, list)| Some(prefix).zip(list.as_deref()))
.map(|(prefix, list)| format!("**{prefix}** {list}"))
.map(|(prefix, list)| format!("**{prefix}** {}", fix_html(list)))
.fold(hp_text, |b, s| b + "; " + &s);
let general_abilities = parse_general_abilities(&sb.markdown);
@@ -184,12 +204,12 @@ pub fn creature_to_obsidian(sb: &Creature) -> String {
&mut s,
"\n**Perception** {}; {}\n",
plussed(sb.perception),
&sb.sense_markdown.as_deref().unwrap_or(""), // TODO
fix_html(&sb.sense_markdown.as_deref().unwrap_or("")), // TODO
);
_ = writeln!(
&mut s,
"\n**Skills**: {}\n",
sb.skill_markdown.as_deref().unwrap_or_default() // TODO
fix_html(sb.skill_markdown.as_deref().unwrap_or_default()) // TODO
);
_ = writeln!(
&mut s,
@@ -219,10 +239,41 @@ pub fn creature_to_obsidian(sb: &Creature) -> String {
_ = writeln!(
&mut s,
"\n**Speed**: {}\n",
&sb.speed_markdown.as_deref().unwrap_or_default() // TODO
fix_html(&sb.speed_markdown.as_deref().unwrap_or_default()) // TODO
);
_ = writeln!(&mut s, "{}", offensive_abilities);
_ = writeln!(&mut s, "\n```");
s
}
#[cfg(test)]
mod tests {
use crate::creature::{Creature, creature_to_obsidian};
const EXAMPLE_CREATURE_1: &str = include_str!("creature1.example.json");
const EXAMPLE_CREATURE_2: &str = include_str!("creature2.example.json");
#[test]
fn fix_link() {
let s = r#"blah blah [Foo Bar](/Monsters.aspx?ID=2871) blah blah
new line
[More Links](/Monsters.aspx?ID=2871)
"#;
insta::assert_snapshot!(&super::fix_links(s));
}
#[test]
fn render_creature_1() {
let creature: Creature = serde_json::from_str(EXAMPLE_CREATURE_1).unwrap();
let obsidian = creature_to_obsidian(&creature);
insta::assert_snapshot!(&obsidian);
}
#[test]
fn render_creature_2() {
let creature: Creature = serde_json::from_str(EXAMPLE_CREATURE_2).unwrap();
let obsidian = creature_to_obsidian(&creature);
insta::assert_snapshot!(&obsidian);
}
}

133
src/creature1.example.json Normal file

File diff suppressed because one or more lines are too long

111
src/creature2.example.json Normal file
View File

@@ -0,0 +1,111 @@
{
"ac": 12,
"ac_scale_number": 3,
"attack_bonus": [
7
],
"attack_bonus_scale_number": [
3
],
"category": "creature",
"charisma": -2,
"charisma_scale_number": 2,
"constitution": 2,
"constitution_scale_number": 3,
"creature_ability": [
"Slow",
"Zombie Bite"
],
"creature_family": "Zombie",
"creature_family_markdown": "[Zombie](/MonsterFamilies.aspx?ID=487)",
"dexterity": -2,
"dexterity_scale_number": 2,
"fortitude_save": 6,
"fortitude_save_scale_number": 3,
"hp_raw": "20 ( void healing )",
"hp_scale_number": 4,
"id": "creature-3249",
"image": [
"/Images/Monsters/Zombie_Shambler.webp"
],
"immunity_markdown": "bleed, [death](/Traits.aspx?ID=571) effects, [disease](/Traits.aspx?ID=578), [mental](/Traits.aspx?ID=647), [paralyzed](/Conditions.aspx?ID=85), [poison](/Traits.aspx?ID=669), [unconscious](/Conditions.aspx?ID=95)",
"intelligence": -5,
"intelligence_scale_number": 2,
"language_markdown": "",
"legacy_id": [
"creature-423"
],
"level": -1,
"markdown": "<title level=\"1\" pfs=\"Standard\">[Zombie Shambler](/Monsters.aspx?ID=3249)</title>\r\n\r\n<row gap=\"medium\">\r\n\r\n<column gap=\"medium\" flex=\"1 1 400px\">\r\n\r\nA zombie shambler is a slow-moving horror dangerous in larger groups.\r\n\r\n<column gap=\"tiny\">\r\n**[Recall Knowledge - Undead](/Rules.aspx?ID=563)**\r\n([Religion](/Skills.aspx?ID=46)): DC 13\r\n\r\n**[Unspecific Lore](/Rules.aspx?ID=563)**: DC 11\r\n\r\n**[Specific Lore](/Rules.aspx?ID=563)**: DC 8\r\n</column>\r\n\r\n</column>\r\n\r\n<column gap=\"medium\">\r\n<image src=\"/images/Monsters/Zombie_Shambler.webp\" />\r\n</column>\r\n\r\n</row>\r\n\r\n<title level=\"2\" right=\"Creature -1\">[Zombie Shambler](/Monsters.aspx?ID=3249)</title>\r\n\r\n<traits>\r\n<trait label=\"Medium\" />\r\n<trait label=\"Mindless\" url=\"/Traits.aspx?ID=652\" />\r\n<trait label=\"Undead\" url=\"/Traits.aspx?ID=722\" />\r\n<trait label=\"Unholy\" url=\"/Traits.aspx?ID=521\" />\r\n<trait label=\"Zombie\" url=\"/Traits.aspx?ID=782\" />\r\n</traits>\r\n\r\n<column gap=\"tiny\">\r\n\r\n**Source** [Monster Core](/Sources.aspx?ID=221) pg. 356\n\n**Perception** +0; [darkvision](/MonsterAbilities.aspx?ID=59)\r\n\r\n**Languages**\n\n**Skills**\r\n[Athletics](/Skills.aspx?ID=3) +7\r\n\r\n<row gap=\"medium\">\r\n**Str** +3\r\n\r\n**Dex** -2\r\n\r\n**Con** +2\r\n\r\n**Int** -5\r\n\r\n**Wis** +0\r\n\r\n**Cha** -2\r\n</row>\r\n\r\n**Slow** A zombie is permanently [slowed 1](/Conditions.aspx?ID=92) and can't use reactions.\n\n</column>\r\n\r\n---\r\n\r\n<column gap=\"tiny\">\r\n\r\n<row gap=\"medium\">\r\n**AC** 12 \r\n\r\n**Fort** +6 \r\n\r\n**Ref** +0 \r\n\r\n**Will** +2 \n\n</row>\r\n\r\n<row gap=\"medium\">\r\n**HP** 20\r\n([void healing](/MonsterAbilities.aspx?ID=83))\n\n</row>\r\n\r\n**Immunities**\r\nbleed, [death](/Traits.aspx?ID=571) effects, [disease](/Traits.aspx?ID=578), [mental](/Traits.aspx?ID=647), [paralyzed](/Conditions.aspx?ID=85), [poison](/Traits.aspx?ID=669), [unconscious](/Conditions.aspx?ID=95)\n\n**Weaknesses**\r\nslashing 5, vitality 5\n\n</column>\r\n\r\n---\r\n\r\n<column gap=\"tiny\">\r\n\r\n**Speed** 25 feet\r\n\r\n**Melee**\r\n<actions string=\"Single Action\" />\r\nfist +7,\r\n**Damage** 1d6+3 bludgeoning plus [Grab](/MonsterAbilities.aspx?ID=45)\n\n**Zombie Bite** <actions string=\"Single Action\" /> **Requirements** The zombie has a creature **grabbed** or [restrained](/Conditions.aspx?ID=90); **Effect** The zombie makes a jaws [unarmed](/Traits.aspx?ID=719) melee Strike against that creature with an attack modifier of +7 that deals 1d8+3 piercing damage.\r\n\r\n</column>\n\n<document level=\"2\" id=\"creature-family-487\" />",
"name": "Zombie Shambler",
"perception": 0,
"perception_scale_number": 1,
"pfs": "Standard",
"primary_source_category": "Rulebooks",
"rarity": "common",
"rarity_id": 1,
"reflex_save": 0,
"reflex_save_scale_number": 1,
"release_date": "2024-03-27",
"resistance": {},
"search_markdown": "<traits>\r\n<trait label=\"Medium\" />\r\n<trait label=\"Mindless\" url=\"/Traits.aspx?ID=652\" />\r\n<trait label=\"Undead\" url=\"/Traits.aspx?ID=722\" />\r\n<trait label=\"Unholy\" url=\"/Traits.aspx?ID=521\" />\r\n<trait label=\"Zombie\" url=\"/Traits.aspx?ID=782\" />\r\n</traits>\r\n\r\n<additional-info>\r\n**Source** [Monster Core](/Sources.aspx?ID=221) pg. 356\r\n\r\n**Creature Family** [Zombie](/MonsterFamilies.aspx?ID=487)\r\n\r\n<row gap=\"medium\">\r\n**HP** 20 ( void healing )\r\n\r\n**AC** 12\r\n\r\n**Fort** +6\r\n\r\n**Ref** +0\r\n\r\n**Will** +2\r\n\r\n**Perception** +0\r\n</row>\r\n</additional-info>\r\n\r\n---\r\n\r\n<summary>\r\nA zombie shambler is a slow-moving horror dangerous in larger groups.\r\n</summary>",
"sense_markdown": "[darkvision](/MonsterAbilities.aspx?ID=59)",
"size": [
"Medium"
],
"size_id": [
3
],
"skill_markdown": "[Athletics](/Skills.aspx?ID=3) +7",
"source": [
"Monster Core"
],
"source_category": [
"Rulebooks"
],
"source_markdown": "[Monster Core](/Sources.aspx?ID=221) pg. 356",
"speed": {
"land": 25,
"max": 25
},
"speed_markdown": "25 feet",
"spell_markdown": "",
"strike_damage_average": [
6
],
"strike_damage_scale_number": [
5
],
"strength": 3,
"strength_scale_number": 4,
"strongest_save": [
"fort",
"fortitude"
],
"summary_markdown": "A zombie shambler is a slow-moving horror dangerous in larger groups.",
"tradition_markdown": "",
"trait": [
"Mindless",
"Undead",
"Unholy",
"Zombie",
"Medium"
],
"trait_markdown": "[Mindless](/Traits.aspx?ID=652), [Undead](/Traits.aspx?ID=722), [Unholy](/Traits.aspx?ID=521), [Zombie](/Traits.aspx?ID=782)",
"type": "Creature",
"url": "/Monsters.aspx?ID=3249",
"vision": "Darkvision",
"weakest_save": [
"ref",
"reflex"
],
"weakness": {
"slashing": 5,
"vitality": 5
},
"weakness_markdown": "slashing 5, vitality 5",
"will_save": 2,
"will_save_scale_number": 2,
"wisdom": 0,
"wisdom_scale_number": 2
}

View File

@@ -0,0 +1,7 @@
---
source: src/creature.rs
expression: "&super::fix_links(s)"
---
blah blah [Foo Bar](https://2e.aonprd.com/Monsters.aspx?ID=2871) blah blah
new line
[More Links](https://2e.aonprd.com/Monsters.aspx?ID=2871)

View File

@@ -0,0 +1,44 @@
---
source: src/creature.rs
expression: "&obsidian"
---
```pf2e-stats
# Tarrasque
## Creature 25
---
==Beast== ==Unique== ==CE== ==Gargantuan==
**Perception** +48; darkvision, scent (imprecise) 120 feet
**Skills**: [Acrobatics](https://2e.aonprd.com/Skills.aspx?ID=1) +47, [Athletics](https://2e.aonprd.com/Skills.aspx?ID=3) +50
**Str** +12, **Dex** +9, **Con** +12, **Int** -3, **Wis** +9, **Cha** +7
---
**AC** 54; **Fort** +47, **Ref** +37 **Will** +39
**HP** 540; **Immunities** acid, clumsy, disease, drained, enfeebled, mental, paralyzed, persistent damage, petrified, poison, polymorph, stupefied; **Resistances** fire 25, physical 25
**Carapace** Tarrasque is immune to cones, lines, rays, and [_magic missile_](https://2e.aonprd.com/Spells.aspx?ID=180) spells. These effects bounce harmlessly off its scales. **Frightful Presence** ([aura](https://2e.aonprd.com/Traits.aspx?ID=206), [emotion](https://2e.aonprd.com/Traits.aspx?ID=60), [fear](https://2e.aonprd.com/Traits.aspx?ID=68), [mental](https://2e.aonprd.com/Traits.aspx?ID=106)) 300 feet, DC 39 **Inexorable** Tarrasque recovers from the [slowed](https://2e.aonprd.com/Conditions.aspx?ID=35) and [stunned](https://2e.aonprd.com/Conditions.aspx?ID=36) conditions at the end of its turn. Its also immune to penalties to its Speeds, and it ignores difficult terrain and greater difficult terrain. **Reactive** Tarrasque gains 3 reactions each round. It can still use only one reaction per trigger. **Attack of Opportunity** `[reaction]` **Reflect** `[reaction]` **Trigger** Tarrasques carapace deflects an effect. **Effect** The effect is redirected back at its source.
---
**Speed**: 50 feet, swim 50 feet
**Melee** `[one-action]` jaws +45 ([Chaotic](https://2e.aonprd.com/Traits.aspx?ID=25), [Evil](https://2e.aonprd.com/Traits.aspx?ID=64), [Magical](https://2e.aonprd.com/Traits.aspx?ID=103), [reach 20 feet](https://2e.aonprd.com/Traits.aspx?ID=192)), **Damage** 5d12+20 piercing plus Improved Grab
**Melee** `[one-action]` claw +45 ([Agile](https://2e.aonprd.com/Traits.aspx?ID=170), [Chaotic](https://2e.aonprd.com/Traits.aspx?ID=25), [Evil](https://2e.aonprd.com/Traits.aspx?ID=64), [Magical](https://2e.aonprd.com/Traits.aspx?ID=103), [reach 15 feet](https://2e.aonprd.com/Traits.aspx?ID=192)), **Damage** 5d10+20 slashing
**Melee** `[one-action]` tail +45 ([Chaotic](https://2e.aonprd.com/Traits.aspx?ID=25), [Evil](https://2e.aonprd.com/Traits.aspx?ID=64), [Magical](https://2e.aonprd.com/Traits.aspx?ID=103), [reach 30 feet](https://2e.aonprd.com/Traits.aspx?ID=192)), **Damage** 4d12+20 bludgeoning
**Melee** `[one-action]` horn +45 ([Chaotic](https://2e.aonprd.com/Traits.aspx?ID=25), [Evil](https://2e.aonprd.com/Traits.aspx?ID=64), [Magical](https://2e.aonprd.com/Traits.aspx?ID=103), [reach 15 feet](https://2e.aonprd.com/Traits.aspx?ID=192)), **Damage** 4d10+20 piercing
**Ranged** `[one-action]` spine +45 ([Brutal](https://2e.aonprd.com/Traits.aspx?ID=249), [Chaotic](https://2e.aonprd.com/Traits.aspx?ID=25), [Evil](https://2e.aonprd.com/Traits.aspx?ID=64), [Magical](https://2e.aonprd.com/Traits.aspx?ID=103), [range increment 120 feet](https://2e.aonprd.com/Traits.aspx?ID=248)), **Damage** 3d10+20 piercing
**Destructive Frenzy** `[three-actions]` Tarrasque makes a jaws Strike, two claw Strikes, two horn Strikes, and one tail Strike in any order.
**Fast Swallow** `[reaction]` **Trigger** Tarrasque Grabs a creature with its jaws; **Effect** Tarrasque uses Swallow Whole.
**Spine Volley** `[two-actions]` Tarrasque flings spines in a 120-foot cone, dealing 3d10+20 piercing damage to each creature in the area (DC 53 basic Reflex save). Tarrasque cant use Spine Volley again for 1d4 rounds.
**[Swallow Whole](https://2e.aonprd.com/MonsterAbilities.aspx?ID=35)** `[one-action]` ([Attack](https://2e.aonprd.com/Traits.aspx?ID=15)) Huge, 10d6+12 bludgeoning plus 10d6 acid, Rupture 50
**[Trample](https://2e.aonprd.com/MonsterAbilities.aspx?ID=39)** `[three-actions]` Huge or smaller, claw, DC 49. When Tarrasque Tramples, it can Stride up to triple its Speed.
```

View File

@@ -0,0 +1,36 @@
---
source: src/creature.rs
expression: "&obsidian"
---
```pf2e-stats
# Zombie Shambler
## Creature -1
---
==Mindless== ==Undead== ==Unholy== ==Zombie== ==Medium==
**Perception** +0; [darkvision](https://2e.aonprd.com/MonsterAbilities.aspx?ID=59)
**Skills**: [Athletics](https://2e.aonprd.com/Skills.aspx?ID=3) +7
**Str** +3, **Dex** -2, **Con** +2, **Int** -5, **Wis** +0, **Cha** -2
**Slow** A zombie is permanently [slowed 1](https://2e.aonprd.com/Conditions.aspx?ID=92) and can't use reactions.
---
**AC** 12; **Fort** +6, **Ref** +0 **Will** +2
**HP** 20 ( void healing ); **Immunities** bleed, [death](https://2e.aonprd.com/Traits.aspx?ID=571) effects, [disease](https://2e.aonprd.com/Traits.aspx?ID=578), [mental](https://2e.aonprd.com/Traits.aspx?ID=647), [paralyzed](https://2e.aonprd.com/Conditions.aspx?ID=85), [poison](https://2e.aonprd.com/Traits.aspx?ID=669), [unconscious](https://2e.aonprd.com/Conditions.aspx?ID=95); **Weaknesses** slashing 5, vitality 5
---
**Speed**: 25 feet
**Melee** `[one-action]` fist +7, **Damage** 1d6+3 bludgeoning plus [Grab](https://2e.aonprd.com/MonsterAbilities.aspx?ID=45)
**Zombie Bite** `[one-action]` **Requirements** The zombie has a creature **grabbed** or [restrained](https://2e.aonprd.com/Conditions.aspx?ID=90); **Effect** The zombie makes a jaws [unarmed](https://2e.aonprd.com/Traits.aspx?ID=719) melee Strike against that creature with an attack modifier of +7 that deals 1d8+3 piercing damage.
```