def to_markdown(self): cb_name = self.nb_buffer.name.split('/')[-1] notebook_name = cb_name.split('.')[0] self.write_buffer() markdown_exporter = MarkdownExporter() body, resources = markdown_exporter.from_filename(cb_name) self.writer.write(body, resources, notebook_name = notebook_name)
def nb2md(nb_path, start=0, end=None, execute=False, kernel_name=""): """Convert a notebook to markdown We use a template that removed all code cells because if the body is to big ( with javascript and CSS) it takes to long to read and parse """ app = get_nbconvert_app(start=start, end=end, execute=execute, kernel_name=kernel_name) # Use the templates included in this package template_file = "mkdocs_md/md-no-codecell.md.j2" extra_template_paths = [settings.templates_dir] exporter = MarkdownExporter( config=app.config, template_file=template_file, extra_template_paths=extra_template_paths, ) _, extension = os.path.splitext(nb_path) if extension == ".py": nb = jupytext.read(nb_path) nb_file = io.StringIO(jupytext.writes(nb, fmt="ipynb")) body, resources = exporter.from_file(nb_file) else: body, resources = exporter.from_filename(nb_path) return body
def create_markdown(self): if self.table_conversion == 'chrome': converter = Screenshot(max_rows=30, max_cols=10, ss_width=1400, ss_height=900, resize=1, chrome_path=None).run else: from ._matplotlib_table import converter self.resources['converter'] = converter self.resources['image_data_dict'] = {} mp = MarkdownPreprocessor() self.nb, self.resources = mp.preprocess(self.nb, self.resources) no_ex_pp = NoExecuteDataFramePreprocessor() self.nb, self.resources = no_ex_pp.preprocess(self.nb, self.resources) # MarkdownExporter converts images to base64 bytes automatically me = MarkdownExporter() md, self.resources = me.from_notebook_node(self.nb, self.resources) image_data_dict = { **self.resources['image_data_dict'], **self.resources['outputs'] } return md, image_data_dict
def main(input, output, name): exporter = MarkdownExporter(template_file=HIDE_TEMPLATE) writer = FilesWriter() with open(input, 'rb') as f: nb = nbformat.read(f, as_version=4) (body, res) = exporter.from_notebook_node(nb) writer.write(MdFormatter().clear(body), PostProcesser().res_path(res, output), notebook_name=output + name)
def create_markdown(self): output_dir = self.nb_home / self.image_dir_name if not output_dir.exists(): output_dir.mkdir() mp = MarkdownPreprocessor(output_dir=output_dir, image_dir_name=Path(self.image_dir_name)) self.nb, self.resources = mp.preprocess(self.nb, self.resources) no_ex_pp = NoExecuteDataFramePreprocessor() self.nb, self.resources = no_ex_pp.preprocess(self.nb, self.resources) me = MarkdownExporter() md, self.resources = me.from_notebook_node(self.nb, self.resources) image_data_dict = self.resources['outputs'] self.write_output_image_data(image_data_dict) image_data_dict = self.get_image_data(image_data_dict) return md, image_data_dict
def ipynb(nb): # Remove parts of cell: c = Config() # Configure our tag removal c.TagRemovePreprocessor.remove_cell_tags = ('hide-cell', ) c.TagRemovePreprocessor.remove_all_outputs_tags = ('hide-output', ) c.TagRemovePreprocessor.remove_input_tags = ('hide-input', ) c.TagRemovePreprocessor.enabled = True # Configure and run our exporter c.MarkdownExporter.preprocessors = [ 'nbconvert.preprocessors.TagRemovePreprocessor' ] exporter = MarkdownExporter(config=c) exporter.register_preprocessor(TagRemovePreprocessor(config=c), True) body, resources = exporter.from_filename(nb) # Add tags to markdown # title = nb.split(".")[0] # with open(f"{title}.ipynb") as f: # notebook = read(f, NO_CONVERT) # text = get_text(notebook) # tags = predict_tags(text) yaml = build_yaml() # Get filenmae filename = os.path.splitext(nb)[0] # Clean up the Markdown body = re.sub(r"!\[svg\]\(", '<img src="/assets/img/' + filename + '_files/', body) body = re.sub(".svg\)", '.svg">', body) body = re.sub(r"!\[png\]\(", '<img src="/assets/img/' + filename + '_files/', body) body = re.sub(".png\)", '.png">', body) body = yaml + body # Save files writer = FilesWriter() writer.write(body, resources, filename)
def create_markdown(self): mp = MarkdownPreprocessor() mp.preprocess(self.nb, self.resources) no_ex_pp = NoExecuteDataFramePreprocessor() no_ex_pp.preprocess(self.nb, self.resources) # MarkdownExporter converts images to base64 bytes automatically # MarkdownExporter deep copies resources and fails when matplotlib # must remove converter key to not error self.resources.pop('converter') me = MarkdownExporter() md, self.resources = me.from_notebook_node(self.nb, self.resources) image_data_dict = { **self.resources['image_data_dict'], **self.resources['outputs'] } return md, image_data_dict
def nb2md(nb_path): """Convert a notebook to markdown We use a template that removed all code cells because if the body is to big (javascript and stuff) it takes to long to read and parse """ template_file = "mkdocs_md/md-no-codecell.md.j2" exporter = MarkdownExporter( template_file=template_file, # uncomment this line when new nbconvert is released # https://github.com/jupyter/nbconvert/pull/1429 extra_template_paths=[os.path.join(THIS_DIR, "templates")], ) # Delete this block when nbconvert is released # exporter.template_paths.append(os.path.join(THIS_DIR, "templates")) # print(exporter.template_paths) # End block body, resources = exporter.from_filename(nb_path) return body
def nb2md(nb_path): """Convert a notebook and return markdown We use a template that removed all code cells because if the body is to big (javascript and stuff) it takes to long to read and parse """ config = Config() template = os.path.join(THIS_DIR, "assets", "md-no-codecell.tpl") template_file = os.path.basename(template) extra_loaders = [] extra_loaders.append(jinja2.FileSystemLoader([os.path.dirname(template)])) exporter = MarkdownExporter( config=config, template_file=template_file, extra_loaders=extra_loaders, # filters=filters, # preprocessors=preprocessors_, ) body, resources = exporter.from_filename(nb_path) return body
def _build_exporter() -> MarkdownExporter: """ Build a MarkdownExporter with a custom template and return it. """ exporter = MarkdownExporter() exporter.template_file = "docs-jekyll.md.j2" exporter.template_path.append(str(Path(__file__).parent / "templates")) exporter.exclude_input_prompt = False exporter.exclude_output_prompt = False exporter.exclude_unknown = False exporter.exclude_raw = False return exporter
def build_book(path_book, path_toc_yaml=None, config_file=None, path_template=None, local_build=False, execute=False, overwrite=False): """Build the markdown for a book using its TOC and a content folder. Parameters ---------- path_book : str Path to the root of the book repository path_toc_yaml : str | None Path to the Table of Contents YAML file config_file : str | None Path to the Jekyll configuration file path_template : str | None Path to the template nbconvert uses to build markdown files local_build : bool Specify you are building site locally for later upload execute : bool Whether to execute notebooks before converting to markdown overwrite : bool Whether to overwrite existing markdown files """ PATH_IMAGES_FOLDER = op.join(path_book, '_build', 'images') BUILD_FOLDER = op.join(path_book, BUILD_FOLDER_NAME) ############################################### # Read in textbook configuration # Load the yaml for this site with open(config_file, 'r') as ff: site_yaml = yaml.safe_load(ff.read()) CONTENT_FOLDER_NAME = site_yaml.get('content_folder_name').strip('/') PATH_CONTENT_FOLDER = op.join(path_book, CONTENT_FOLDER_NAME) # Load the textbook yaml for this site if not op.exists(path_toc_yaml): raise _error("No toc.yml file found, please create one at `{}`".format( path_toc_yaml)) with open(path_toc_yaml, 'r') as ff: toc = yaml.safe_load(ff.read()) # Drop divider items and non-linked pages in the sidebar, un-nest sections toc = _prepare_toc(toc) ################################################ # Generating the Jekyll files for all content n_skipped_files = 0 n_built_files = 0 case_check = _case_sensitive_fs(BUILD_FOLDER) and local_build print("Convert and copy notebook/md files...") for ix_file, page in enumerate(tqdm(list(toc))): url_page = page.get('url', None) title = page.get('title', None) if page.get('external', None): # If its an external link, just pass continue # Make sure URLs (file paths) have correct structure _check_url_page(url_page, CONTENT_FOLDER_NAME) ############################################## # Create path to old/new file and create directory # URL will be relative to the CONTENT_FOLDER path_url_page = os.path.join(PATH_CONTENT_FOLDER, url_page.lstrip('/')) path_url_folder = os.path.dirname(path_url_page) # URLs shouldn't have the suffix in there already so # now we find which one to add for suf in SUPPORTED_FILE_SUFFIXES: if op.exists(path_url_page + suf): path_url_page = path_url_page + suf break if not op.exists(path_url_page): raise _error( "Could not find file called {} with any of these extensions: {}" .format(path_url_page, SUPPORTED_FILE_SUFFIXES)) # Create and check new folder / file paths path_build_new_folder = path_url_folder.replace( os.sep + CONTENT_FOLDER_NAME, os.sep + BUILD_FOLDER_NAME) + os.sep path_build_new_file = op.join( path_build_new_folder, op.basename(path_url_page).replace('.ipynb', '.md')) if overwrite is False and op.exists(path_build_new_file) \ and os.stat(path_build_new_file).st_mtime > os.stat(path_url_page).st_mtime: n_skipped_files += 1 continue if not op.isdir(path_build_new_folder): os.makedirs(path_build_new_folder) ################################################ # Generate previous/next page URLs if ix_file == 0: url_prev_page = '' prev_file_title = '' else: prev_file_title = toc[ix_file - 1].get('title') url_prev_page = toc[ix_file - 1].get('url') pre_external = toc[ix_file - 1].get('external', False) if pre_external is False: url_prev_page = _prepare_url(url_prev_page) if ix_file == len(toc) - 1: url_next_page = '' next_file_title = '' else: next_file_title = toc[ix_file + 1].get('title') url_next_page = toc[ix_file + 1].get('url') next_external = toc[ix_file + 1].get('external', False) if next_external is False: url_next_page = _prepare_url(url_next_page) ############################################################################### # Get kernel name and presence of widgets from notebooks metadata kernel_name = '' if path_url_page.endswith('.ipynb'): data = nbf.read(path_url_page, nbf.NO_CONVERT) if 'metadata' in data and 'kernelspec' in data['metadata']: kernel_name = data['metadata']['kernelspec']['name'] has_widgets = "true" if any( "interactive" in cell['metadata'].get('tags', []) for cell in data['cells']) else "false" ############################################ # Content conversion # Convert notebooks or just copy md if no notebook. if path_url_page.endswith('.ipynb'): notebook_name = op.splitext(op.basename(path_url_page))[0] ntbk = nbf.read(path_url_page, nbf.NO_CONVERT) ######################################## # Notebook cleaning # Clean up the file before converting cleaner = NotebookCleaner(ntbk) cleaner.remove_cells(empty=True) cleaner.clear('stderr') ntbk = cleaner.ntbk _clean_notebook_cells(ntbk) ############################################# # Conversion to Jekyll Markdown # create a configuration object that changes the preprocessors c = Config() c.FilesWriter.build_directory = path_build_new_folder if execute is True: # Excution of the notebook if we wish ep = ExecutePreprocessor(timeout=600, kernel_name=kernel_name) ep.preprocess( ntbk, {'metadata': { 'path': op.dirname(path_url_folder) }}) # Define the path to images and then the relative path to where they'll originally be placed path_after_build_folder = path_build_new_folder.split( os.sep + BUILD_FOLDER_NAME + os.sep)[-1] path_images_new_folder = op.join(PATH_IMAGES_FOLDER, path_after_build_folder) path_images_rel = op.relpath(path_images_new_folder, path_build_new_folder) # Generate Markdown from our notebook using the template output_resources = { 'output_files_dir': path_images_rel, 'unique_key': notebook_name } exp = MarkdownExporter(template_file=path_template, config=c) markdown, resources = exp.from_notebook_node( ntbk, resources=output_resources) # Now write the markdown and resources writer = FilesWriter(config=c) writer.write(markdown, resources, notebook_name=notebook_name) elif path_url_page.endswith('.md'): # If a non-notebook file, just copy it over. # If markdown we'll add frontmatter later sh.copy2(path_url_page, path_build_new_file) else: raise _error("Files must end in ipynb or md. Found file {}".format( path_url_page)) ############################################################################### # Modify the generated Markdown to work with Jekyll # Clean markdown for Jekyll quirks (e.g. extra escape characters) with open(path_build_new_file, 'r', encoding='utf8') as ff: lines = ff.readlines() lines = _clean_lines(lines, path_build_new_file, path_book, PATH_IMAGES_FOLDER) # Split off original yaml yaml_orig, lines = _split_yaml(lines) # Front-matter YAML yaml_fm = [] yaml_fm += ['---'] # In case pre-existing links are sanitized sanitized = url_page.lower().replace('_', '-') if sanitized != url_page: if case_check and url_page.lower() == sanitized: raise RuntimeError( 'Redirect {} clashes with page {} for local build on ' 'case-insensitive FS\n'.format(sanitized, url_page) + 'Rename source page to lower case or build on a case ' 'sensitive FS, e.g. case-sensitive disk image on Mac') yaml_fm += ['redirect_from:'] yaml_fm += [' - "{}"'.format(sanitized)] if path_url_page.endswith('.ipynb'): interact_path = CONTENT_FOLDER_NAME + '/' + \ path_url_page.split(CONTENT_FOLDER_NAME + '/')[-1] yaml_fm += ['interact_link: {}'.format(interact_path)] yaml_fm += ["kernel_name: {}".format(kernel_name)] yaml_fm += ["has_widgets: {}".format(has_widgets)] # Page metadata yaml_fm += ["title: '{}'".format(title)] yaml_fm += ['prev_page:'] yaml_fm += [' url: {}'.format(url_prev_page)] yaml_fm += [" title: '{}'".format(prev_file_title)] yaml_fm += ['next_page:'] yaml_fm += [' url: {}'.format(url_next_page)] yaml_fm += [" title: '{}'".format(next_file_title)] # Add back any original YaML, and end markers yaml_fm += yaml_orig yaml_fm += [ 'comment: "***PROGRAMMATICALLY GENERATED, DO NOT EDIT. SEE ORIGINAL FILES IN /{}***"' .format(CONTENT_FOLDER_NAME) ] yaml_fm += ['---'] yaml_fm = [ii + '\n' for ii in yaml_fm] lines = yaml_fm + lines # Write the result as UTF-8. with open(path_build_new_file, 'w', encoding='utf8') as ff: ff.writelines(lines) n_built_files += 1 ####################################################### # Finishing up... # Copy non-markdown files in notebooks/ in case they're referenced in the notebooks print('Copying non-content files inside `{}/`...'.format( CONTENT_FOLDER_NAME)) _copy_non_content_files(PATH_CONTENT_FOLDER, CONTENT_FOLDER_NAME, BUILD_FOLDER_NAME) # Message at the end msg = [ "Generated {} new files\nSkipped {} already-built files".format( n_built_files, n_skipped_files) ] if n_built_files == 0: msg += [ "Delete the markdown files in '{}' for any pages that you wish to re-build, or use --overwrite option to re-build all." .format(BUILD_FOLDER_NAME) ] msg += ["Your Jupyter Book is now in `{}/`.".format(BUILD_FOLDER_NAME)] msg += ["Demo your Jupyter book with `make serve` or push to GitHub!"] print_message_box('\n'.join(msg))
def nb2medium( title: Param("The title of your article"), notebook: Param("Path to your notebook"), log_level: Param( "The minimum reported logging level (debug, info, warning, error or critical)" ) = 'info', log_to_stdout: Param( "Whether logging should be redirected to stdout (internal use)", store_true) = False): if isinstance(log_level, str): log_level = log_level.upper() # convert logger init_logger('converter', log_level, log_to_stdout=log_to_stdout) init_logger('uploader', log_level, log_to_stdout=log_to_stdout) upload_logger = logging.getLogger('uploader') if not os.getenv('MEDIUM_TOKEN'): upload_logger.critical( 'MEDIUM_TOKEN not found, please make sure MEDIUM_TOKEN is defined') else: upload_logger.debug('Detected MEDIUM_TOKEN') # declare exporter m = MarkdownExporter() # Hide Preprocessors m.register_preprocessor(HidePreprocessor(mode='source'), enabled=True) m.register_preprocessor(HidePreprocessor(mode='output'), enabled=True) m.register_preprocessor(HidePreprocessor(mode='cell'), enabled=True) m.register_preprocessor( TagRemovePreprocessor(remove_input_tags=('hide-source', ), remove_all_outputs_tags=('hide-output', ), remove_cell_tags=('hide-cell', ), enabled=True)) # Gister Preprocessors m.register_preprocessor(GisterPreprocessor(), enabled=True) #Image Preprocessors m.register_preprocessor(ImagePreprocessor(), enabled=True) # process notebook b, r = m.from_filename(notebook) # write notebook to Markdown path, name = os.path.split(notebook) basename, ext = os.path.splitext(name) WriteMarkdown(b, r, filename=basename) md_home = os.path.join(path, basename, f"{basename}.md") # upload markdown document as medium post post_request = post_article(title=title, content=open(md_home).read()) if post_request.ok: upload_logger.info( f"Draft of '{title}' from {os.path.basename(notebook)} notebook uploaded to Medium: {post_request.json()['data']['url']}" ) else: upload_logger.error( f"Could not upload draft of '{title}' from {os.path.basename(notebook)} notebook!!" ) raise Exception(f"Could not upload the draft because of an error \ submitting to medium:\n \ post request error reason -> {post_request.reason},\n \ post request response code -> {post_request.status_code}")
import nbformat as nbf from nbconvert.exporters import MarkdownExporter from IPython.display import Image import os import sys md_exporter = MarkdownExporter() if (len(sys.argv) != 2): print("Insert filename.ipynb to be converted") exit() filename = str(sys.argv[1]) base_name = os.path.splitext(filename)[0].lower() nb = nbf.read(filename, as_version=4) (body, resources) = md_exporter.from_notebook_node(nb) # save markdown with open(base_name + '.md', 'w') as file: file.write(body) # save images for resource in resources['outputs'].keys(): file_res = base_name + '_' + resource body.replace(resource, '/tutorials/img/' + file_res) with open(file_res, 'wb') as file: file.write(resources['outputs'][resource])
def convert_ipynb_to_post(path_ipynb, _folder_github_page, out_subfolder="_posts", b_exclud_code=True, b_numbering=False) -> HeaderIpynb: """ipynb를 mardkdown file로 변환. [description] Parameters ---------- path_ipynb : str 파일 경로 _folder_github_page : str 변환 파일을 저장할 폴더 out_subfolder : str MD파일을 저장할 _folder_github_page의 하위 폴더 b_exclud_code : bool, [description] (the default is True) b_numbering : bool [description] (the default is False) Returns ------- HeaderIpynb [description] """ _folder_github_page = os.path.abspath(_folder_github_page) filename_ext_ipynb = os.path.split(path_ipynb)[1] filename_ipynb = os.path.splitext(filename_ext_ipynb)[0] exporter = MarkdownExporter() md, resource = exporter.from_filename(path_ipynb) md = md.strip() md = re.sub('///---', '---', md) if b_exclud_code: md = re.sub( '( ```python\n.*? ```)', lambda m: re.sub("^" + " ", "", m.group(1), flags=re.MULTILINE), md, flags=re.DOTALL) md = re.sub('```python\n# Show in Markdown\n', '---python\n# Show in Markdown', md) md = re.sub('```python\n.*?```\n', '', md, flags=re.DOTALL) md = re.sub('---python\n# Show in Markdown', '```python\n', md) md = re.sub('# Show in Markdown\n', '', md) if b_numbering: md = numbering_of_header_of_md(md) md = re.sub('../assets/images/', '/assets/images/', md) _header = _parse_ipynb_header(md) filename_post = f'{_header.date.strftime("%Y-%m-%d")}-{filename_ipynb}' folder_post = f'{_folder_github_page}{os.sep}{out_subfolder}{os.sep}' folder_img = f'{_folder_github_page}{os.sep}assets{os.sep}images{os.sep}{filename_post}{os.sep}' shutil.rmtree(folder_img, ignore_errors=True) os.mkdir(folder_img) outputs = resource['outputs'] for filename_img, data in outputs.items(): pattern = r'!\[png\]\({}\)'.format(filename_img) result_re = re.findall(pattern, md) if len(result_re) > 0: filename_img_in_web = f"![img]({{{{ '/assets/images/{filename_post}/{filename_img}' | relative_url }}}})" \ f"{{: .center-image }}" md = re.sub(pattern, filename_img_in_web, md) with open(f'{folder_img}{filename_img}', 'wb') as f: f.write(data) with open(f'{folder_post}{filename_post}.md', 'w', encoding='utf-8') as file_md: file_md.write(md) return _header
def main(argv=None): argv = sys.argv[1:] import nbformat from nbconvert.preprocessors import ExecutePreprocessor from nbconvert.preprocessors.execute import CellExecutionError notebook = argv[0] outfile = argv[1] note_folder = os.path.dirname(notebook) if not os.path.exists(outfile): with open(notebook) as f: nb = nbformat.read(f, as_version=4) #print('Running notebook {} ...'.format(argv[0])) ep = ExecutePreprocessor(timeout=600, kernel_name='python3') try: nb, resources = ep.preprocess(nb, {'metadata': {'path': note_folder}}) except CellExecutionError as c: print(c) return 2 with open(outfile, 'wt') as f: nbformat.write(nb, f) #else: # with open(outfile, 'r') as f: # nb = nbformat.read(f, as_version=4) #header, filename = create_meta_header(note_folder) # Generate metadata header #import codecs #with codecs.open('docs/_posts/{}.md'.format(filename), 'w', 'utf-8') as f: # # Write a header for the gh-pages website and safe it for later usage # f.seek(0) # f.write(header) # f.write('\n') # from nbconvert.exporters import HTMLExporter # htmlexport = HTMLExporter() # htmlnb, htmlresources = htmlexport.from_notebook_node(nb, resources=dict( # #output_files_dir='images/'.format(filename), # encoding='utf-8') # ) # import codecs # with codecs.open(os.path.join(note_folder,'executed_notebook.html'), 'w', 'utf-8') as f: # # Write a header for the gh-pages website and safe it for later usage # f.seek(0) # f.write(htmlnb) # #f.write('\n') if 0: # following some old code for writing markdown exports: from nbconvert.exporters import MarkdownExporter mdexport = MarkdownExporter() from nbconvert.writers import FilesWriter mdnb, mdresources = mdexport.from_notebook_node(nb, resources=dict(output_files_dir='images/'.format(filename), encoding='utf-8')) fw = FilesWriter(build_directory=note_folder) fw.write(mdnb, mdresources, 'executed_notebook') # Make blog post mdnb, mdresources = mdexport.from_notebook_node(nb, resources=dict(output_files_dir='../assets/posts/images/{}/'.format(filename), encoding='utf-8')) fw = FilesWriter(build_directory='docs/_posts/', relpath='../assets/posts/images/{}/'.format(filename)) fw.write(mdnb, mdresources, filename) import codecs with codecs.open('docs/_posts/{}.md'.format(filename), 'w', 'utf-8') as f: # Write a header for the gh-pages website and safe it for later usage f.seek(0) f.write(header) f.write('\n') if 0: # Markdown export not supported yet: adjusted_nb = mdnb.replace('{{', '{ {').replace('<!--', '```').replace('-->', '```') adjusted_nb = adjusted_nb.replace('../assets', '{{ site.base_url }}{{ site.url }}/assets') adjusted_nb = adjusted_nb.replace('$', '$$') adjusted_nb = adjusted_nb.replace('$$$', '$$') f.write(adjusted_nb) return 0
def publish_markdown(self): """Updates the Markdown version of a Jupyter notebook file.""" nb = self.get_clean_notebook() exporter = MarkdownExporter() (content, resources) = exporter.from_notebook_node(nb) markdown = self.get_markdown_file() # separate front matter from content title, description, weight, content = self.parse_front_matter( content, markdown) template = ( '+++\n' 'title = "{0}"\n' 'description = "{1}"\n' 'weight = {2}\n' '+++\n\n' '<!--\n' 'AUTOGENERATED FROM {4}\n' 'PLEASE UPDATE THE JUPYTER NOTEBOOK AND REGENERATE THIS FILE' ' USING scripts/nb_to_md.py.' '-->\n\n' '<style>\n' '.notebook-links {{display: flex; margin: 1em 0;}}\n' '.notebook-links a {{padding: .75em; margin-right: .75em;' ' font-weight: bold;}}\n' 'a.colab-link {{\n' 'padding-left: 3.25em;\n' 'background-image: url(/docs/images/logos/colab.ico);\n' 'background-repeat: no-repeat;\n' 'background-size: contain;\n' '}}\n' 'a.github-link {{\n' 'padding-left: 2.75em;\n' 'background-image: url(/docs/images/logos/github.png);\n' 'background-repeat: no-repeat;\n' 'background-size: auto 75%;\n' 'background-position: left center;\n' '}}\n' '</style>\n' '<div class="notebook-links">\n' '<a class="colab-link" href="https://colab.research.google.com/' 'github/kubeflow/website/blob/master/{4}">Run in Google Colab' '</a>\n' '<a class="github-link" href="https://github.com/kubeflow/websi' 'te/blob/master/{4}">View source on GitHub</a>\n' '</div>\n\n' '{3}' '\n\n' '<div class="notebook-links">\n' '<a class="colab-link" href="https://colab.research.google.com/' 'github/kubeflow/website/blob/master/{4}">Run in Google Colab' '</a>\n' '<a class="github-link" href="https://github.com/kubeflow/websi' 'te/blob/master/{4}">View source on GitHub</a>\n' '</div>') markdown.write_file( template.format(title, description, weight, content, self.file_path))