Spaces:
Running
Running
| import tkinter as tk | |
| from tkinter import scrolledtext | |
| from tkinterdnd2 import DND_FILES, TkinterDnD | |
| import re | |
| import os | |
| # --- Conversion Logic --- | |
| def convert_md_to_smf(markdown_text): | |
| """ | |
| Converts a Markdown string to an SMF-compatible BBCode string. | |
| The order of replacements is important to avoid conflicts. | |
| """ | |
| smf_text = markdown_text | |
| # 1. Block elements (Code blocks, blockquotes) | |
| # Code blocks (```...```) - DOTALL flag allows `.` to match newlines | |
| smf_text = re.sub(r'```(.*?)```', r'[code]\1[/code]', smf_text, flags=re.DOTALL) | |
| # Blockquotes (>) | |
| smf_text = re.sub(r'^\> (.*)', r'[quote]\1[/quote]', smf_text, flags=re.MULTILINE) | |
| # 2. Headings (must be at the start of a line) | |
| smf_text = re.sub(r'^# (.*)', r'[size=18pt][b]\1[/b][/size]', smf_text, flags=re.MULTILINE) | |
| smf_text = re.sub(r'^## (.*)', r'[size=16pt][b]\1[/b][/size]', smf_text, flags=re.MULTILINE) | |
| smf_text = re.sub(r'^### (.*)', r'[size=14pt][b]\1[/b][/size]', smf_text, flags=re.MULTILINE) | |
| smf_text = re.sub(r'^#### (.*)', r'[size=14pt][b]\1[/b][/size]', smf_text, flags=re.MULTILINE) | |
| # 3. Lists (must be at the start of a line) | |
| # This is a simplified conversion. It doesn't wrap the whole block in [list] tags, | |
| # as that requires more complex state management, but it's often sufficient. | |
| smf_text = re.sub(r'^\* (.*)', r'[li]\1[/li]', smf_text, flags=re.MULTILINE) | |
| smf_text = re.sub(r'^- (.*)', r'[li]\1[/li]', smf_text, flags=re.MULTILINE) | |
| smf_text = re.sub(r'^\d+\. (.*)', r'[li]\1[/li]', smf_text, flags=re.MULTILINE) # Note: SMF may need [list type=decimal] wrapper | |
| # 4. Images and Links | |
| smf_text = re.sub(r'!\[.*?\]\((.*?)\)', r'[img]\1[/img]', smf_text) # Images | |
| smf_text = re.sub(r'\[(.*?)\]\((.*?)\)', r'[url=\2]\1[/url]', smf_text) # Links | |
| # 5. Inline formatting | |
| smf_text = re.sub(r'\*\*(.*?)\*\*', r'[b]\1[/b]', smf_text) # Bold (**) | |
| smf_text = re.sub(r'__(.*?)__', r'[b]\1[/b]', smf_text) # Bold (__) | |
| smf_text = re.sub(r'\*(.*?)\*', r'[i]\1[/i]', smf_text) # Italic (*) | |
| smf_text = re.sub(r'_(.*?)_', r'[i]\1[/i]', smf_text) # Italic (_) | |
| smf_text = re.sub(r'~~(.*?)~~', r'[s]\1[/s]', smf_text) # Strikethrough | |
| smf_text = re.sub(r'`(.*?)`', r'[code]\1[/code]', smf_text) # Inline code | |
| # 6. Horizontal Rule | |
| smf_text = re.sub(r'^\s*---\s*$', '\n[hr]\n', smf_text, flags=re.MULTILINE) | |
| return smf_text | |
| # --- GUI Application --- | |
| class App(TkinterDnD.Tk): | |
| def __init__(self): | |
| super().__init__() | |
| self.title("Markdown to SMF Converter") | |
| self.geometry("500x400") | |
| # Main frame | |
| main_frame = tk.Frame(self) | |
| main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) | |
| # Label for instructions | |
| self.drop_label = tk.Label( | |
| main_frame, | |
| text="Drag and Drop .md files here", | |
| font=("Arial", 14), | |
| bg="lightblue", | |
| relief="solid", | |
| bd=2 | |
| ) | |
| self.drop_label.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) | |
| # Scrolled text for status logs | |
| self.status_log = scrolledtext.ScrolledText( | |
| main_frame, | |
| height=10, | |
| state='disabled' # Make it read-only | |
| ) | |
| self.status_log.pack(fill=tk.BOTH, expand=True) | |
| # Register the drop zone | |
| self.drop_target_register(DND_FILES) | |
| self.dnd_bind('<<Drop>>', self.handle_drop) | |
| self.log("Ready to convert files.") | |
| def log(self, message): | |
| """Adds a message to the status log.""" | |
| self.status_log.config(state='normal') | |
| self.status_log.insert(tk.END, message + "\n") | |
| self.status_log.see(tk.END) # Auto-scroll to the bottom | |
| self.status_log.config(state='disabled') | |
| def handle_drop(self, event): | |
| """Handles the file drop event.""" | |
| # The event.data is a string of file paths, sometimes in curly braces | |
| # We clean it up and split it into a list of individual paths | |
| file_paths = self.tk.splitlist(event.data) | |
| self.log("\n--- New Drop Detected ---") | |
| for file_path in file_paths: | |
| if file_path.lower().endswith('.md'): | |
| self.process_file(file_path) | |
| else: | |
| self.log(f"Skipped: '{os.path.basename(file_path)}' is not a .md file.") | |
| self.log("--- All files processed ---") | |
| def process_file(self, file_path): | |
| """Reads, converts, and saves a single markdown file.""" | |
| filename = os.path.basename(file_path) | |
| self.log(f"Processing: '{filename}'...") | |
| try: | |
| # Read the markdown file | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| markdown_content = f.read() | |
| # Convert the content | |
| smf_content = convert_md_to_smf(markdown_content) | |
| # Define the output path | |
| base_name = os.path.splitext(file_path)[0] | |
| output_path = f"{base_name}_smf.txt" | |
| # Save the converted content | |
| with open(output_path, 'w', encoding='utf-8') as f: | |
| f.write(smf_content) | |
| self.log(f"Success! Saved to: '{os.path.basename(output_path)}'") | |
| except Exception as e: | |
| self.log(f"Error processing '{filename}': {e}") | |
| # --- Main Execution --- | |
| if __name__ == "__main__": | |
| app = App() | |
| app.mainloop() |