broadfield-dev commited on
Commit
6fb9f70
·
verified ·
1 Parent(s): 7aa2446

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -107
app.py CHANGED
@@ -25,122 +25,112 @@ DEFAULT_STYLES = {
25
 
26
  def is_repo2markdown_format(text):
27
  """Detects if the text is in the Repo2Markdown format."""
28
- return "## File Structure" in text and text.count("### File:") > 1
29
 
30
  def parse_repo2markdown(text):
31
  """
32
- Parses Repo2Markdown text to extract individual files.
33
- Returns a list of dictionaries, each representing a file.
34
  """
35
  components = []
36
  # Regex to find sections starting with '### File:'
37
- # It captures the filename and all content until the next '### File:' or the end of the string.
38
  pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\Z)', re.MULTILINE)
39
 
40
- # Find the introductory text before the first file.
41
  first_match = pattern.search(text)
42
  if first_match:
43
  intro_text = text[:first_match.start()].strip()
44
  if intro_text:
45
- components.append({
46
- 'type': 'intro',
47
- 'filename': 'Introduction',
48
- 'content': intro_text,
49
- 'is_selected': True
50
- })
51
 
52
- # Find all file sections
53
  for match in pattern.finditer(text):
54
  filename = match.group(1).strip()
55
- content = match.group(2).strip()
56
- components.append({
57
- 'type': 'file',
58
- 'filename': filename,
59
- 'content': content,
60
- 'is_selected': True # Default to selected
61
- })
62
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  return components
64
 
65
  @app.route("/", methods=["GET", "POST"])
66
  def index():
67
  """Main route to handle parsing, component selection, and conversion."""
68
- # Initialize variables
69
  preview_html = None
70
  download_available = False
71
  error_message = None
72
  components = []
73
-
74
- # Set default values on GET request
 
75
  markdown_text = ""
76
  download_type = "png"
77
  styles = DEFAULT_STYLES.copy()
78
  include_fontawesome = False
79
 
80
  if request.method == "POST":
81
- # Preserve styling options across all POST requests
82
  styles = {key: request.form.get(key, default) for key, default in DEFAULT_STYLES.items()}
83
  include_fontawesome = "include_fontawesome" in request.form
84
  download_type = request.form.get("download_type", "png")
 
 
 
 
 
85
 
86
- final_markdown_to_render = ""
 
 
 
 
 
 
 
87
 
88
- # Determine the action based on the button/data submitted
89
- action = "parse"
90
- if 'generate_from_components' in request.form:
91
- action = "generate"
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- # --- ACTION 1: PARSE uploaded text or file ---
94
- if action == "parse":
95
- markdown_text = request.form.get("markdown_text", "")
96
- uploaded_file = request.files.get("markdown_file")
97
- if uploaded_file and uploaded_file.filename != '':
98
- try:
99
- markdown_text = uploaded_file.read().decode("utf-8")
100
- except Exception as e:
101
- error_message = f"Error reading file: {e}"
102
-
103
- if markdown_text and is_repo2markdown_format(markdown_text):
104
- try:
105
- components = parse_repo2markdown(markdown_text)
106
- if not components:
107
- error_message = "Repo2Markdown format detected, but no file components could be parsed."
108
- except Exception as e:
109
- error_message = f"Error parsing Repo2Markdown format: {e}"
110
- else:
111
- final_markdown_to_render = markdown_text
112
 
113
- # --- ACTION 2: GENERATE from selected components ---
114
- elif action == "generate":
115
- final_markdown_parts = []
116
-
117
- # Re-read component data from hidden form fields to reconstruct the UI
118
- comp_count = int(request.form.get('component_count', 0))
119
- for i in range(comp_count):
120
- comp_type = request.form.get(f'comp_type_{i}')
121
- if not comp_type: continue
122
-
123
- is_selected = f'include_comp_{i}' in request.form
124
- filename = request.form.get(f'comp_filename_{i}')
125
- content = request.form.get(f'comp_content_{i}')
126
 
127
- # Re-add to components list to maintain UI state
128
- components.append({
129
- 'type': comp_type,
130
- 'filename': filename,
131
- 'content': content,
132
- 'is_selected': is_selected
133
- })
134
-
135
- # If selected, add it to the final document to be rendered
136
- if is_selected:
137
- if comp_type == 'intro':
138
- final_markdown_parts.append(content)
139
- else: # file
140
- final_markdown_parts.append(f"### File: {filename}\n{content}")
141
 
142
- final_markdown_to_render = "\n\n---\n\n".join(final_markdown_parts)
143
-
144
  # --- HTML & PNG Conversion Logic ---
145
  if final_markdown_to_render:
146
  try:
@@ -149,6 +139,7 @@ def index():
149
  fontawesome_link = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">' if include_fontawesome else ""
