import json from typing import List from dataclasses import asdict import re from .types import SearchResult, StackOverflowAnswer, StackOverflowComment def format_response(results: List[SearchResult], format_type: str = "markdown") -> str: """Format search results as either JSON or Markdown. Args: results (List[SearchResult]): List of search results to format format_type (str, optional): Output format type - either "json" or "markdown". Defaults to "markdown". Returns: str: Formatted string representation of the search results """ if format_type == "json": def _convert_to_dict(obj): if hasattr(obj, "__dataclass_fields__"): return asdict(obj) return obj class DataClassJSONEncoder(json.JSONEncoder): def default(self, obj): if hasattr(obj, "__dataclass_fields__"): return asdict(obj) return super().default(obj) return json.dumps(results, cls=DataClassJSONEncoder, indent=2) if not results: return "No results found." markdown = "" for result in results: markdown += f"# {result.question.title}\n\n" markdown += f"**Score:** {result.question.score} | **Answers:** {result.question.answer_count}\n\n" question_body = clean_html(result.question.body) markdown += f"## Question\n\n{question_body}\n\n" if result.comments and result.comments.question: markdown += "### Question Comments\n\n" for comment in result.comments.question: markdown += f"- {clean_html(comment.body)} *(Score: {comment.score})*\n" markdown += "\n" markdown += "## Answers\n\n" for answer in result.answers: markdown += f"### {'✓ ' if answer.is_accepted else ''}Answer (Score: {answer.score})\n\n" answer_body = clean_html(answer.body) markdown += f"{answer_body}\n\n" if (result.comments and result.comments.answers and answer.answer_id in result.comments.answers and result.comments.answers[answer.answer_id] ): markdown += "#### Answer Comments\n\n" for comment in result.comments.answers[answer.answer_id]: markdown += f"- {clean_html(comment.body)} *(Score: {comment.score})*\n" markdown += "/n" markdown += f"---\n\n[View on Stack Overflow]({result.question.link})\n\n" return markdown def clean_html(html_text: str) -> str: """Clean HTML tags from text while preserving code blocks. Args: html_text (str): HTML text to be cleaned Returns: str: Cleaned text with HTML tags removed and code blocks preserved """ code_blocks = [] def replace_code_block(match): code = match.group(1) or match.group(2) code_blocks.append(code) return f"CODE_BLOCK_{len(code_blocks)-1}" html_without_code = re.sub(r'
(.*?)
|(.*?)
', replace_code_block, html_text, flags=re.DOTALL)
text_without_html = re.sub(r'<[^>]+>', '', html_without_code)
for i, code in enumerate(code_blocks):
if '\n' in code or len(code) > 80:
text_without_html = text_without_html.replace(f"CODE_BLOCK_{i}", f"```\n{code}\n```")
else:
text_without_html = text_without_html.replace(f"CODE_BLOCK_{i}", f"`{code}`")
text_without_html = text_without_html.replace("<", "<")
text_without_html = text_without_html.replace(">", ">")
text_without_html = text_without_html.replace("&", "&")
text_without_html = text_without_html.replace(""", "\"")
return text_without_html