tmp working changes from laptop
This commit is contained in:
parent
2bd75328a1
commit
2fd92ca0b8
131
amanuensis/lexicon/citation.py
Normal file
131
amanuensis/lexicon/citation.py
Normal file
@ -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
|
@ -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}"))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user