Markdown is one of my favorite file formats as someone who doesn’t know a lot of frontend. If you’re like me, then you’ll be pleased to know that a Python library exists for dynamically generating it: SnakeMD. Not only that, but today I’m going to share with you the complete guide to using it.
Table of Contents
- What Is SnakeMD?
- How Do I Install SnakeMD?
- How Do I Use SnakeMD?
- Real World Use of SnakeMD
- What Other Libraries Would You Like to See?
What Is SnakeMD?
SnakeMD is a library that I wrote to generate Markdown code. For those unfamiliar with Markdown, it’s basically shorthand HTML. Rather than writing all those messy tags, you can use a much cleaner syntax:
- Lists? Use
- word
! - Paragraphs? Just write text!
- Italics? Use
*word*
- Bold? Use
**word**
- Headings? Use
#
,##
, and###
Of course, there’s more, but this should give you a good idea of what Markdown actually is. Of course, if you use SnakeMD, you don’t need to know Markdown at all! You can make use of the various functions to build up the document programmatically.
How Do I Install SnakeMD?
To install SnakeMD, you can make use of pip
:
pip install snakemd
Currently, SnakeMD has no dependencies, so you should see something like this:
pip install --pre snakemd Collecting snakemd Using cached SnakeMD-2.0.0b1-py3-none-any.whl (17 kB) Installing collected packages: snakemd Successfully installed snakemd-2.0.0b1
As of writing, the current version of SnakeMD is 2.0.0b1. All subsequent documentation will be written with that version in mind.
How Do I Use SnakeMD?
In order to learn how to use SnakeMD, we need to take a moment to talk about how SnakeMD is designed.
SnakeMD Structure
Broadly, SnakeMD is a library that is designed with two kinds of folks in mind: those who just need a quick way to make some Markdown and those who want a little more control over how that Markdown looks.
As a result, there are actually two different ways to make use of SnakeMD. The first is to create a document and use the set of Document methods. The second is to create a Document and create each individual block-level elements as needed.
My intent with this design was to mirror a library I like to use, Plotly. When I don’t need a lot of configuration, there are a set of functions I can use. When I do want a bit more control, I can use the underlying objects.
Ultimately, each element of a Markdown document is modeled as an object. Therefore, you can use the Document object as-is, or you can make use of the various other block-level elements such as Paragraphs, MDLists, Headings, Tables, etc.
Creating a Markdown Document
Regardless of how you choose to make use of SnakeMD, you will always need to create a document. To do that, I recommend making use of the top-level function: new_doc()
.
import snakemd doc = snakemd.new_doc()
This will create a document from which we can add elements. If you run a line like this yourself, here’s what you might see:
import snakemd >>> snakemd.new_doc() <snakemd.document.Document object at 0x0000024B32ADF3A0>
Up next, we’ll take a look at how to use some of the convenience methods for updating this document.
Using the Convenience Methods
As mentioned, every document can be updated by a set of convenience methods. For example, here are a few that might be useful to you:
# Adds a H1 element to the document doc.add_heading("This is the Title of the Page") # Adds a paragraph to the document doc.add_paragraph("This is an example of a paragraph") # Adds an ordered list to the document doc.add_ordered_list(["This", "is", "an", "ordered", "list"]) # Adds a code block to the document doc.add_code("print('Hello World')", "python") # Adds a horizontal rule to the document doc.add_horizontal_rule()
And of course, this is what these code segments look like when you actually run them:
>>> doc.add_heading("This is the Title of the Page") <snakemd.elements.Heading object at 0x0000024B32707D60> >>> doc.add_paragraph("This is an example of a paragraph") <snakemd.elements.Paragraph object at 0x0000015253759F00> >>> doc.add_ordered_list(["This", "is", "an", "ordered", "list"]) <snakemd.elements.MDList object at 0x0000015253700FD0> >>> doc.add_code("print('Hello World')", "python") <snakemd.elements.Code object at 0x0000015253BFEDD0> >>> doc.add_horizontal_rule() <snakemd.elements.HorizontalRule object at 0x0000015253BFECE0>
As you can see, each function returns the actual element generated. In general though, you don’t need to use them. That’s because the document contains all these elements already. Take a look:
str(doc) "# This is the Title of the Page\n\nThis is an example of a paragraph\n\n1. This\n2. is\n3. an\n4. ordered\n5. list\n\n```python\nprint('Hello World')\n```\n\n***"
Or in an actually readable format:
print(doc) # This is the Title of the Page This is an example of a paragraph 1. This 2. is 3. an 4. ordered 5. list ```python print('Hello World') ``` ***
Now, that’s pretty cool. Up next, we’ll look at some more advanced customization options.
Using Blocks Directly
Each block-level element (a.k.a. block) can be constructed by hand. Sometimes this needs to be done to mix and match elements. For example, I often need to put links into items in a list, so I might do the following:
import snakemd items = [ snakemd.Inline("Google", link="google.com"), snakemd.Inline("Amazon", link="amazon.com"), ] doc.add_block(snakemd.MDList(items))
And so here’s the same doc but with a list of links at the bottom:
print(doc) # This is the Title of the Page This is an example of a paragraph 1. This 2. is 3. an 4. ordered 5. list ′′′python print('Hello World') ′′′ *** - [Google](google.com) - [Amazon](amazon.com)
For context, the Inline
object is used when we want to customize only a piece of a broader element such as a list or paragraph. For example, we could do a similar thing with a paragraph:
p = snakemd.Paragraph([ "The text above shows links, but ", snakemd.Inline("here", link="https://therenegadecoder.com"), " is another link." ]) doc.add_block(p)
Which, of course, updates the document as follows:
print(doc) # This is the Title of the Page This is an example of a paragraph 1. This 2. is 3. an 4. ordered 5. list ′′′python print('Hello World') ′′′ --- - [Google](google.com) - [Amazon](amazon.com) The text above shows links, but [here](https://therenegadecoder.com) is another link.
Personally, I find this a bit cumbersome, so I tend to use the insert_link()
method instead:
doc.add_paragraph("The best way to insert links is to use this function") \ .insert_link( "this", "https://www.snakemd.io/en/latest/docs/element-api/#snakemd.Paragraph.insert_link" )
Which then gives us this document:
print(doc) # This is the Title of the Page This is an example of a paragraph 1. This 2. is 3. an 4. ordered 5. list ′′′python print('Hello World') ′′′ --- - [Google](google.com) - [Amazon](amazon.com) The text above shows links, but [here](https://therenegadecoder.com) is another link. The best way to insert links is to use [this](https://www.snakemd.io/en/latest/docs/element-api/#snakemd.Paragraph.insert_link) function
Outside of that, there are lots of ways to use SnakeMD. I’m sure folks will find other ways to use it!
Real World Use of SnakeMD
If you head over to the SnakeMD repo, you’ll see that a lot of folks are actually using SnakeMD. At the time of writing, it’s in use in 12 repos, a few of which are mine. As a result, I figured I’d share some ways it’s being used.
Sample Programs Website Automation
One of my most recent uses of SnakeMD is in automating the Sample Programs website. After all, all of the web pages are written in Markdown, so it was a natural fit. For example, here’s a method I wrote to generate the list of articles for a given programming language:
def _add_language_article_section(doc: snakemd.Document, repo: subete.Repo, language: str): """ Generates a list of articles for each language page. :param snakemd.Document doc: the document to add the section to. :param subete.Repo repo: the repo to pull from. :param str language: the language to add to the document in its lookup form (e.g., Python). """ doc.add_heading("Articles", level=2) articles = [] for program in repo[language]: link = snakemd.Inline( str(program), link=program._sample_program_doc_url ) articles.append(link) doc.add_block(snakemd.MDList(articles))
As you can see, I manually generate a list using the program name and its documentation URL. Now, that’s pretty cool!
Garmin Database
A fairly popular repo, GarminDB, has apparently been using SnakeMD to render Markdown in Jupyter notebooks. I didn’t do too much digging, but I thought this was cool!
doc = snakemd.new_doc() doc.add_heading("Activities Report") doc.add_paragraph("Analysis of all activities in the database.") doc.add_table( ['Type', 'Count'], [ ["Total activities", Activities.row_count(garmin_act_db)], ["Total Lap records", ActivityLaps.row_count(garmin_act_db)], ["Activity records", ActivityRecords.row_count(garmin_act_db)], ["Fitness activities", Activities.row_count(garmin_act_db, Activities.type, 'fitness')], ["Recreation activities", Activities.row_count(garmin_act_db, Activities.type, 'recreation')] ])
Tables are one of those features I neglected to share, so here’s what that might look like in action.
Zotero2MD
Another cool repo I saw in the wild was Zotero2MD. It’s a fairly small project that pulls annotations from Zotero, a citation manager, and dumps them into Markdown files. It really take too long to find a function making use of the MDList object:
def create_annotations_section(self, annotations: List) -> None: """Generate the annotation sections (titled "Highlights") In Zotero, an annotation is a highlighted text with the possibility of having related comment and tag(s). In addition, a note can also be added to a page without any highlight. This is also considered an annotation. The itemType="annotation" in the API response of both scenarios above. Parameters ---------- annotations: List[Dict] A list containing all annotations of a Zotero Item. Returns ------- None """ self.doc.add_heading(level=1, text="Highlights") annots = [] for h in annotations: formatted_annotation = self.format_annotation(h) if isinstance(formatted_annotation, tuple): annots.append(formatted_annotation[0]) annots.append(formatted_annotation[1]) else: annots.append(formatted_annotation) self.doc.add_block(MDList(annots))
To be honest, it’s really cool to see stuff like this in the wild making use of one of my libraries. Hopefully, more folks will make use of it soon!
What Other Libraries Would You Like to See?
With all that said, I think I’ve talked enough about SnakeMD. The last thing I’ll mention is next steps. Specifically, if you’re looking to go from dynamic markdown to a document like a PDF, I have a guide for that. It’s served a lot of my students well ever since I created markdown templates for them.
As always, if you liked this article and would like to see more like it, consider showing your support. One quick way to show your support is to keep browsing:
- 3 Things Software Developers Don’t Need to Know
- The Complete Guide to Subete: A Python Library for Browsing Code Snippets
Otherwise, take care! See you next time.
Recent Code Posts
In the world of programming languages, expressions are an interesting concept that folks tend to implicitly understand but might not be able to define. As a result, I figured I'd take a crack at...
It might seem like a straightforward concept, but variables are more interesting than you think. In an effort to expand our concept map, we're here to cover one of the most basic programming...