From 2fd92ca0b84f8ce69134984b1e5ee17688cacdba Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 12 Dec 2022 17:30:10 -0800 Subject: [PATCH] tmp working changes from laptop --- amanuensis/lexicon/citation.py | 131 +++++++++++++++++++++++++++++++ amanuensis/lexicon/constraint.py | 49 ++++++++---- 2 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 amanuensis/lexicon/citation.py diff --git a/amanuensis/lexicon/citation.py b/amanuensis/lexicon/citation.py new file mode 100644 index 0000000..4349b06 --- /dev/null +++ b/amanuensis/lexicon/citation.py @@ -0,0 +1,131 @@ +""" +Submodule for citation logic. +""" + +import enum +from typing import Sequence, Set, List, Dict, Union + +from amanuensis.backend import * +from amanuensis.db import Lexicon, Article +from amanuensis.parser import * + + +class CitationNodeType(enum.Enum): + ExtantWritten = 0 + """Title of an article that was previously written.""" + + ExtantPhantom = 1 + """Unwritten title cited by an extant written article.""" + + NewDraft = 2 + """Title of a pending article with a new title.""" + + PhantomDraft = 3 + """Title of a pending article with a phantom title.""" + + PhantomPending = 4 + """Unwritten title cited by a draft article.""" + + +class CitationNode: + """ + Represents an article in the context of citations. Phantom articles do not + correspond to articles in the database. + """ + + def __init__(self, title: str, node_type: CitationNodeType) -> None: + self.title: str = title + self.cites: Set[CitationNode] = set() + self.cited_by: Set[CitationNode] = set() + self.node_type: CitationNodeType = node_type + + +class CitationMap: + """Represents metadata about which articles cite each other.""" + + def __init__(self) -> None: + self.by_title: Dict[str, CitationNode] = {} + + def __contains__(self, title: str) -> bool: + return title in self.by_title + + def __getitem__(self, title: str) -> CitationNode: + return self.by_title[title] + + def get_or_add(self, title: str, node_type: CitationNodeType) -> CitationNode: + """ + Get the citation node for a title. If one does not exist, create it + with the given type. + """ + if title not in self.by_title: + self.add(title, node_type) + return self.by_title[title] + + def add(self, title: str, node_type: CitationNodeType) -> None: + """ + Create a citation node with the given title and type. + """ + self.by_title[title] = CitationNode(title, node_type) + + def create_citation( + self, citer: Union[CitationNode, str], cited: Union[CitationNode, str] + ) -> None: + """Add a citation between two titles.""" + if isinstance(citer, str): + citer = self.by_title[citer] + if isinstance(cited, str): + cited = self.by_title[cited] + citer.cites.add(cited) + cited.cited_by.add(citer) + + +class GetCitations(RenderableVisitor): + """Returns a list of all article titles cited in this article.""" + def __init__(self) -> None: + self.citations: List[str] = [] + + def CitationSpan(self, span): + self.citations.append(span.cite_target) + + def ParsedArticle(self, span): + return self.citations + + +def create_citation_map(lexicon: Lexicon, articles: Sequence[Article]) -> CitationMap: + """ + Generates mappings useful for tracking citations. + """ + article: Article + citemap = CitationMap() + + # Add extant articles to the citation map + for article in articles: + if article.turn < lexicon.current_turn: + citemap.add(article.title, CitationNodeType.ExtantWritten) + + # Add phantoms created by extant articles + for article in articles: + if article.turn < lexicon.current_turn: + parsed = parse_raw_markdown(article.body) + citeds = parsed.render(GetCitations()) + for cited in citeds: + cited_node = citemap.get_or_add(cited, CitationNodeType.ExtantPhantom) + citemap.create_citation(article.title, cited_node) + + # Add drafts, noting new and phantom drafts + for article in articles: + if article.turn >= lexicon.current_turn: + draft_node = citemap.get_or_add(article.title, CitationNodeType.NewDraft) + if draft_node.node_type == CitationNodeType.ExtantPhantom: + draft_node.node_type = CitationNodeType.PhantomDraft + + # Add phantoms created by drafts + for article in articles: + if article.turn >= lexicon.current_turn: + parsed = parse_raw_markdown(article.body) + citeds = parsed.render(GetCitations()) + for cited in citeds: + cited_node = citemap.get_or_add(cited, CitationNodeType.PhantomPending) + citemap.create_citation(article.title, cited_node) + + return citemap diff --git a/amanuensis/lexicon/constraint.py b/amanuensis/lexicon/constraint.py index 17cb48e..65a559e 100644 --- a/amanuensis/lexicon/constraint.py +++ b/amanuensis/lexicon/constraint.py @@ -4,15 +4,18 @@ from typing import Sequence from amanuensis.db import * from amanuensis.parser import * +from .citation import CitationMap, CitationNodeType, create_citation_map + class ConstraintCheck(RenderableVisitor): """Analyzes an article for content-based constraint violations.""" + def __init__(self) -> None: self.word_count: int = 0 self.signatures: int = 0 def TextSpan(self, span): - self.word_count += len(re.split(r'\s+', span.innertext.strip())) + self.word_count += len(re.split(r"\s+", span.innertext.strip())) return self def SignatureParagraph(self, span): @@ -50,24 +53,44 @@ class ConstraintMessage: return {"severity": self.severity, "message": self.message} -def title_constraint_check(title: str) -> Sequence[ConstraintMessage]: - """Perform checks that apply to the article title.""" +def constraint_check( + article: Article, + parsed: Renderable, + citemap: CitationMap, +) -> Sequence[ConstraintMessage]: + """""" messages = [] + # TODO: Need + # player index assignments for article turn + # extant article titles + # pending article titles + # phantom article titles + # pending phantom article titles + # index capacities + title = article.title + # author_id = article.character_id + + ### + # Constraints that apply to the title + ### # I: Current index assignments # TODO # E: No title - if not title: + if not article.title: messages.append(ConstraintMessage.error("Missing title")) - # I: This article is new - # TODO - # E: And new articles are forbidden - # TODO + if citemap[title].node_type == CitationNodeType.NewDraft: + # I: This article is new + messages.append(ConstraintMessage.info("Writing a new article")) - # I: This article is a phantom - # TODO + # E: New articles are forbidden + # TODO + + if citemap[title].node_type == CitationNodeType.PhantomDraft: + # I: This article is a phantom + messages.append(ConstraintMessage.info("Writing a phantom article") # I: This article is an addendum # TODO @@ -98,14 +121,8 @@ def title_constraint_check(title: str) -> Sequence[ConstraintMessage]: # W: The article's title matches a character's name # TODO - return messages - - -def content_constraint_check(parsed: Renderable) -> Sequence[ConstraintMessage]: check_result: ConstraintCheck = parsed.render(ConstraintCheck()) - messages = [] - # I: Word count messages.append(ConstraintMessage.info(f"Word count: {check_result.word_count}"))