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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -92
app.py CHANGED
@@ -12,8 +12,6 @@ from pygments.formatters import HtmlFormatter
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,7 +22,7 @@ def parse_repo2markdown(text):
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,15 +30,14 @@ def parse_repo2markdown(text):
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,47 +45,63 @@ def parse_standard_readme(text):
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,79 +113,25 @@ def parse_endpoint():
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():
 
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
  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
  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
  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
  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():