broadfield-dev commited on
Commit
1dd192e
·
verified ·
1 Parent(s): a818a11

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -51
app.py CHANGED
@@ -12,6 +12,8 @@ from pygments.formatters import HtmlFormatter
12
  from pygments.styles import get_all_styles
13
 
14
  app = Flask(__name__)
 
 
15
  TEMP_DIR = "/tmp/markdown_temp"
16
  os.makedirs(TEMP_DIR, exist_ok=True)
17
 
@@ -22,7 +24,7 @@ def parse_repo2markdown(text):
22
  if first_match:
23
  intro_text = text[:first_match.start()].strip()
24
  if intro_text:
25
- components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_text})
26
  for match in pattern.finditer(text):
27
  filename = match.group(1).strip()
28
  raw_content = match.group(2).strip()
@@ -30,14 +32,15 @@ def parse_repo2markdown(text):
30
  if code_match:
31
  components.append({'type': 'file', 'filename': filename, 'content': code_match.group(2).strip(), 'is_code_block': True, 'language': code_match.group(1)})
32
  else:
33
- components.append({'type': 'file', 'filename': filename, 'content': raw_content, 'is_code_block': False})
34
  return components
35
 
36
  def parse_standard_readme(text):
37
  components = []
38
  parts = re.split(r'^(## .*?)$', text, flags=re.MULTILINE)
39
- if parts[0].strip():
40
- components.append({'type': 'intro', 'filename': 'Header', 'content': parts[0].strip()})
 
41
  for i in range(1, len(parts), 2):
42
  components.append({'type': 'section', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
43
  return components
@@ -45,63 +48,47 @@ def parse_standard_readme(text):
45
  def parse_changelog(text):
46
  components = []
47
  parts = re.split(r'^(## \[\d+\.\d+\.\d+.*?\].*?)$', text, flags=re.MULTILINE)
48
- if parts[0].strip():
49
- components.append({'type': 'intro', 'filename': 'Changelog Header', 'content': parts[0].strip()})
 
50
  for i in range(1, len(parts), 2):
51
  components.append({'type': 'version', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
52
  return components
53
 
54
  def parse_agent_action(text):
55
  components = []
 
 
56
  action_pattern = re.compile(r'^### HF_ACTION: (.*)$', re.MULTILINE)
57
  for match in action_pattern.finditer(text):
58
  components.append({'type': 'action', 'filename': 'Agent Command', 'content': match.group(1).strip()})
 
 
 
 
 
 
 
59
  file_pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\n## File Structure|\n### HF_ACTION:|\Z)', re.MULTILINE)
60
  for match in file_pattern.finditer(text):
61
- components.append({'type': 'file', 'filename': match.group(1).strip(), 'content': match.group(2).strip()})
62
- struct_match = re.search(r'## File Structure\n(```[\s\S]*?```)', text)
63
- if struct_match:
64
- components.append({'type': 'structure', 'filename': 'File Structure', 'content': struct_match.group(1).strip()})
65
- return components
66
-
67
- def build_full_html(markdown_text, styles, include_fontawesome):
68
- wrapper_id = "#output-wrapper"
69
- font_family = styles.get('font_family', "'Inter', sans-serif")
70
- google_font_name = font_family.split(',')[0].strip("'\"")
71
- google_font_link = f'<link href="https://fonts.googleapis.com/css2?family={google_font_name.replace(" ", "+")}:wght@400;700&display=swap" rel="stylesheet">'
72
-
73
- highlight_theme = styles.get('highlight_theme', 'monokai')
74
- pygments_css = ""
75
- if highlight_theme != 'none':
76
- formatter = HtmlFormatter(style=highlight_theme, cssclass="codehilite")
77
- pygments_css = formatter.get_style_defs(f' {wrapper_id}')
78
 
79
- scoped_css = f"""
80
- body {{ background-color: {styles.get('background_color', '#ffffff')}; margin: 0; padding: 0; }}
81
- {wrapper_id} {{
82
- font-family: {font_family};
83
- font-size: {styles.get('font_size', '16')}px;
84
- line-height: {styles.get('line_height', '1.6')};
85
- color: {styles.get('text_color', '#333')};
86
- background-color: {styles.get('background_color', '#fff')};
87
- padding: {styles.get('page_padding', '40')}px;
88
- }}
89
- {wrapper_id} table {{ border-collapse: collapse; width: 100%; margin-bottom: 1em; }}
90
- {wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }}
91
- {wrapper_id} pre {{ padding: {styles.get('code_padding', '15')}px; border-radius: 8px; overflow-x: auto; }}
92
- {wrapper_id} h1, {wrapper_id} h2, {wrapper_id} h3 {{ border-bottom: 1px solid #eee; padding-bottom: 5px; }}
93
- {pygments_css}
94
- {styles.get('custom_css', '')}
95
- """
96
- html_content = markdown.markdown(markdown_text, extensions=['fenced_code', 'tables', 'codehilite', 'nl2br'])
97
- fa = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">' if include_fontawesome else ""
98
- return f"<!DOCTYPE html><html><head><meta charset='UTF-8'>{google_font_link}{fa}<style>{scoped_css}</style></head><body><div id='output-wrapper'>{html_content}</div></body></html>"
99
 
100
  @app.route('/parse', methods=['POST'])
101
  def parse_endpoint():
102
  text = request.form.get('markdown_text', '')
103
  if 'markdown_file' in request.files and request.files['markdown_file'].filename != '':
104
  text = request.files['markdown_file'].read().decode('utf-8')
 
 
105
  try:
106
  if "### HF_ACTION:" in text or "File and Code Formatting:" in text:
107
  format_name, components = "Agent Action", parse_agent_action(text)
@@ -113,25 +100,79 @@ def parse_endpoint():
113
  format_name, components = "Standard README", parse_standard_readme(text)
114
  else:
115
  format_name, components = "Unknown", [{'type': 'text', 'filename': 'Full Text', 'content': text}]
 
116
  return jsonify({'format': format_name, 'components': components})
117
  except Exception as e:
118
- return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  @app.route('/convert', methods=['POST'])
121
  def convert_endpoint():
122
  data = request.json
123
  try:
124
- full_html = build_full_html(data.get('markdown_text', ''), data.get('styles', {}), data.get('include_fontawesome', False))
125
- options = {"quiet": "", "encoding": "UTF-8", "width": 1024, "disable-smart-width": ""}
 
 
 
 
 
126
  if data.get('download', False):
127
- if data.get('download_type') == 'html':
 
128
  return send_file(BytesIO(full_html.encode("utf-8")), as_attachment=True, download_name="output.html", mimetype="text/html")
 
 
 
 
129
  png_bytes = imgkit.from_string(full_html, False, options=options)
130
- return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
131
- png_bytes = imgkit.from_string(full_html, False, options=options)
132
- return jsonify({'preview_html': full_html, 'preview_png_base64': base64.b64encode(png_bytes).decode('utf-8')})
133
  except Exception as e:
134
- return jsonify({'error': str(e)}), 500
 
135
 
136
  @app.route('/')
137
  def index():
 
12
  from pygments.styles import get_all_styles
13
 
14
  app = Flask(__name__)
15
+
16
+ # Corrected line to use the universally writable /tmp directory
17
  TEMP_DIR = "/tmp/markdown_temp"
18
  os.makedirs(TEMP_DIR, exist_ok=True)
19
 
 
24
  if first_match:
25
  intro_text = text[:first_match.start()].strip()
26
  if intro_text:
27
+ components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_text, 'is_code_block': False, 'language': ''})
28
  for match in pattern.finditer(text):
29
  filename = match.group(1).strip()
30
  raw_content = match.group(2).strip()
 
32
  if code_match:
33
  components.append({'type': 'file', 'filename': filename, 'content': code_match.group(2).strip(), 'is_code_block': True, 'language': code_match.group(1)})
34
  else:
35
+ components.append({'type': 'file', 'filename': filename, 'content': raw_content, 'is_code_block': False, 'language': ''})
36
  return components
37
 
38
  def parse_standard_readme(text):
39
  components = []
40
  parts = re.split(r'^(## .*?)$', text, flags=re.MULTILINE)
41
+ intro_content = parts[0].strip()
42
+ if intro_content:
43
+ components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_content})
44
  for i in range(1, len(parts), 2):
