Permalink
Browse files

Dead easy Markdown/HTML conversion! (using new note parser)

Last week I wrote a simple note to Markdown converter so I could easily
convert some notes to HTML (which I then copy/pasted to Google Docs :-).
This weekend I wrote the same thing again but now more or less properly,
using the following three stages:

  1. Parse the note's syntax for easy manipulation;
  2. Convert the note to Markdown;
  3. Convert the Markdown to HTML.

I'm not exactly proud of the parser yet. My first few attempts used Vim
regular expressions but that was just awful. What I have now is awfully
slow but it works and it's very portable. Also I haven't really been
bothered by the performance so far :-)
  • Loading branch information...
2 parents 96024ad + 4220fa8 commit 4907b6cf0484b547f8116a257516a22780715269 @xolox committed Jun 23, 2013
View
@@ -111,6 +111,10 @@ This option defines the pathname of the Python script that's used to perform acc
This option defines the pathname of the text file that stores the list of known tags used for tag name completion and the `:ShowTaggedNotes` command. The text file is created automatically when it's first needed, after that you can recreate it manually by executing `:IndexTaggedNotes` (see below).
+### The `g:notes_markdown_program` option
+
+The `:NoteToHtml` command requires the [Markdown] [markdown] program. By default the name of this program is assumed to be simply `markdown`. If you want to use a different program for Markdown to HTML conversion, set this option to the name of the program.
+
## Commands
To edit one of your existing notes (or create a new one) you can use Vim commands such as [:edit] [edit], [:split] [split] and [:tabedit] [tabedit] with a filename that starts with *note:* followed by (part of) the title of one of your notes, e.g.:
@@ -210,6 +214,24 @@ The completion menu is populated from a text file listing all your tags, one on
If for any reason you want to recreate the list of tags you can execute the `:IndexTaggedNotes` command.
+### The `:NoteToHtml` command
+
+This command converts the current note to HTML. It works by first converting the current note to [Markdown] [markdown] and then using the `markdown` program to convert that to HTML. It requires an external program to convert Markdown to HTML. By default the program `markdown` is used, but you can change the name of the program using the `g:notes_markdown_program` option.
+
+Note that this command can be a bit slow, because the parser for the note taking syntax is written in Vim script (for portability) and has not been optimized for speed (yet).
+
+### The `:NoteToMarkdown` command
+
+Convert the current note to a [Markdown document] [markdown]. The vim-notes syntax shares a lot of similarities with the Markdown text format, but there are some notable differences, which this command takes care of:
+
+ * The first line of a note is an implicit document title. In Markdown format it has to be marked with `#`. This also implies that the remaining headings should be shifted by one level.
+
+ * Preformatted blocks are marked very differently in notes and Markdown (`{{{` and `}}}` markers versus 4 space indentation).
+
+ * The markers and indentation of list items differ between notes and Markdown (dumb bullets vs Unicode bullets and 3 vs 4 spaces).
+
+Note that this command can be a bit slow, because the parser for the note taking syntax is written in Vim script (for portability) and has not been optimized for speed (yet).
+
## Mappings
The following key mappings are defined inside notes.
@@ -315,6 +337,7 @@ This software is licensed under the [MIT license] [mit].
[highlight]: http://vimdoc.sourceforge.net/htmldoc/syntax.html#:highlight
[levenshtein]: http://en.wikipedia.org/wiki/Levenshtein_distance
[mapleader]: http://vimdoc.sourceforge.net/htmldoc/map.html#mapleader
+[markdown]: http://en.wikipedia.org/wiki/Markdown
[mit]: http://en.wikipedia.org/wiki/MIT_License
[modeline]: http://vimdoc.sourceforge.net/htmldoc/options.html#modeline
[monaco]: http://en.wikipedia.org/wiki/Monaco_(typeface)
View
@@ -0,0 +1 @@
+{"vim_script_nr": 3375, "dependencies": {"vim-misc": {}}, "homepage": "http://peterodding.com/code/vim/notes", "name": "vim-notes"}
View
@@ -1,12 +1,13 @@
" Vim auto-load script
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: May 25, 2013
+" Last Change: June 23, 2013
" URL: http://peterodding.com/code/vim/notes/
" Note: This file is encoded in UTF-8 including a byte order mark so
" that Vim loads the script using the right encoding transparently.
-let g:xolox#notes#version = '0.21.5'
+let g:xolox#notes#version = '0.22'
+let g:xolox#notes#url_pattern = '\<\(mailto:\|javascript:\|\w\{3,}://\)\(\S*\w\)\+/\?'
let s:scriptdir = expand('<sfile>:p:h')
function! xolox#notes#init() " {{{1
@@ -60,6 +61,10 @@ function! xolox#notes#init() " {{{1
if !exists('g:notes_recentindex')
let g:notes_recentindex = xolox#misc#path#merge(localdir, 'recent.txt')
endif
+ " Define the default location of the template for HTML conversion.
+ if !exists('g:notes_html_template')
+ let g:notes_html_template = xolox#misc#path#merge(localdir, 'template.html')
+ endif
" Define the default action when a note's filename and title are out of sync.
if !exists('g:notes_title_sync')
" Valid values are "no", "change_title", "rename_file" and "prompt".
@@ -82,11 +87,13 @@ function! xolox#notes#init() " {{{1
let g:notes_ruler_text = repeat(' ', ((&tw > 0 ? &tw : 79) - 5) / 2) . '* * *'
endif
" Symbols used to denote list items with increasing nesting levels.
+ let g:notes_unicode_bullets = ['', '', '', '', '', '']
+ let g:notes_ascii_bullets = ['*', '-', '+']
if !exists('g:notes_list_bullets')
if xolox#notes#unicode_enabled()
- let g:notes_list_bullets = ['', '', '', '', '', '']
+ let g:notes_list_bullets = g:notes_unicode_bullets
else
- let g:notes_list_bullets = ['*', '-', '+']
+ let g:notes_list_bullets = g:notes_ascii_bullets
endif
endif
endfunction
@@ -0,0 +1,84 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 23, 2013
+" URL: http://peterodding.com/code/vim/notes/
+
+if !exists('g:notes_markdown_program')
+ let g:notes_markdown_program = 'markdown'
+endif
+
+function! xolox#notes#html#view() " {{{1
+ " Convert the current note to a web page and show the web page in a browser.
+ " Requires [Markdown] [markdown] to be installed; you'll get a warning if it
+ " isn't.
+ "
+ " [markdown]: http://en.wikipedia.org/wiki/Markdown
+ try
+ " Convert the note's text to HTML using Markdown.
+ let starttime = xolox#misc#timer#start()
+ let note_title = xolox#notes#current_title()
+ let filename = xolox#notes#title_to_fname(note_title)
+ let note_text = join(getline(1, '$'), "\n")
+ let raw_html = xolox#notes#html#convert_note(note_text)
+ let styled_html = xolox#notes#html#apply_template({
+ \ 'encoding': &encoding,
+ \ 'title': note_title,
+ \ 'content': raw_html,
+ \ 'version': g:xolox#notes#version,
+ \ 'date': strftime('%A %B %d, %Y at %H:%M'),
+ \ 'filename': fnamemodify(filename, ':~'),
+ \ })
+ let filename = s:create_temporary_file(note_title)
+ if writefile(split(styled_html, "\n"), filename) != 0
+ throw printf("Failed to write HTML file! (%s)", filename)
+ endif
+ " Open the generated HTML in a web browser.
+ call xolox#misc#open#url('file://' . filename)
+ call xolox#misc#timer#stop("notes.vim %s: Rendered HTML preview in %s.", g:xolox#notes#version, starttime)
+ catch
+ call xolox#misc#msg#warn("notes.vim %s: %s at %s", g:xolox#notes#version, v:exception, v:throwpoint)
+ endtry
+endfunction
+
+function! xolox#notes#html#convert_note(note_text) " {{{1
+ " Convert a note's text to a web page (HTML) using the [Markdown text
+ " format] [markdown] as an intermediate format. This function takes the text
+ " of a note (the first argument) and converts it to HTML, returning a
+ " string.
+ if !executable(g:notes_markdown_program)
+ throw "HTML conversion requires the `markdown' program! On Debian/Ubuntu you can install it by executing `sudo apt-get install markdown'."
+ endif
+ let markdown = xolox#notes#markdown#convert_note(a:note_text)
+ let result = xolox#misc#os#exec({'command': g:notes_markdown_program, 'stdin': markdown})
+ let html = join(result['stdout'], "\n")
+ return html
+endfunction
+
+function! xolox#notes#html#apply_template(variables) " {{{1
+ " The vim-notes plug-in contains a web page template that's used to provide
+ " a bit of styling when a note is converted to a web page and presented to
+ " the user. This function takes the original HTML produced by [Markdown]
+ " [markdown] (the first argument) and wraps it in the configured template,
+ " returning the final HTML as a string.
+ let filename = expand(g:notes_html_template)
+ call xolox#misc#msg#debug("notes.vim %s: Reading web page template from %s ..", g:xolox#notes#version, filename)
+ let template = join(readfile(filename), "\n")
+ let output = substitute(template, '{{\(.\{-}\)}}', '\= s:template_callback(a:variables)', 'g')
+ return output
+endfunction
+
+function! s:template_callback(variables) " {{{1
+ " Callback for xolox#notes#html#apply_template().
+ let key = xolox#misc#str#trim(submatch(1))
+ return get(a:variables, key, '')
+endfunction
+
+function! s:create_temporary_file(note_title) " {{{1
+ " Create a temporary filename for a note converted to an HTML document,
+ " based on the title of the note.
+ if !exists('s:temporary_directory')
+ let s:temporary_directory = xolox#misc#path#tempdir()
+ endif
+ let filename = xolox#misc#str#slug(a:note_title) . '.html'
+ return xolox#misc#path#merge(s:temporary_directory, filename)
+endfunction
@@ -0,0 +1,94 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 23, 2013
+" URL: http://peterodding.com/code/vim/notes/
+
+function! xolox#notes#markdown#view() " {{{1
+ " Convert the current note to a Markdown document and show the converted text.
+ let note_text = join(getline(1, '$'), "\n")
+ let markdown_text = xolox#notes#markdown#convert_note(note_text)
+ vnew
+ call setline(1, split(markdown_text, "\n"))
+ setlocal filetype=markdown
+endfunction
+
+function! xolox#notes#markdown#convert_note(note_text) " {{{1
+ " Convert a note's text to the [Markdown text format] [markdown]. The syntax
+ " used by vim-notes has a lot of similarities with Markdown, but there are
+ " some notable differences like the note title and the way code blocks are
+ " represented. This function takes the text of a note (the first argument)
+ " and converts it to the Markdown format, returning a string.
+ "
+ " [markdown]: http://en.wikipedia.org/wiki/Markdown
+ let starttime = xolox#misc#timer#start()
+ let blocks = xolox#notes#parser#parse_note(a:note_text)
+ call map(blocks, 'xolox#notes#markdown#convert_block(v:val)')
+ let markdown = join(blocks, "\n\n")
+ call xolox#misc#timer#stop("notes.vim %s: Converted note to Markdown in %s.", g:xolox#notes#version, starttime)
+ return markdown
+endfunction
+
+function! xolox#notes#markdown#convert_block(block) " {{{1
+ " Convert a single block produced by `xolox#misc#notes#parser#parse_note()`
+ " (the first argument, expected to be a dictionary) to the [Markdown text
+ " format] [markdown]. Returns a string.
+ if a:block.type == 'title'
+ let text = s:make_urls_explicit(a:block.text)
+ return printf("# %s", text)
+ elseif a:block.type == 'heading'
+ let marker = repeat('#', 1 + a:block.level)
+ let text = s:make_urls_explicit(a:block.text)
+ return printf("%s %s", marker, text)
+ elseif a:block.type == 'code'
+ let comment = "<!-- An innocent comment to force Markdown out of list parsing mode. See also http://meta.stackoverflow.com/a/99637 -->"
+ let text = xolox#misc#str#indent(xolox#misc#str#dedent(a:block.text), 4)
+ return join([comment, text], "\n\n")
+ elseif a:block.type == 'divider'
+ return '* * *'
+ elseif a:block.type == 'list'
+ let items = []
+ if a:block.ordered
+ let counter = 1
+ for item in a:block.items
+ let indent = repeat(' ', item.indent * 4)
+ let text = s:make_urls_explicit(item.text)
+ call add(items, printf("%s%d. %s", indent, counter, text))
+ let counter += 1
+ endfor
+ else
+ for item in a:block.items
+ let indent = repeat(' ', item.indent * 4)
+ let text = s:make_urls_explicit(item.text)
+ call add(items, printf("%s- %s", indent, text))
+ endfor
+ endif
+ return join(items, "\n\n")
+ elseif a:block.type == 'block-quote'
+ let lines = []
+ for line in a:block.lines
+ let prefix = repeat('>', line.level)
+ call add(lines, printf('%s %s', prefix, line.text))
+ endfor
+ return join(lines, "\n")
+ elseif a:block.type == 'paragraph'
+ let text = s:make_urls_explicit(a:block.text)
+ if len(text) <= 50 && text =~ ':$'
+ let text = printf('**%s**', text)
+ endif
+ return text
+ else
+ let msg = "Encountered unsupported block: %s!"
+ throw printf(msg, string(a:block))
+ endif
+endfunction
+
+function! s:make_urls_explicit(text) " {{{1
+ " In the vim-notes syntax, URLs are implicitly hyperlinks.
+ " In Markdown syntax they have to be wrapped in <markers>.
+ return substitute(a:text, g:xolox#notes#url_pattern, '\= s:url_callback(submatch(0))', 'g')
+endfunction
+
+function! s:url_callback(url)
+ let label = substitute(a:url, '^\w\+:\(//\)\?', '', '')
+ return printf('[%s](%s)', label, a:url)
+endfunction
Oops, something went wrong.

0 comments on commit 4907b6c

Please sign in to comment.