ipynb_to_medium
1import html 2import os 3import re 4 5import jupytext 6import markdown 7import requests 8from bs4 import BeautifulSoup 9from markdown.extensions import fenced_code 10from nbformat import read 11 12 13class NotebookToMedium: 14 """ 15 Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API. 16 """ 17 18 def __init__(self): 19 self.md = markdown.Markdown(extensions=[fenced_code.FencedCodeExtension()]) 20 21 def convert_notebook_to_markdown(self, input_notebook, output_markdown): 22 """ 23 Convert a Jupyter notebook to Markdown and save it to a file. 24 25 Args: 26 input_notebook (str): Path to the input Jupyter notebook. 27 output_markdown (str): Path to the output Markdown file. 28 """ 29 with open(input_notebook, "r", encoding="utf-8") as notebook_file: 30 notebook = read(notebook_file, as_version=4) 31 32 markdown_text = jupytext.writes(notebook, fmt="markdown") 33 34 # Remove the Jupytext metadata from the Markdown 35 markdown_text = re.sub( 36 r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL 37 ) 38 39 with open(output_markdown, "w", encoding="utf-8") as output_file: 40 output_file.write(markdown_text) 41 42 def convert_markdown_to_html( 43 self, 44 input_markdown, 45 output_html, 46 nest_as_medium=True, 47 transform_pre_code=True, 48 add_title_to_pictures=True, 49 ): 50 """ 51 Convert a Markdown file to HTML and save it to a file. 52 53 Args: 54 input_markdown (str): Path to the input Markdown file. 55 output_html (str): Path to the output HTML file. 56 """ 57 58 with open(input_markdown, "r", encoding="utf-8") as markdown_file: 59 markdown_text = markdown_file.read() 60 61 markdown_text = html.escape(markdown_text) 62 63 html_text = self.md.convert(markdown_text) 64 65 if transform_pre_code: 66 html_text = self.transform_pre_code(html_text) 67 68 if nest_as_medium: 69 html_text = self.transform_nested_ul_to_medium_nested_list(html_text) 70 71 if add_title_to_pictures: 72 html_text = self.add_title_to_pictures(html_text) 73 74 html_text = html.unescape(html_text) 75 76 with open(output_html, "w", encoding="utf-8") as output_file: 77 output_file.write(html_text) 78 79 def convert_notebook_to_html(self, input_notebook, output_html): 80 """ 81 Convert a Jupyter notebook to HTML. 82 83 This method performs the conversion by first converting the notebook to Markdown, 84 and then converting the Markdown to HTML. 85 IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead 86 of converting directly to html with jupytext is purposeful 87 88 Args: 89 input_notebook (str): Path to the input Jupyter notebook. 90 output_html (str): Path to the output HTML file. 91 """ 92 temp_markdown = "temp_markdown.md" 93 94 # Convert the notebook to Markdown 95 self.convert_notebook_to_markdown(input_notebook, temp_markdown) 96 97 # Convert the Markdown to HTML 98 self.convert_markdown_to_html(temp_markdown, output_html) 99 100 # Remove the temporary Markdown file 101 os.remove(temp_markdown) 102 103 def transform_nested_ul_to_medium_nested_list(self, input_string): 104 """ 105 Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format. 106 107 Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists. 108 109 Args: 110 input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags. 111 112 Returns: 113 str: The transformed HTML string. 114 """ 115 soup = BeautifulSoup(input_string, "html.parser") 116 117 # Find and replace NESTED <ul> tags and its <li> tags for Medium 118 for ul1 in soup.find_all("ul"): 119 for ul2 in ul1.find_all("ul"): 120 replace_string = " ".join(str(item) for item in ul2.contents) 121 replace_string = replace_string.replace("<li>", "<br>\n- ").replace( 122 "</li>", "" 123 ) 124 ul2.replace_with(replace_string) 125 126 result = str(soup.prettify()) 127 return result 128 129 def transform_pre_code(self, input_string): 130 """ 131 Transform ```<pre>``` elements with ```<code>``` tags inside. 132 133 This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags, 134 extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element 135 with new attributes for Medium.com-friendly code blocks. 136 137 Args: 138 input_string (str): The input HTML string. 139 140 Returns: 141 str: The transformed HTML string with updated attributes for `<pre>` elements. 142 """ 143 soup = BeautifulSoup(input_string, "html.parser") 144 for pre in soup.find_all("pre"): 145 code = pre.find("code") 146 if code: 147 language_list = code.get("class") 148 language = "" 149 if language_list: 150 for item in language_list: 151 if "language-" in item: 152 language = item.replace("language-", "") 153 pre["data-code-block-lang"] = language 154 pre["data-code-block-mode"] = "2" 155 pre["spellcheck"] = "false" 156 pre["class"] = "graf--preV2" 157 pre["data-testid"] = "editorCodeBlockParagraph" 158 159 span_tag = soup.new_tag("span") 160 span_tag["class"] = "pre--content" 161 content = "".join(map(str, code.contents)) 162 span_tag.string = content 163 code.replace_with(span_tag) 164 165 return str(soup.prettify()) 166 167 def add_title_to_pictures(self, input_string): 168 """ 169 Add captions to images with titles in the input HTML. 170 171 This function searches for `<img>` elements with a "title" attribute or with a quoted " " 172 string in the URL (which comes from the Markdown) in the input HTML string and adds extra 173 tags to transform them into figures with captions. The title attribute is used 174 as the caption text. 175 176 Args: 177 input_string (str): The input HTML string containing `<img>` elements. 178 179 Returns: 180 str: The HTML string with captions added to images with titles. 181 """ 182 soup = BeautifulSoup(input_string, "html.parser") 183 for img in soup.find_all("img"): 184 title = img.get("title") 185 if not title: 186 src = img.get("src") 187 if '"' in src: 188 title = src.split('"')[1] 189 else: 190 title = "" 191 if title != "": 192 replace_string = ( 193 '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">' 194 + '<div class="aspectRatioPlaceholder">' 195 + str(img) 196 .replace(title, "") 197 .replace('"', "") # we don't want the title in the URL 198 + "</div>" 199 + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>' 200 + " </figure>" 201 ) 202 img.replace_with(replace_string) 203 result = str(soup.prettify()) 204 return result 205 206 def push_to_medium( 207 self, 208 file_to_upload, 209 medium_id, 210 token, 211 title, 212 tag_list, 213 publish_status="draft", 214 content_format="html", 215 ): 216 """ 217 Push an HTML file to Medium as a draft post. 218 219 Args: 220 input_file (str): Path to the .ipynb or .md file to be uploaded. 221 medium_id (str): User ID for Medium. 222 token (str): Medium API token. 223 title (str): Title for the Medium post. 224 tag_list (list): List of tags for the Medium post. 225 publish_status (str, optional): Publish status (default: 'draft'). 226 """ 227 if len(tag_list) > 5: 228 raise ValueError("Tag list should not contain more than 5 elements.") 229 230 with open(file_to_upload, "r", encoding="utf-8") as content_text: 231 content = content_text.read() 232 233 url = f"https://api.medium.com/v1/users/{medium_id}/posts" 234 235 post_data = { 236 "title": title, 237 "contentFormat": content_format, 238 "content": content, 239 "tags": tag_list, 240 "publishStatus": publish_status, 241 } 242 243 headers = { 244 "Authorization": f"Bearer {token}", 245 "Content-Type": "application/json", 246 "Accept": "application/json", 247 "Accept-Charset": "utf-8", 248 } 249 250 response = requests.post(url, headers=headers, json=post_data) 251 252 if response.status_code == 201: 253 post_details = response.json() 254 print("Draft Post Created Successfully:") 255 print("Post Details:") 256 print(post_details) 257 else: 258 print("Failed to create draft post. Status code:", response.status_code) 259 print("Response:", response.text) 260 261 def push_ipynb_or_md_to_medium( 262 self, input_file, medium_id, token, title, tag_list, publish_status="draft" 263 ): 264 """ 265 Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post. 266 267 Args: 268 input_file (str): Path to the .ipynb or .md file to be uploaded. 269 id (str): User ID for Medium. 270 token (str): Medium API token. 271 title (str): Title for the Medium post. 272 tag_list (list): List of tags for the Medium post. 273 publish_status (str, optional): Publish status (default: 'draft'). 274 """ 275 if input_file.lower().endswith(".ipynb"): 276 html_output_file = os.path.splitext(input_file)[0] + ".html" 277 self.convert_notebook_to_html(input_file, html_output_file) 278 elif input_file.lower().endswith(".md"): 279 html_output_file = os.path.splitext(input_file)[0] + ".html" 280 self.convert_markdown_to_html(input_file, html_output_file) 281 else: 282 raise ValueError("Input file must be either .ipynb or .md") 283 284 self.push_to_medium( 285 html_output_file, medium_id, token, title, tag_list, publish_status 286 )
14class NotebookToMedium: 15 """ 16 Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API. 17 """ 18 19 def __init__(self): 20 self.md = markdown.Markdown(extensions=[fenced_code.FencedCodeExtension()]) 21 22 def convert_notebook_to_markdown(self, input_notebook, output_markdown): 23 """ 24 Convert a Jupyter notebook to Markdown and save it to a file. 25 26 Args: 27 input_notebook (str): Path to the input Jupyter notebook. 28 output_markdown (str): Path to the output Markdown file. 29 """ 30 with open(input_notebook, "r", encoding="utf-8") as notebook_file: 31 notebook = read(notebook_file, as_version=4) 32 33 markdown_text = jupytext.writes(notebook, fmt="markdown") 34 35 # Remove the Jupytext metadata from the Markdown 36 markdown_text = re.sub( 37 r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL 38 ) 39 40 with open(output_markdown, "w", encoding="utf-8") as output_file: 41 output_file.write(markdown_text) 42 43 def convert_markdown_to_html( 44 self, 45 input_markdown, 46 output_html, 47 nest_as_medium=True, 48 transform_pre_code=True, 49 add_title_to_pictures=True, 50 ): 51 """ 52 Convert a Markdown file to HTML and save it to a file. 53 54 Args: 55 input_markdown (str): Path to the input Markdown file. 56 output_html (str): Path to the output HTML file. 57 """ 58 59 with open(input_markdown, "r", encoding="utf-8") as markdown_file: 60 markdown_text = markdown_file.read() 61 62 markdown_text = html.escape(markdown_text) 63 64 html_text = self.md.convert(markdown_text) 65 66 if transform_pre_code: 67 html_text = self.transform_pre_code(html_text) 68 69 if nest_as_medium: 70 html_text = self.transform_nested_ul_to_medium_nested_list(html_text) 71 72 if add_title_to_pictures: 73 html_text = self.add_title_to_pictures(html_text) 74 75 html_text = html.unescape(html_text) 76 77 with open(output_html, "w", encoding="utf-8") as output_file: 78 output_file.write(html_text) 79 80 def convert_notebook_to_html(self, input_notebook, output_html): 81 """ 82 Convert a Jupyter notebook to HTML. 83 84 This method performs the conversion by first converting the notebook to Markdown, 85 and then converting the Markdown to HTML. 86 IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead 87 of converting directly to html with jupytext is purposeful 88 89 Args: 90 input_notebook (str): Path to the input Jupyter notebook. 91 output_html (str): Path to the output HTML file. 92 """ 93 temp_markdown = "temp_markdown.md" 94 95 # Convert the notebook to Markdown 96 self.convert_notebook_to_markdown(input_notebook, temp_markdown) 97 98 # Convert the Markdown to HTML 99 self.convert_markdown_to_html(temp_markdown, output_html) 100 101 # Remove the temporary Markdown file 102 os.remove(temp_markdown) 103 104 def transform_nested_ul_to_medium_nested_list(self, input_string): 105 """ 106 Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format. 107 108 Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists. 109 110 Args: 111 input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags. 112 113 Returns: 114 str: The transformed HTML string. 115 """ 116 soup = BeautifulSoup(input_string, "html.parser") 117 118 # Find and replace NESTED <ul> tags and its <li> tags for Medium 119 for ul1 in soup.find_all("ul"): 120 for ul2 in ul1.find_all("ul"): 121 replace_string = " ".join(str(item) for item in ul2.contents) 122 replace_string = replace_string.replace("<li>", "<br>\n- ").replace( 123 "</li>", "" 124 ) 125 ul2.replace_with(replace_string) 126 127 result = str(soup.prettify()) 128 return result 129 130 def transform_pre_code(self, input_string): 131 """ 132 Transform ```<pre>``` elements with ```<code>``` tags inside. 133 134 This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags, 135 extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element 136 with new attributes for Medium.com-friendly code blocks. 137 138 Args: 139 input_string (str): The input HTML string. 140 141 Returns: 142 str: The transformed HTML string with updated attributes for `<pre>` elements. 143 """ 144 soup = BeautifulSoup(input_string, "html.parser") 145 for pre in soup.find_all("pre"): 146 code = pre.find("code") 147 if code: 148 language_list = code.get("class") 149 language = "" 150 if language_list: 151 for item in language_list: 152 if "language-" in item: 153 language = item.replace("language-", "") 154 pre["data-code-block-lang"] = language 155 pre["data-code-block-mode"] = "2" 156 pre["spellcheck"] = "false" 157 pre["class"] = "graf--preV2" 158 pre["data-testid"] = "editorCodeBlockParagraph" 159 160 span_tag = soup.new_tag("span") 161 span_tag["class"] = "pre--content" 162 content = "".join(map(str, code.contents)) 163 span_tag.string = content 164 code.replace_with(span_tag) 165 166 return str(soup.prettify()) 167 168 def add_title_to_pictures(self, input_string): 169 """ 170 Add captions to images with titles in the input HTML. 171 172 This function searches for `<img>` elements with a "title" attribute or with a quoted " " 173 string in the URL (which comes from the Markdown) in the input HTML string and adds extra 174 tags to transform them into figures with captions. The title attribute is used 175 as the caption text. 176 177 Args: 178 input_string (str): The input HTML string containing `<img>` elements. 179 180 Returns: 181 str: The HTML string with captions added to images with titles. 182 """ 183 soup = BeautifulSoup(input_string, "html.parser") 184 for img in soup.find_all("img"): 185 title = img.get("title") 186 if not title: 187 src = img.get("src") 188 if '"' in src: 189 title = src.split('"')[1] 190 else: 191 title = "" 192 if title != "": 193 replace_string = ( 194 '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">' 195 + '<div class="aspectRatioPlaceholder">' 196 + str(img) 197 .replace(title, "") 198 .replace('"', "") # we don't want the title in the URL 199 + "</div>" 200 + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>' 201 + " </figure>" 202 ) 203 img.replace_with(replace_string) 204 result = str(soup.prettify()) 205 return result 206 207 def push_to_medium( 208 self, 209 file_to_upload, 210 medium_id, 211 token, 212 title, 213 tag_list, 214 publish_status="draft", 215 content_format="html", 216 ): 217 """ 218 Push an HTML file to Medium as a draft post. 219 220 Args: 221 input_file (str): Path to the .ipynb or .md file to be uploaded. 222 medium_id (str): User ID for Medium. 223 token (str): Medium API token. 224 title (str): Title for the Medium post. 225 tag_list (list): List of tags for the Medium post. 226 publish_status (str, optional): Publish status (default: 'draft'). 227 """ 228 if len(tag_list) > 5: 229 raise ValueError("Tag list should not contain more than 5 elements.") 230 231 with open(file_to_upload, "r", encoding="utf-8") as content_text: 232 content = content_text.read() 233 234 url = f"https://api.medium.com/v1/users/{medium_id}/posts" 235 236 post_data = { 237 "title": title, 238 "contentFormat": content_format, 239 "content": content, 240 "tags": tag_list, 241 "publishStatus": publish_status, 242 } 243 244 headers = { 245 "Authorization": f"Bearer {token}", 246 "Content-Type": "application/json", 247 "Accept": "application/json", 248 "Accept-Charset": "utf-8", 249 } 250 251 response = requests.post(url, headers=headers, json=post_data) 252 253 if response.status_code == 201: 254 post_details = response.json() 255 print("Draft Post Created Successfully:") 256 print("Post Details:") 257 print(post_details) 258 else: 259 print("Failed to create draft post. Status code:", response.status_code) 260 print("Response:", response.text) 261 262 def push_ipynb_or_md_to_medium( 263 self, input_file, medium_id, token, title, tag_list, publish_status="draft" 264 ): 265 """ 266 Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post. 267 268 Args: 269 input_file (str): Path to the .ipynb or .md file to be uploaded. 270 id (str): User ID for Medium. 271 token (str): Medium API token. 272 title (str): Title for the Medium post. 273 tag_list (list): List of tags for the Medium post. 274 publish_status (str, optional): Publish status (default: 'draft'). 275 """ 276 if input_file.lower().endswith(".ipynb"): 277 html_output_file = os.path.splitext(input_file)[0] + ".html" 278 self.convert_notebook_to_html(input_file, html_output_file) 279 elif input_file.lower().endswith(".md"): 280 html_output_file = os.path.splitext(input_file)[0] + ".html" 281 self.convert_markdown_to_html(input_file, html_output_file) 282 else: 283 raise ValueError("Input file must be either .ipynb or .md") 284 285 self.push_to_medium( 286 html_output_file, medium_id, token, title, tag_list, publish_status 287 )
Write your Medium articles in a Jupyter Notebook and push it directly to Medium using the Medium API.
22 def convert_notebook_to_markdown(self, input_notebook, output_markdown): 23 """ 24 Convert a Jupyter notebook to Markdown and save it to a file. 25 26 Args: 27 input_notebook (str): Path to the input Jupyter notebook. 28 output_markdown (str): Path to the output Markdown file. 29 """ 30 with open(input_notebook, "r", encoding="utf-8") as notebook_file: 31 notebook = read(notebook_file, as_version=4) 32 33 markdown_text = jupytext.writes(notebook, fmt="markdown") 34 35 # Remove the Jupytext metadata from the Markdown 36 markdown_text = re.sub( 37 r"---\s+jupyter:\s+\S+.*?---", "", markdown_text, flags=re.DOTALL 38 ) 39 40 with open(output_markdown, "w", encoding="utf-8") as output_file: 41 output_file.write(markdown_text)
Convert a Jupyter notebook to Markdown and save it to a file.
Args: input_notebook (str): Path to the input Jupyter notebook. output_markdown (str): Path to the output Markdown file.
43 def convert_markdown_to_html( 44 self, 45 input_markdown, 46 output_html, 47 nest_as_medium=True, 48 transform_pre_code=True, 49 add_title_to_pictures=True, 50 ): 51 """ 52 Convert a Markdown file to HTML and save it to a file. 53 54 Args: 55 input_markdown (str): Path to the input Markdown file. 56 output_html (str): Path to the output HTML file. 57 """ 58 59 with open(input_markdown, "r", encoding="utf-8") as markdown_file: 60 markdown_text = markdown_file.read() 61 62 markdown_text = html.escape(markdown_text) 63 64 html_text = self.md.convert(markdown_text) 65 66 if transform_pre_code: 67 html_text = self.transform_pre_code(html_text) 68 69 if nest_as_medium: 70 html_text = self.transform_nested_ul_to_medium_nested_list(html_text) 71 72 if add_title_to_pictures: 73 html_text = self.add_title_to_pictures(html_text) 74 75 html_text = html.unescape(html_text) 76 77 with open(output_html, "w", encoding="utf-8") as output_file: 78 output_file.write(html_text)
Convert a Markdown file to HTML and save it to a file.
Args: input_markdown (str): Path to the input Markdown file. output_html (str): Path to the output HTML file.
80 def convert_notebook_to_html(self, input_notebook, output_html): 81 """ 82 Convert a Jupyter notebook to HTML. 83 84 This method performs the conversion by first converting the notebook to Markdown, 85 and then converting the Markdown to HTML. 86 IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead 87 of converting directly to html with jupytext is purposeful 88 89 Args: 90 input_notebook (str): Path to the input Jupyter notebook. 91 output_html (str): Path to the output HTML file. 92 """ 93 temp_markdown = "temp_markdown.md" 94 95 # Convert the notebook to Markdown 96 self.convert_notebook_to_markdown(input_notebook, temp_markdown) 97 98 # Convert the Markdown to HTML 99 self.convert_markdown_to_html(temp_markdown, output_html) 100 101 # Remove the temporary Markdown file 102 os.remove(temp_markdown)
Convert a Jupyter notebook to HTML.
This method performs the conversion by first converting the notebook to Markdown, and then converting the Markdown to HTML. IMPORTANT: This is to avoid generating binaries, using intermediate Markdown instead of converting directly to html with jupytext is purposeful
Args: input_notebook (str): Path to the input Jupyter notebook. output_html (str): Path to the output HTML file.
104 def transform_nested_ul_to_medium_nested_list(self, input_string): 105 """ 106 Transform nested ```<ul>``` and ```<li>``` tags to a medium.com-friendly format. 107 108 Replaces nested ```<ul>``` tags with ```<br>``` and ```<li>``` tags with '-' to format them as medium.com lists. 109 110 Args: 111 input_string (str): The input HTML string containing nested ```<ul>``` and ```<li>``` tags. 112 113 Returns: 114 str: The transformed HTML string. 115 """ 116 soup = BeautifulSoup(input_string, "html.parser") 117 118 # Find and replace NESTED <ul> tags and its <li> tags for Medium 119 for ul1 in soup.find_all("ul"): 120 for ul2 in ul1.find_all("ul"): 121 replace_string = " ".join(str(item) for item in ul2.contents) 122 replace_string = replace_string.replace("<li>", "<br>\n- ").replace( 123 "</li>", "" 124 ) 125 ul2.replace_with(replace_string) 126 127 result = str(soup.prettify()) 128 return result
Transform nested <ul> and <li> tags to a medium.com-friendly format.
Replaces nested <ul> tags with <br> and <li> tags with '-' to format them as medium.com lists.
Args:
input_string (str): The input HTML string containing nested <ul> and <li> tags.
Returns: str: The transformed HTML string.
130 def transform_pre_code(self, input_string): 131 """ 132 Transform ```<pre>``` elements with ```<code>``` tags inside. 133 134 This function takes an HTML string as input, searches for `<pre>` elements that contain `<code>` tags, 135 extracts the programming language from the `<code>` tag's class attribute, and transforms the `<pre>` element 136 with new attributes for Medium.com-friendly code blocks. 137 138 Args: 139 input_string (str): The input HTML string. 140 141 Returns: 142 str: The transformed HTML string with updated attributes for `<pre>` elements. 143 """ 144 soup = BeautifulSoup(input_string, "html.parser") 145 for pre in soup.find_all("pre"): 146 code = pre.find("code") 147 if code: 148 language_list = code.get("class") 149 language = "" 150 if language_list: 151 for item in language_list: 152 if "language-" in item: 153 language = item.replace("language-", "") 154 pre["data-code-block-lang"] = language 155 pre["data-code-block-mode"] = "2" 156 pre["spellcheck"] = "false" 157 pre["class"] = "graf--preV2" 158 pre["data-testid"] = "editorCodeBlockParagraph" 159 160 span_tag = soup.new_tag("span") 161 span_tag["class"] = "pre--content" 162 content = "".join(map(str, code.contents)) 163 span_tag.string = content 164 code.replace_with(span_tag) 165 166 return str(soup.prettify())
Transform <pre> elements with <code> tags inside.
This function takes an HTML string as input, searches for <pre> elements that contain <code> tags,
extracts the programming language from the <code> tag's class attribute, and transforms the <pre> element
with new attributes for Medium.com-friendly code blocks.
Args: input_string (str): The input HTML string.
Returns:
str: The transformed HTML string with updated attributes for <pre> elements.
168 def add_title_to_pictures(self, input_string): 169 """ 170 Add captions to images with titles in the input HTML. 171 172 This function searches for `<img>` elements with a "title" attribute or with a quoted " " 173 string in the URL (which comes from the Markdown) in the input HTML string and adds extra 174 tags to transform them into figures with captions. The title attribute is used 175 as the caption text. 176 177 Args: 178 input_string (str): The input HTML string containing `<img>` elements. 179 180 Returns: 181 str: The HTML string with captions added to images with titles. 182 """ 183 soup = BeautifulSoup(input_string, "html.parser") 184 for img in soup.find_all("img"): 185 title = img.get("title") 186 if not title: 187 src = img.get("src") 188 if '"' in src: 189 title = src.split('"')[1] 190 else: 191 title = "" 192 if title != "": 193 replace_string = ( 194 '<figure tabindex="0" contenteditable="false" data-testid="editorImageParagraph" class="graf graf--figure graf-after--h4">' 195 + '<div class="aspectRatioPlaceholder">' 196 + str(img) 197 .replace(title, "") 198 .replace('"', "") # we don't want the title in the URL 199 + "</div>" 200 + f'<figcaption class="imageCaption" contenteditable="true" data-default-value="Type caption for image (optional)">{title}<br></figcaption>' 201 + " </figure>" 202 ) 203 img.replace_with(replace_string) 204 result = str(soup.prettify()) 205 return result
Add captions to images with titles in the input HTML.
This function searches for <img> elements with a "title" attribute or with a quoted " "
string in the URL (which comes from the Markdown) in the input HTML string and adds extra
tags to transform them into figures with captions. The title attribute is used
as the caption text.
Args:
input_string (str): The input HTML string containing <img> elements.
Returns: str: The HTML string with captions added to images with titles.
207 def push_to_medium( 208 self, 209 file_to_upload, 210 medium_id, 211 token, 212 title, 213 tag_list, 214 publish_status="draft", 215 content_format="html", 216 ): 217 """ 218 Push an HTML file to Medium as a draft post. 219 220 Args: 221 input_file (str): Path to the .ipynb or .md file to be uploaded. 222 medium_id (str): User ID for Medium. 223 token (str): Medium API token. 224 title (str): Title for the Medium post. 225 tag_list (list): List of tags for the Medium post. 226 publish_status (str, optional): Publish status (default: 'draft'). 227 """ 228 if len(tag_list) > 5: 229 raise ValueError("Tag list should not contain more than 5 elements.") 230 231 with open(file_to_upload, "r", encoding="utf-8") as content_text: 232 content = content_text.read() 233 234 url = f"https://api.medium.com/v1/users/{medium_id}/posts" 235 236 post_data = { 237 "title": title, 238 "contentFormat": content_format, 239 "content": content, 240 "tags": tag_list, 241 "publishStatus": publish_status, 242 } 243 244 headers = { 245 "Authorization": f"Bearer {token}", 246 "Content-Type": "application/json", 247 "Accept": "application/json", 248 "Accept-Charset": "utf-8", 249 } 250 251 response = requests.post(url, headers=headers, json=post_data) 252 253 if response.status_code == 201: 254 post_details = response.json() 255 print("Draft Post Created Successfully:") 256 print("Post Details:") 257 print(post_details) 258 else: 259 print("Failed to create draft post. Status code:", response.status_code) 260 print("Response:", response.text)
Push an HTML file to Medium as a draft post.
Args: input_file (str): Path to the .ipynb or .md file to be uploaded. medium_id (str): User ID for Medium. token (str): Medium API token. title (str): Title for the Medium post. tag_list (list): List of tags for the Medium post. publish_status (str, optional): Publish status (default: 'draft').
262 def push_ipynb_or_md_to_medium( 263 self, input_file, medium_id, token, title, tag_list, publish_status="draft" 264 ): 265 """ 266 Convert an `.ipynb` or `.md` file to HTML and push to Medium as a draft post. 267 268 Args: 269 input_file (str): Path to the .ipynb or .md file to be uploaded. 270 id (str): User ID for Medium. 271 token (str): Medium API token. 272 title (str): Title for the Medium post. 273 tag_list (list): List of tags for the Medium post. 274 publish_status (str, optional): Publish status (default: 'draft'). 275 """ 276 if input_file.lower().endswith(".ipynb"): 277 html_output_file = os.path.splitext(input_file)[0] + ".html" 278 self.convert_notebook_to_html(input_file, html_output_file) 279 elif input_file.lower().endswith(".md"): 280 html_output_file = os.path.splitext(input_file)[0] + ".html" 281 self.convert_markdown_to_html(input_file, html_output_file) 282 else: 283 raise ValueError("Input file must be either .ipynb or .md") 284 285 self.push_to_medium( 286 html_output_file, medium_id, token, title, tag_list, publish_status 287 )
Convert an .ipynb or .md file to HTML and push to Medium as a draft post.
Args: input_file (str): Path to the .ipynb or .md file to be uploaded. id (str): User ID for Medium. token (str): Medium API token. title (str): Title for the Medium post. tag_list (list): List of tags for the Medium post. publish_status (str, optional): Publish status (default: 'draft').