eM, or extendable Markdown, is small Ruby program that attempts to provide a method for extending Markdown, "a syntax designed for writing on the web." I made it more to illustrate a concept-- it is not very easy to use in its current version. Maybe you can help make it better!
I've admired the philosophy and mission of Markdown for a while now. It is able to convey, both to humans and machines, the basic formatting of writing-- bold, italics, and lists-- while also adding common web writing components like inline links and images. As a result, web publishing and blogging tools like Jekyl and Octopress that take Markdown as an input by default work really well for publishing.
For this reason I think of Markdown as the modern word-processor for the web: simple enough to be readable as plain text (and thus portable enough for the web), but powerful enough to do most of things most users do when word processing. Markdown of course is not a single app like word processors of old (like Microsoft Word), but rather a free and open syntax that can be easily converted to HTML.
However, as the web grows and expands I was curious if I could make a kernel of a framework that would allow Markdown to be extended to include new functionality. For example, web writers may want to frequently embed tweets or Facebook posts, or they may want a quick way to generate links to other webpages without going and getting the title of that webpage.
This project, eM, is built to be cloned and have extensions, written as Ruby classes, added to it. So far I've written three of these extensions as examples (the three cases described in the paragraph above). But I hope you will add more for whatever your needs are. Eventually, if this catches on, I'd love to collaborate on a system by which eM users could share their extensions, uploading and downloading them as seamlessly as Ruby gems.
In general, eM converts .em files contained in the to_convert directory in to HTML files in the converted directory. The .em files can be straight old-fashioned Markdown, or utilize whatever eM extensions are contained in the extensions directory.
OK so how do I do that?
Clone this repository to your local machine. Install the following gems (gem install gem_name):
- kramdown
- nokogiri
- twitter-text
- koala
Now run the eM editor by running ruby editor.rb. You'll be able to (n) create a new eM file, (a) re-convert all eM files, or quit the editor.
Choosing the menu item labeled "Create a new eM file" creates a new .em file in the to_convert directory and opens it in Vim (sorry for the hard default-- feel free to change). After you save the file and quit Vim (or your text editor of choice), you'll have the choice to "convert" that .em file to HTML.
The conversion process is executed in the em_file.rb model. It's a bit confusing as of this version, but basically we go through 5 steps:
- Read the .em file into an array called
em_array(referred to asself.em_array). Each line in the file becomes an element in the array. (See theread_em_filemethod.) - Apply the eM extensions in the
extensionsdirectory to each element of theem_array. - Convert this
em_arrayto HTML. (See thefilter_emmethod.) - Gather any frontend code specified in all of the extensions. (See the
gather_frontend_codemethod). - Print the HTML to an HTML file in the
converteddirectory using the ERB template in thetemplatesdirectory. (See theconvert_and_print_tomethod.)
Right, so let's look at the filter_em method in the em_file.rb file, which does the work of applying the eM extensions.
For each element of the em_array (remember, each element of the em_array is a string that is a line from the eM file the user writes) we're going to cycle through all the Ruby files in the extensions directory. If, when passed into the identify class method of an extension model, a given line of em returns true, we're going to "apply" that extension model to this line of em.
By "apply" we mean that we're going to instantiate an instance of this extension model and pass the em line into it. Then we're going to call that extension model's present instance method on the instance and use the string that it returns as the new line.
self.em_array.each do |em_line|
model_identity = false
model_names.each do |model_name|
if Object.const_get(model_name).identify(em_line)
model_identity = model_name
break
end
end
if model_identity != false
new_array << Object.const_get(model_identity).new(em_line).present
else
new_array << em_line
end
end(Could probably re-write that each loop as a map to make it cleaner, but that's for your pull request.)
If no model is identified, just pass the em_line on. We'll pass all of it through a "normal" markdown converter called Kramdown in the em_array_to_html method.
If you want the Tweet extension that I wrote to work, you'll need to register a new Twitter app, then create a file in the em directory called something like secrets.rb and gitignore it.
There you can declare variables to be used in the Tweet extension. I used global Ruby variables, which probably isn't a great choice. But here's what my secrets.rb file looks like:
$twitter_consumer_key = "key"
$twitter_consumer_secret = "secret"
$twitter_access_token = "token"
$twitter_access_token_secret = "token_secret"but with the actual keys as the strings.
Create a new Ruby file in the extensions directory named after your extension. All lowercase, with underscores for spaces. So let's say you want to make a new extension called "awesome blockquote". You'd make a file in the extensions directory called awesome_blockquote.rb.
Then, in that Ruby file, you'd define a new class as class AwesomeBlockquote. Include any Ruby gems or helper files you need.
- You're extension will need an initialize method, where you'll be passed the line of em. An example would be something like
def initialize(em_line)
@em_line = em_line
end-
You'll also need a class method called
identify, which the eM editor will use to direct a line of eM to your extension. -
Lastly you must have a
presentinstance method, which must return the HTML product of your extension (with theaction_viewgem'shtml_safemethod applied to it).
If your extension requires any frontend code to be place in the final HTML document in order to look as you desire, you may include class methods head_code and/or body_top_code to insert a string of code in to the head or the top of the body of the final HTML document. See the tweet.rb and facebook.rb extensions for examples.
If you need to store secret tokens or whatever,
In the current version of eM, I'm not even really sure what order the extensions in the extensions directory are applied to each line of eM. There is currently no way to give one extension identifying priority over another extension. Thus, for now, no one line of user-written eM can make more than one extension's identify method return true.
Obviously this would be something to improve in future versions.