150
  style_block = f"""<style>
151
  body {{ font-family: {styles['font_family']}; font-size: {styles['font_size']}px; color: {styles['text_color']}; background-color: {styles['background_color']}; padding: 25px; display: inline-block; }}
 
152
  table {{ border-collapse: collapse; width: 100%; }}
153
  th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
154
  th {{ background-color: #f2f2f2; }}
@@ -175,7 +166,6 @@ def index():
175
  error_message = f"An error occurred during conversion: {e}"
176
  print(f"Error: {traceback.format_exc()}")
177
 
178
- # --- RENDER THE MAIN TEMPLATE ---
179
  return render_template_string("""
180
  <!DOCTYPE html>
181
  <html lang="en">
@@ -184,7 +174,8 @@ def index():
184
  <title>Advanced Markdown Converter & Composer</title>
185
  <style>
186
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background-color: #f9f9f9; }
187
- h1, h2 { text-align: center; color: #333; }
 
188
  form { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
189
  textarea { width: 100%; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; padding: 10px; font-family: monospace; }
190
  .controls { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 20px; margin-top: 20px; }
@@ -203,7 +194,6 @@ def index():
203
  .preview { border: 1px solid #ddd; padding: 20px; margin-top: 20px; background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
204
  .error { color: #D8000C; background-color: #FFD2D2; padding: 10px; border-radius: 5px; margin-top: 15px; }
205
  .info { color: #00529B; background-color: #BDE5F8; padding: 10px; border-radius: 5px; margin: 10px 0; }
206
- /* Component Selection Styles */
207
  .component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
208
  .component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
209
  .component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
@@ -222,29 +212,20 @@ def index():
222
  </head>
223
  <body>
224
  <h1>Advanced Markdown Converter & Composer</h1>
225
-
226
  <form method="post" enctype="multipart/form-data">
227
- <!-- This section is shown if no components are parsed yet -->
228
- {% if not components %}
229
- <fieldset>
230
- <legend>Option 1: Paste Markdown</legend>
231
- <textarea name="markdown_text" rows="10" placeholder="Paste your Markdown here...">{{ markdown_text }}</textarea>
232
- </fieldset>
233
- <h2 style="font-size: 1.2em;">OR</h2>
234
- <fieldset>
235
- <legend>Option 2: Upload a Markdown File</legend>
236
- <input type="file" name="markdown_file" accept=".md,.txt,text/markdown">
237
- </fieldset>
238
- <div class="controls"><button type="submit" class="action-btn">Parse and Preview</button></div>
239
- {% endif %}
240
-
241
- <!-- This section is shown only after a Repo2Markdown file is parsed -->
242
  {% if components %}
243
- <input type="hidden" name="generate_from_components" value="true">
244
- <input type="hidden" name="component_count" value="{{ components|length }}">
245
  <fieldset>
246
  <legend>Detected File Components</legend>
247
- <div class="info">Repo2Markdown format detected. Select the files to include in the final document.</div>
248
  <div class="selection-controls">
249
  <button type="button" onclick="toggleAllComponents(true)">Select All</button>
250
  <button type="button" onclick="toggleAllComponents(false)">Deselect All</button>
@@ -253,23 +234,18 @@ def index():
253
  {% for comp in components %}
254
  <div class="component-container">
255
  <div class="component-header">
256
- <input type="checkbox" name="include_comp_{{ loop.index0 }}" id="include_comp_{{ loop.index0 }}" class="component-checkbox" {% if comp.is_selected %}checked{% endif %}>
257
  <label for="include_comp_{{ loop.index0 }}">{{ comp.filename }}</label>
258
  </div>
259
  <div class="component-content">
260
  <textarea readonly>{{ comp.content }}</textarea>
261
  </div>
262
  </div>
263
- <!-- Hidden fields to preserve data on next post -->
264
- <input type="hidden" name="comp_type_{{ loop.index0 }}" value="{{ comp.type }}">
265
- <input type="hidden" name="comp_filename_{{ loop.index0 }}" value="{{ comp.filename }}">
266
- <input type="hidden" name="comp_content_{{ loop.index0 }}" value="{{ comp.content }}">
267
  {% endfor %}
268
  </div>
269
  </fieldset>
270
  {% endif %}
271
 
272
- <!-- Styling and Generation controls -->
273
  <fieldset>
274
  <legend>Styling Options</legend>
275
  <div class="style-grid">
@@ -286,9 +262,7 @@ def index():
286
 
287
  <div class="controls">
288
  <div class="main-actions">
289
- <button type="submit" class="action-btn">
290
- {% if components %}Re-Generate Preview{% else %}Generate Preview{% endif %}
291
- </button>
292
  <div><label for="download_type">Output format:</label><select id="download_type" name="download_type"><option value="png" {% if download_type == 'png' %}selected{% endif %}>PNG</option><option value="html" {% if download_type == 'html' %}selected{% endif %}>HTML</option></select></div>
293
  {% if download_available %}<button type="submit" name="download" value="true" class="download-btn">Download {{ download_type.upper() }}</button>{% endif %}
294
  </div>
 
25
 
26
  def is_repo2markdown_format(text):
27
  """Detects if the text is in the Repo2Markdown format."""
28
+ return "## File Structure" in text and text.count("### File:") > 0
29
 
30
  def parse_repo2markdown(text):
31
  """
32
+ Parses Repo2Markdown text, extracts files, and cleans content for display.
 
33
  """
34
  components = []
35
  # Regex to find sections starting with '### File:'
 
36
  pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\Z)', re.MULTILINE)
37
 
 
38
  first_match = pattern.search(text)
39
  if first_match:
40
  intro_text = text[:first_match.start()].strip()
41
  if intro_text:
42
+ components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_text, 'is_code_block': False, 'language': ''})
 
 
 
 
 
43
 
 
44
  for match in pattern.finditer(text):
45
  filename = match.group(1).strip()
46
+ raw_content = match.group(2).strip()
 
 
 
 
 
 
47
 
48
+ # Check if the entire content is a single fenced code block
49
+ code_match = re.search(r'^```(\w*)\s*\n([\s\S]*?)\s*```$', raw_content, re.DOTALL)
50
+
51
+ if code_match:
52
+ # It's a code block; store the inner content and language
53
+ language = code_match.group(1)
54
+ inner_content = code_match.group(2).strip()
55
+ components.append({'type': 'file', 'filename': filename, 'content': inner_content, 'is_code_block': True, 'language': language})
56
+ else:
57
+ # It's plain text (e.g., binary file notification)
58
+ components.append({'type': 'file', 'filename': filename, 'content': raw_content, 'is_code_block': False, 'language': ''})
59
+
60
  return components
61
 
62
  @app.route("/", methods=["GET", "POST"])
63
  def index():
64
  """Main route to handle parsing, component selection, and conversion."""
 
65
  preview_html = None
66
  download_available = False
67
  error_message = None
68
  components = []
69
+ final_markdown_to_render = ""
70
+
71
+ # Set defaults for a GET request
72
  markdown_text = ""
73
  download_type = "png"
74
  styles = DEFAULT_STYLES.copy()
75
  include_fontawesome = False
76
 
77
  if request.method == "POST":
78
+ # Always update styles and settings from the form on any POST
79
  styles = {key: request.form.get(key, default) for key, default in DEFAULT_STYLES.items()}
80
  include_fontawesome = "include_fontawesome" in request.form
81
  download_type = request.form.get("download_type", "png")
82
+
83
+ # --- TEXT PROCESSING LOGIC ---
84
+ # Prioritize new text/file upload. This is the source of truth for parsing.
85
+ new_markdown_text = request.form.get("markdown_text", "")
86
+ uploaded_file = request.files.get("markdown_file")
87
 
88
+ if uploaded_file and uploaded_file.filename != '':
89
+ try:
90
+ markdown_text = uploaded_file.read().decode("utf-8")
91
+ except Exception as e:
92
+ error_message = f"Error reading file: {e}"
93
+ markdown_text = "" # Clear text on error
94
+ else:
95
+ markdown_text = new_markdown_text
96
 
97
+ # --- PARSING & COMPONENT BUILDING ---
98
+ if markdown_text and is_repo2markdown_format(markdown_text):
99
+ try:
100
+ # Always re-parse the source text to generate fresh components
101
+ parsed_components = parse_repo2markdown(markdown_text)
102
+ final_markdown_parts = []
103
+
104
+ # Check for user selections and reconstruct both the component list for the UI
105
+ # and the final markdown for rendering.
106
+ for i, comp_data in enumerate(parsed_components):
107
+ is_selected = f'include_comp_{i}' in request.form
108
+
109
+ # Update component with selection status for re-rendering the UI
110
+ comp_data['is_selected'] = is_selected
111
+ components.append(comp_data)
112
 
113
+ # If selected, add it to the list for the final document
114
+ if is_selected:
115
+ if comp_data['type'] == 'intro':
116
+ final_markdown_parts.append(comp_data['content'])
117
+ else: # It's a file
118
+ reconstructed_content = ""
119
+ if comp_data['is_code_block']:
120
+ # Re-add the fences for rendering
121
+ reconstructed_content = f"```{comp_data['language']}\n{comp_data['content']}\n```"
122
+ else:
123
+ reconstructed_content = comp_data['content']
124
+ final_markdown_parts.append(f"### File: {comp_data['filename']}\n{reconstructed_content}")
 
 
 
 
 
 
 
125
 
126
+ final_markdown_to_render = "\n\n---\n\n".join(final_markdown_parts)
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ except Exception as e:
129
+ error_message = f"Error processing Repo2Markdown: {e}"
130
+ else:
131
+ # If not Repo2Markdown format, just treat it as plain markdown
132
+ final_markdown_to_render = markdown_text
 
 
 
 
 
 
 
 
 
133
 
 
 
134
  # --- HTML & PNG Conversion Logic ---
135
  if final_markdown_to_render:
136
  try:
 
139
  fontawesome_link = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">' if include_fontawesome else ""
140
  style_block = f"""<style>
141
  body {{ font-family: {styles['font_family']}; font-size: {styles['font_size']}px; color: {styles['text_color']}; background-color: {styles['background_color']}; padding: 25px; display: inline-block; }}
142
+ h3 {{ border-bottom: 1px solid #ccc; padding-bottom: 5px; margin-top: 2em; }}
143
  table {{ border-collapse: collapse; width: 100%; }}
144
  th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
145
  th {{ background-color: #f2f2f2; }}
 
166
  error_message = f"An error occurred during conversion: {e}"
167
  print(f"Error: {traceback.format_exc()}")
168
 
 
169
  return render_template_string("""
170
  <!DOCTYPE html>
171
  <html lang="en">
 
174
  <title>Advanced Markdown Converter & Composer</title>
175
  <style>
176
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background-color: #f9f9f9; }
177
+ h1, h2, h3 { color: #333; }
178
+ h1 { text-align: center; }
179
  form { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
180
  textarea { width: 100%; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; padding: 10px; font-family: monospace; }
181
  .controls { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 20px; margin-top: 20px; }
 
194
  .preview { border: 1px solid #ddd; padding: 20px; margin-top: 20px; background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
195
  .error { color: #D8000C; background-color: #FFD2D2; padding: 10px; border-radius: 5px; margin-top: 15px; }
196
  .info { color: #00529B; background-color: #BDE5F8; padding: 10px; border-radius: 5px; margin: 10px 0; }
 
197
  .component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
198
  .component-container { border: 1px solid #e0e0e0; border-radius: 5px; background: #fafafa; }
199
  .component-header { background: #f1f1f1; padding: 8px 12px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; }
 
212
  </head>
213
  <body>
214
  <h1>Advanced Markdown Converter & Composer</h1>
 
215
  <form method="post" enctype="multipart/form-data">
216
+ <fieldset>
217
+ <legend>Source Content</legend>
218
+ <div class="info">Paste your content below, or upload a file. If Repo2Markdown format is detected, component selection will appear.</div>
219
+ <textarea name="markdown_text" rows="8" placeholder="Paste your Markdown here...">{{ markdown_text }}</textarea>
220
+ <div style="margin-top: 10px; display: flex; align-items: center; gap: 10px;">
221
+ <label for="markdown_file" style="margin-bottom:0;">Or upload a file:</label>
222
+ <input type="file" name="markdown_file" id="markdown_file" accept=".md,.txt,text/markdown">
223
+ </div>
224
+ </fieldset>
225
+
 
 
 
 
 
226
  {% if components %}
 
 
227
  <fieldset>
228
  <legend>Detected File Components</legend>
 
229
  <div class="selection-controls">
230
  <button type="button" onclick="toggleAllComponents(true)">Select All</button>
231
  <button type="button" onclick="toggleAllComponents(false)">Deselect All</button>
 
234
  {% for comp in components %}
235
  <div class="component-container">
236
  <div class="component-header">
237
+ <input type="checkbox" name="include_comp_{{ loop.index0 }}" id="include_comp_{{ loop.index0 }}" class="component-checkbox" {% if comp.is_selected %}checked{% else %}checked{% endif %}>
238
  <label for="include_comp_{{ loop.index0 }}">{{ comp.filename }}</label>
239
  </div>
240
  <div class="component-content">
241
  <textarea readonly>{{ comp.content }}</textarea>
242
  </div>
243
  </div>
 
 
 
 
244
  {% endfor %}
245
  </div>
246
  </fieldset>
247
  {% endif %}
248
 
 
249
  <fieldset>
250
  <legend>Styling Options</legend>
251
  <div class="style-grid">
 
262
 
263
  <div class="controls">
264
  <div class="main-actions">
265
+ <button type="submit" class="action-btn">Generate / Update Preview</button>
 
 
266
  <div><label for="download_type">Output format:</label><select id="download_type" name="download_type"><option value="png" {% if download_type == 'png' %}selected{% endif %}>PNG</option><option value="html" {% if download_type == 'html' %}selected{% endif %}>HTML</option></select></div>
267
  {% if download_available %}<button type="submit" name="download" value="true" class="download-btn">Download {{ download_type.upper() }}</button>{% endif %}
268
  </div>