257 lines
6.4 KiB
Python
257 lines
6.4 KiB
Python
"""
|
|
Marker parsing for Orchestrated Discussions.
|
|
|
|
This module handles parsing of structured markers in discussion content:
|
|
- VOTE: READY|CHANGES|REJECT
|
|
- Q: / QUESTION: - Questions
|
|
- TODO: / ACTION: - Action items
|
|
- DECISION: - Decisions
|
|
- ASSIGNED: - Claimed tasks
|
|
- DONE: - Completed tasks
|
|
- CONCERN: - Raised concerns
|
|
- DIAGRAM: - Diagram references (path to .puml file)
|
|
- @alias - Mentions
|
|
|
|
See docs/DESIGN.md for full marker specification.
|
|
"""
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
|
|
# Regex patterns for marker extraction
|
|
VOTE_PATTERN = re.compile(r'^VOTE:\s*(READY|CHANGES|REJECT)\s*$', re.IGNORECASE | re.MULTILINE)
|
|
QUESTION_PATTERN = re.compile(r'^(?:Q|QUESTION):\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
TODO_PATTERN = re.compile(r'^(?:TODO|ACTION):\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
DECISION_PATTERN = re.compile(r'^DECISION:\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
ASSIGNED_PATTERN = re.compile(r'^ASSIGNED:\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
DONE_PATTERN = re.compile(r'^DONE:\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
CONCERN_PATTERN = re.compile(r'^CONCERN:\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
DIAGRAM_PATTERN = re.compile(r'^DIAGRAM:\s*(.+)$', re.IGNORECASE | re.MULTILINE)
|
|
MENTION_PATTERN = re.compile(r'@(\w+)')
|
|
|
|
|
|
@dataclass
|
|
class Question:
|
|
"""A question raised in the discussion."""
|
|
text: str
|
|
author: str
|
|
status: str = "open" # open, answered, deferred
|
|
|
|
|
|
@dataclass
|
|
class ActionItem:
|
|
"""An action item or TODO."""
|
|
text: str
|
|
author: str
|
|
assignee: Optional[str] = None
|
|
status: str = "todo" # todo, assigned, done
|
|
|
|
|
|
@dataclass
|
|
class Decision:
|
|
"""A decision made in the discussion."""
|
|
text: str
|
|
author: str
|
|
supporters: list[str] = None
|
|
|
|
def __post_init__(self):
|
|
if self.supporters is None:
|
|
self.supporters = []
|
|
|
|
|
|
@dataclass
|
|
class Concern:
|
|
"""A concern raised by a participant."""
|
|
text: str
|
|
author: str
|
|
addressed: bool = False
|
|
|
|
|
|
@dataclass
|
|
class Diagram:
|
|
"""A diagram reference in the discussion."""
|
|
path: str # Path to the diagram file
|
|
author: str
|
|
|
|
|
|
@dataclass
|
|
class Mention:
|
|
"""An @mention in the discussion."""
|
|
target: str # The alias mentioned
|
|
author: str
|
|
context: str # Surrounding text
|
|
|
|
|
|
def extract_vote(text: str) -> Optional[str]:
|
|
"""
|
|
Extract vote from text.
|
|
|
|
Args:
|
|
text: Text to search for vote
|
|
|
|
Returns:
|
|
Vote value (READY, CHANGES, REJECT) or None
|
|
"""
|
|
match = VOTE_PATTERN.search(text)
|
|
if match:
|
|
return match.group(1).upper()
|
|
return None
|
|
|
|
|
|
def extract_questions(text: str, author: str = "unknown") -> list[Question]:
|
|
"""
|
|
Extract all questions from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute questions to
|
|
|
|
Returns:
|
|
List of Question objects
|
|
"""
|
|
questions = []
|
|
for match in QUESTION_PATTERN.finditer(text):
|
|
questions.append(Question(
|
|
text=match.group(1).strip(),
|
|
author=author
|
|
))
|
|
return questions
|
|
|
|
|
|
def extract_action_items(text: str, author: str = "unknown") -> list[ActionItem]:
|
|
"""
|
|
Extract all action items/TODOs from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute items to
|
|
|
|
Returns:
|
|
List of ActionItem objects
|
|
"""
|
|
items = []
|
|
for match in TODO_PATTERN.finditer(text):
|
|
item_text = match.group(1).strip()
|
|
# Check for @mention to determine assignee
|
|
mention = MENTION_PATTERN.search(item_text)
|
|
assignee = mention.group(1) if mention else None
|
|
|
|
items.append(ActionItem(
|
|
text=item_text,
|
|
author=author,
|
|
assignee=assignee
|
|
))
|
|
return items
|
|
|
|
|
|
def extract_decisions(text: str, author: str = "unknown") -> list[Decision]:
|
|
"""
|
|
Extract all decisions from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute decisions to
|
|
|
|
Returns:
|
|
List of Decision objects
|
|
"""
|
|
decisions = []
|
|
for match in DECISION_PATTERN.finditer(text):
|
|
decisions.append(Decision(
|
|
text=match.group(1).strip(),
|
|
author=author
|
|
))
|
|
return decisions
|
|
|
|
|
|
def extract_concerns(text: str, author: str = "unknown") -> list[Concern]:
|
|
"""
|
|
Extract all concerns from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute concerns to
|
|
|
|
Returns:
|
|
List of Concern objects
|
|
"""
|
|
concerns = []
|
|
for match in CONCERN_PATTERN.finditer(text):
|
|
concerns.append(Concern(
|
|
text=match.group(1).strip(),
|
|
author=author
|
|
))
|
|
return concerns
|
|
|
|
|
|
def extract_diagrams(text: str, author: str = "unknown") -> list[Diagram]:
|
|
"""
|
|
Extract all diagram references from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute diagrams to
|
|
|
|
Returns:
|
|
List of Diagram objects
|
|
"""
|
|
diagrams = []
|
|
for match in DIAGRAM_PATTERN.finditer(text):
|
|
diagrams.append(Diagram(
|
|
path=match.group(1).strip(),
|
|
author=author
|
|
))
|
|
return diagrams
|
|
|
|
|
|
def extract_mentions(text: str, author: str = "unknown") -> list[Mention]:
|
|
"""
|
|
Extract all @mentions from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author making the mentions
|
|
|
|
Returns:
|
|
List of Mention objects
|
|
"""
|
|
mentions = []
|
|
for match in MENTION_PATTERN.finditer(text):
|
|
# Get surrounding context (the line containing the mention)
|
|
start = text.rfind('\n', 0, match.start()) + 1
|
|
end = text.find('\n', match.end())
|
|
if end == -1:
|
|
end = len(text)
|
|
context = text[start:end].strip()
|
|
|
|
mentions.append(Mention(
|
|
target=match.group(1),
|
|
author=author,
|
|
context=context
|
|
))
|
|
return mentions
|
|
|
|
|
|
def extract_all_markers(text: str, author: str = "unknown") -> dict:
|
|
"""
|
|
Extract all markers from text.
|
|
|
|
Args:
|
|
text: Text to search
|
|
author: Author to attribute markers to
|
|
|
|
Returns:
|
|
Dict with keys: vote, questions, action_items, decisions, concerns, mentions
|
|
"""
|
|
return {
|
|
"vote": extract_vote(text),
|
|
"questions": extract_questions(text, author),
|
|
"action_items": extract_action_items(text, author),
|
|
"decisions": extract_decisions(text, author),
|
|
"concerns": extract_concerns(text, author),
|
|
"diagrams": extract_diagrams(text, author),
|
|
"mentions": extract_mentions(text, author),
|
|
}
|