45
  components.append({'type': 'section', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
46
  return components
 
48
  def parse_changelog(text):
49
  components = []
50
  parts = re.split(r'^(## \[\d+\.\d+\.\d+.*?\].*?)$', text, flags=re.MULTILINE)
51
+ intro_content = parts[0].strip()
52
+ if intro_content:
53
+ components.append({'type': 'intro', 'filename': 'Changelog Header', 'content': intro_content})
54
  for i in range(1, len(parts), 2):
55
  components.append({'type': 'version', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()})
56
  return components
57
 
58
  def parse_agent_action(text):
59
  components = []
60
+
61
+ # Extract Actions
62
  action_pattern = re.compile(r'^### HF_ACTION: (.*)$', re.MULTILINE)
63
  for match in action_pattern.finditer(text):
64
  components.append({'type': 'action', 'filename': 'Agent Command', 'content': match.group(1).strip()})
65
+
66
+ # Extract File Structure
67
+ structure_match = re.search(r'## File Structure\n(```[\s\S]*?```)', text)
68
+ if structure_match:
69
+ components.append({'type': 'structure', 'filename': 'Project Structure', 'content': structure_match.group(1).strip()})
70
+
71
+ # Extract Files (using the existing logic from Repo2Markdown)
72
  file_pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\n## File Structure|\n### HF_ACTION:|\Z)', re.MULTILINE)
73
  for match in file_pattern.finditer(text):
74
+ filename = match.group(1).strip()
75
+ content = match.group(2).strip()
76
+ is_code = "```" in content
77
+ components.append({'type': 'file', 'filename': filename, 'content': content, 'is_code_block': is_code})
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ # Instructions (Everything else)
80
+ if not components:
81
+ components.append({'type': 'instruction', 'filename': 'Instructions', 'content': text})
82
+
83
+ return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  @app.route('/parse', methods=['POST'])
86
  def parse_endpoint():
87
  text = request.form.get('markdown_text', '')
88
  if 'markdown_file' in request.files and request.files['markdown_file'].filename != '':
89
  text = request.files['markdown_file'].read().decode('utf-8')
90
+ if not text: return jsonify({'error': 'No text or file provided.'}), 400
91
+
92
  try:
93
  if "### HF_ACTION:" in text or "File and Code Formatting:" in text:
94
  format_name, components = "Agent Action", parse_agent_action(text)
 
100
  format_name, components = "Standard README", parse_standard_readme(text)
101
  else:
102
  format_name, components = "Unknown", [{'type': 'text', 'filename': 'Full Text', 'content': text}]
103
+
104
  return jsonify({'format': format_name, 'components': components})
105
  except Exception as e:
106
+ return jsonify({'error': f'Failed to parse: {e}'}), 500
107
+
108
+ def build_full_html(markdown_text, styles, include_fontawesome):
109
+ wrapper_id = "#output-wrapper"
110
+ font_family = styles.get('font_family', "'Arial', sans-serif")
111
+ google_font_name = font_family.split(',')[0].strip("'\"")
112
+ google_font_link = ""
113
+ if " " in google_font_name and google_font_name not in ["Times New Roman", "Courier New"]:
114
+ google_font_link = f'<link href="https://fonts.googleapis.com/css2?family={google_font_name.replace(" ", "+")}:wght@400;700&display=swap" rel="stylesheet">'
115
+
116
+ highlight_theme = styles.get('highlight_theme', 'default')
117
+ pygments_css = ""
118
+ if highlight_theme != 'none':
119
+ formatter = HtmlFormatter(style=highlight_theme, cssclass="codehilite")
120
+ pygments_css = formatter.get_style_defs(f' {wrapper_id}')
121
+
122
+ scoped_css = f"""
123
+ {wrapper_id} {{
124
+ font-family: {font_family}; font-size: {styles.get('font_size', '16')}px;
125
+ color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')};
126
+ }}
127
+ {wrapper_id} table {{ border-collapse: collapse; width: 100%; }}
128
+ {wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
129
+ {wrapper_id} th {{ background-color: #f2f2f2; }}
130
+ {wrapper_id} img {{ max-width: 100%; height: auto; }}
131
+ {wrapper_id} pre {{ padding: {styles.get('code_padding', '15')}px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; }}
132
+ {wrapper_id} h1, {wrapper_id} h2, {wrapper_id} h3 {{ border-bottom: 1px solid #eee; padding-bottom: 5px; margin-top: 1.5em; }}
133
+ {wrapper_id} :not(pre) > code {{ font-family: 'Courier New', monospace; background-color: #eef; padding: .2em .4em; border-radius: 3px; }}
134
+ {pygments_css} {styles.get('custom_css', '')}
135
+ """
136
+
137
+ md_extensions = ['fenced_code', 'tables', 'codehilite']
138
+ html_content = markdown.markdown(markdown_text, extensions=md_extensions, extension_configs={'codehilite': {'css_class': 'codehilite'}})
139
+ final_html_body = f'<div id="output-wrapper">{html_content}</div>'
140
+
141
+ 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 ""
142
+
143
+ full_html = f"""<!DOCTYPE html>
144
+ <html><head><meta charset="UTF-8">{google_font_link}{fontawesome_link}<style>
145
+ #ouput-wrapper {{ background-color: {styles.get('background_color', '#fff')}; padding: 25px; display: inline-block;}}
146
+ {scoped_css}
147
+ </style></head><body>{final_html_body}</body></html>"""
148
+
149
+ return full_html
150
 
151
  @app.route('/convert', methods=['POST'])
152
  def convert_endpoint():
153
  data = request.json
154
  try:
155
+ full_html = build_full_html(
156
+ markdown_text=data.get('markdown_text', ''),
157
+ styles=data.get('styles', {}),
158
+ include_fontawesome=data.get('include_fontawesome', False)
159
+ )
160
+ options = {"quiet": "", 'encoding': "UTF-8"}
161
+
162
  if data.get('download', False):
163
+ download_type = data.get('download_type', 'png')
164
+ if download_type == 'html':
165
  return send_file(BytesIO(full_html.encode("utf-8")), as_attachment=True, download_name="output.html", mimetype="text/html")
166
+ else:
167
+ png_bytes = imgkit.from_string(full_html, False, options=options)
168
+ return send_file(BytesIO(png_bytes), as_attachment=True, download_name="output.png", mimetype="image/png")
169
+ else:
170
  png_bytes = imgkit.from_string(full_html, False, options=options)
171
+ png_base64 = base64.b64encode(png_bytes).decode('utf-8')
172
+ return jsonify({'preview_html': full_html, 'preview_png_base64': png_base64})
 
173
  except Exception as e:
174
+ traceback.print_exc()
175
+ return jsonify({'error': f'Failed to convert content: {str(e)}'}), 500
176
 
177
  @app.route('/')
178
  def index():