2010-06-24

The Need


Having recently implemented parsing of Markdown code in my blog, and deciding to write most, if not all, of my future documentation in Markdown, what I really needed, was a way to do the following:

  1. write markdown code in a spellchecking text editor
  2. convert the markdown code to HTML
  3. preview the HTML as it would appear in a browser

Simple enough, but unfortunately, all of the solutions that I found were online forms. I wanted something that I could run on my own machine.

The Solution


As is often the case, the solution was to write a quick little application. For this application, I chose to use Python due to the readily available Python Markdown module as well as my "quick/dirty" need and recent Python usage on various projects.

The application uses GTK for the graphical interface, and webkit for HTML rendering.

Without further ado ...
Enter the Python

#!/usr/bin/env python 
''' 
Hey, this is GPLv3 and copyright 2010 Jezra 
''' 
import gtk 
import webkit 
import markdown #http://www.freewisdom.org/projects/python-markdown/ 
global default_file 
default_file = "markdown.txt" 
try: 
    import gtkspell 
    has_gtkspell=True 
except: 
    has_gtkspell=False 
     
class application: 
    def `__init__`(self): 
        #build the UI 
        winder = gtk.Window(gtk.WINDOW_TOPLEVEL) 
        winder.connect("destroy",self.quit) 
        winder.maximize() 
        #add some accellerators 
        #create an accellerator group for this window 
        accel = gtk.AccelGroup() 
        #add the ctrl+q to quit 
        accel.connect_group(gtk.keysyms.q, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.quit_accel ) 
        #add ctrl+s to save 
        accel.connect_group(gtk.keysyms.s, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.save_accel ) 
        #lock the group 
        accel.lock() 
        #add the group to the window 
        winder.add_accel_group(accel) 
        #we need a paned 
        pane = gtk.HPaned() 
        #and a vbox 
        box = gtk.VBox(False) 
        #add the pane to the box 
        box.pack_start(pane) 
        #add the box to the window 
        winder.add(box) 
        #do the text crap for the first pane 
        self.tb = gtk.TextBuffer() 
        tv = gtk.TextView(self.tb) 
        tv.set_wrap_mode(gtk.WRAP_WORD) 
        #try and add spell checking to the textview 
        if has_gtkspell: 
            #what if there is no aspell library? 
            try: 
                self.spell = gtkspell.Spell(tv) 
                self.has_spell_library=True 
            except Exception: 
                #bummer 
                self.has_spell_library=False 
                print Exception.message 
        #add the text view to a scrollable window 
        input_scroll = gtk.ScrolledWindow() 
        input_scroll.add_with_viewport(tv) 
        input_scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) 
        #add to the pane 
        pane.pack1(input_scroll,True) 
        #make the HTML viewable area 
        self.wv = webkit.WebView() 
        #disable the plugins for the webview 
        ws = self.wv.get_settings() 
        ws.set_property('enable-plugins',False) 
        self.wv.set_settings(ws) 
        out_scroll = gtk.ScrolledWindow() 
        out_scroll.add(self.wv) 
        out_scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) 
        pane.add2(out_scroll) 
        #we will add buttons at the bottom 
        bbox = gtk.HButtonBox() 
        bbox.set_layout(gtk.BUTTONBOX_START) 
        #we need buttons! 
        savebutton = gtk.Button("Save") 
        savebutton.connect("clicked", self.save) 
        markdownbutton = gtk.Button("_Markdown",use_underline=True) 
        markdownbutton.connect("clicked", self.markdown) 
        #add the buttons to the bbox 
        bbox.pack_start(savebutton,False,False,0) 
        bbox.pack_start(markdownbutton,False,False,0) 
        #add the bbox to the box 
        box.pack_start(bbox,False,False,0) 
        winder.show_all() 
        self.read_default_file() 
    def run(self): 
        gtk.main() 
    def quit(self,widget=None): 
        self.save() 
        gtk.main_quit() 
    def markdown(self,widget): 
        text = self.get_buffer_text() 
        mdtext = markdown.markdown(text) 
        self.wv.load_html_string(mdtext,"file:///") 
    def read_default_file(self): 
        try: 
            f = open(default_file,"r") 
            text = f.read() 
            self.tb.set_text(text) 
            f.close() 
        except: 
            pass 
    def quit_accel(self,accel_group, acceleratable, keyval, modifier): 
        self.quit() 
    def save_accel(self,accel_group, acceleratable, keyval, modifier): 
        self.save() 
    def save(self,widget=None): 
        #get the text 
        text = self.get_buffer_text() 
        f = open(default_file,"w") 
        f.write(text) 
        f.close() 
    def get_buffer_text(self): 
        start_iter = self.tb.get_start_iter() 
        end_iter = self.tb.get_end_iter() 
        text=self.tb.get_text(start_iter,end_iter) 
        return text 
