Files
PyPost/lua/Actions.py

291 lines
11 KiB
Python

from pathlib import Path
from log import Logger
import re
import json
PLUGINS_DIR = Path(__file__).parent / "plugins"
HTML_DIR = Path(__file__).parent / "../html"
MARKDOWN_DIR = Path(__file__).parent / ".." / "markdown"
class Actions:
def __init__(self):
"""Initialize Actions with directory paths"""
self.plugins_dir = PLUGINS_DIR
self.html_dir = HTML_DIR
self.markdown_dir = MARKDOWN_DIR
# Ensure directories exist
self.plugins_dir.mkdir(exist_ok=True)
self.html_dir.mkdir(exist_ok=True)
self.markdown_dir.mkdir(exist_ok=True)
# File I/O Operations
def _safe_read_file(self, path):
"""Safe file reading with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
return p.read_text(encoding="utf-8")
except Exception as e:
Logger.log_lua_error(f"Error reading file {path}: {e}")
return None
def _safe_write_file(self, path, content):
"""Safe file writing with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
p.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_lua_error(f"Error writing file {path}: {e}")
return False
# HTML/Markdown Content Operations
def _read_content(self, base_dir, filename):
"""Read content from HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
if path.exists():
return path.read_text(encoding="utf-8")
return None
except Exception as e:
Logger.log_lua_error(f"Error reading {filename}: {e}")
return None
def _write_content(self, base_dir, filename, content):
"""Write content to HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
path.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_lua_error(f"Error writing {filename}: {e}")
return False
def _list_files(self, base_dir, extension):
"""List files with given extension"""
try:
return [f.name for f in base_dir.glob(f"*{extension}")]
except Exception as e:
Logger.log_lua_error(f"Error listing files: {e}")
return []
# HTML Manipulation Helpers
def _html_find_tag(self, html, tag):
"""Find first occurrence of HTML tag"""
pattern = f"<{tag}[^>]*>.*?</{tag}>"
match = re.search(pattern, html, re.DOTALL | re.IGNORECASE)
return match.group(0) if match else None
def _html_replace_tag(self, html, tag, new_content):
"""Replace HTML tag content"""
pattern = f"(<{tag}[^>]*>).*?(</{tag}>)"
return re.sub(pattern, f"\\1{new_content}\\2", html, flags=re.DOTALL | re.IGNORECASE)
def _html_insert_before(self, html, marker, content):
"""Insert content before a marker"""
return html.replace(marker, content + marker)
def _html_insert_after(self, html, marker, content):
"""Insert content after a marker"""
return html.replace(marker, marker + content)
def _html_wrap_content(self, html, tag, wrapper_tag, attrs=""):
"""Wrap tag content with another tag"""
pattern = f"(<{tag}[^>]*>)(.*?)(</{tag}>)"
def replacer(match):
open_tag, content, close_tag = match.groups()
return f"{open_tag}<{wrapper_tag} {attrs}>{content}</{wrapper_tag}>{close_tag}"
return re.sub(pattern, replacer, html, flags=re.DOTALL | re.IGNORECASE)
def _html_remove_tag(self, html, tag, keep_content=True):
"""Remove HTML tag, optionally keeping its content"""
if keep_content:
pattern = f"<{tag}[^>]*>(.*?)</{tag}>"
return re.sub(pattern, r"\1", html, flags=re.DOTALL | re.IGNORECASE)
else:
pattern = f"<{tag}[^>]*>.*?</{tag}>"
return re.sub(pattern, "", html, flags=re.DOTALL | re.IGNORECASE)
def _html_add_class(self, html, tag, class_name):
"""Add a CSS class to all instances of a tag"""
def add_class_to_tag(match):
tag_content = match.group(0)
if 'class=' in tag_content:
return re.sub(
r'class="([^"]*)"',
f'class="\\1 {class_name}"',
tag_content
)
else:
return tag_content.replace('>', f' class="{class_name}">', 1)
pattern = f"<{tag}[^>]*>"
return re.sub(pattern, add_class_to_tag, html, flags=re.IGNORECASE)
def _html_add_attribute(self, html, tag, attr_name, attr_value):
"""Add an attribute to all instances of a tag"""
def add_attr_to_tag(match):
tag_content = match.group(0)
if attr_name in tag_content:
return tag_content # Don't duplicate
return tag_content.replace('>', f' {attr_name}="{attr_value}">', 1)
pattern = f"<{tag}[^>]*>"
return re.sub(pattern, add_attr_to_tag, html, flags=re.IGNORECASE)
# Markdown Manipulation Helpers
def _md_add_header(self, markdown, level, text):
"""Add header to markdown"""
prefix = "#" * level
return f"{prefix} {text}\n\n{markdown}"
def _md_replace_section(self, markdown, header, new_content):
"""Replace markdown section"""
# Find section starting with header
pattern = f"(#{1,6}\\s+{re.escape(header)}.*?)(?=#{1,6}\\s+|$)"
return re.sub(pattern, f"## {header}\n\n{new_content}\n\n", markdown, flags=re.DOTALL)
def _md_append_content(self, markdown, content):
"""Append content to markdown"""
return markdown.rstrip() + "\n\n" + content
def _md_prepend_content(self, markdown, content):
"""Prepend content to markdown"""
return content + "\n\n" + markdown.lstrip()
def _md_insert_at_position(self, markdown, position, content):
"""Insert content at specific line position"""
lines = markdown.split('\n')
if 0 <= position <= len(lines):
lines.insert(position, content)
return '\n'.join(lines)
def _md_find_section(self, markdown, header):
"""Find a markdown section and return its content"""
pattern = f"#{1,6}\\s+{re.escape(header)}\\s*\n(.*?)(?=#{1,6}\\s+|$)"
match = re.search(pattern, markdown, re.DOTALL)
return match.group(1).strip() if match else None
def _md_remove_section(self, markdown, header):
"""Remove a markdown section"""
pattern = f"#{1,6}\\s+{re.escape(header)}.*?(?=#{1,6}\\s+|$)"
return re.sub(pattern, "", markdown, flags=re.DOTALL).strip()
def _md_add_list_item(self, markdown, item, ordered=False):
"""Add an item to the end of markdown (as list item)"""
prefix = "1. " if ordered else "- "
return markdown.rstrip() + f"\n{prefix}{item}\n"
def _md_wrap_code_block(self, markdown, language=""):
"""Wrap content in a markdown code block"""
return f"```{language}\n{markdown}\n```"
# JSON Conversion Helpers
def _table_to_json(self, lua_table):
"""Convert Lua table to JSON string"""
try:
# Handle lupa tables by converting to dict
if hasattr(lua_table, 'items'):
py_dict = dict(lua_table.items())
elif hasattr(lua_table, '__iter__') and not isinstance(lua_table, str):
# Handle array-like lua tables
py_dict = list(lua_table)
else:
py_dict = dict(lua_table)
return json.dumps(py_dict, indent=2, ensure_ascii=False)
except Exception as e:
Logger.log_lua_error(f"Error converting table to JSON: {e}")
return "{}"
def _json_to_table(self, json_str):
"""Convert JSON string to Lua table (Python dict)"""
try:
return json.loads(json_str)
except Exception as e:
Logger.log_lua_error(f"Error parsing JSON: {e}")
return {}
def _json_parse(self, json_str):
"""Alias for _json_to_table"""
return self._json_to_table(json_str)
def _json_stringify(self, data):
"""Convert Python object to JSON string"""
try:
return json.dumps(data, indent=2, ensure_ascii=False)
except Exception as e:
Logger.log_lua_error(f"Error stringifying to JSON: {e}")
return "{}"
# Utility Functions
def _string_split(self, text, delimiter):
"""Split string by delimiter"""
return text.split(delimiter)
def _string_join(self, items, delimiter):
"""Join list of strings with delimiter"""
try:
return delimiter.join(str(item) for item in items)
except Exception as e:
Logger.log_lua_error(f"Error joining strings: {e}")
return ""
def _string_replace(self, text, old, new):
"""Replace all occurrences of old with new"""
return text.replace(old, new)
def _string_match(self, text, pattern):
"""Match string against regex pattern"""
try:
match = re.search(pattern, text)
return match.group(0) if match else None
except Exception as e:
Logger.log_lua_error(f"Error in regex match: {e}")
return None
def _string_match_all(self, text, pattern):
"""Find all matches of pattern in text"""
try:
return re.findall(pattern, text)
except Exception as e:
Logger.log_lua_error(f"Error in regex findall: {e}")
return []
def _file_exists(self, path):
"""Check if file exists"""
try:
return Path(path).exists()
except Exception as e:
Logger.log_lua_error(f"Error checking file existence: {e}")
return False
def _file_size(self, path):
"""Get file size in bytes"""
try:
return Path(path).stat().st_size
except Exception as e:
Logger.log_lua_error(f"Error getting file size: {e}")
return 0
def _list_directory(self, path):
"""List all files and directories in path"""
try:
p = Path(path)
if not p.exists():
return []
return [item.name for item in p.iterdir()]
except Exception as e:
Logger.log_lua_error(f"Error listing directory: {e}")
return []