if `__name__`=="`__main__`": 
    a = application() 
    a.run() 

Usage

  • Ctrl+q quits the application
  • Ctrl+s saves the current document
  • Alt+m marks down the text and displays the HTML in the right pane

Note: the 'save' location is hard coded to be "markdown.txt" in the directory where the code is run from.

EDIT:the code for markdowner is now available on launchpad

Now quit reading, and go markdown some text.

Comments
2010-07-03 Jon Kulp:
Jezra, this is brilliant!! I use Markdown for all my course syllabi and some other stuff and this is a very cool preview mechanism for it. I normally use vim and then just run a shell script that converts it to html and displays in browser. I'll definitely be using this thing, man! Feature request: syntax highlighting! I have this in vim thanks to some guy who put together a syntax file and shared it on the web.
2010-07-03 jezra:
I only *start* the conversation... I mean code. It is up to others to add features. Well, this may turn into a project, and if so, you will be able to add your feature request to whichever source control system get used.
2010-09-18 yadon:
This looks quite intriguing, in the vein of what I'm looking for! Did you write this with Python 2.7 in mind, or will it only work on 2.6? I've never really used pyGTK or pywebkitgtk before, so I find these dependencies a bit confusing. Could you clarify a bit exactly what versions would be optimal? Thanks.
2010-09-18 Jezra:
yadon, there is no reason why the code shouldn't run with *almost* any of the 2..X python releases. It should also work with python 3, although I haven't tested it.

GTK is used for creating the graphical interface of the application, and Webkit is used for rendering the HTML that gets produced by the markdown module. Any version of these two libraries released in the last 2 years should be sufficient.
2010-09-18 yadon:
Thank you for clarifying. I'm quite new to Python, hence the confusion.

I'd appreciate if you could sort one more thing out for me. The libraries which you mention, GTK and Webkit respectively, are in actuality pyGTK and pywebkitgtk, or are there other modules for Python to use? Did you write this code with these specific modules in mind?
2010-09-18 jezra:
Python uses the pyGTK module to access the GTK library. It is the same with webkit. In a sense pyGTK is a bridge that allows python and GTK to interact.

There are various libraries that I could have used to create the interface and display the HTML but I prefer GTK and I use GTK for almost all of my interface needs.

If you have further questions, it would probably be more efficient to email them to me.
2010-11-30 Mark Stosberg:
How do open a pre-existing Markdown file?jj
2010-11-30 Mark Stosberg:
I figured it out. Nice tool!

###

4a5
> import sys
9c10,11
< default_file = "markdown.txt"
---
> # default to loading a file on the command line: ./markdowner.py file.mkd
> default_file = sys.argv[1]
2011-05-24 Anonymous:
sed 's/input_scroll.add_with_viewport/input_scroll.add/g'

If I read the documentation correctly, you add a text view to a scrolled window with add() since it has it's own native scrolling
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 5
Answer:
required
  • Tags:
  • Python
subscribe
 
2019
2016
2015
2014
2013
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008