-
-
Save kopischke/1009149 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby -wKU | |
# Adapted from Brett Terpstra’s original “Markdown to Evernote” service (http://brettterpstra.com/a-better-os-x-system-service-for-evernote-notes-with-multimarkdown/) | |
# Martin Kopischke 2011 – License: Creative Commons Attribution Share-Alike (CC BY-SA) 3.0 Unported (http://creativecommons.org/licenses/by-sa/3.0/) | |
# Changes: – create only one Evernote note per (Multi)Markdown input passed (instead of one per line) | |
# – do not choke on shell escape characters (use Tempfile instead of shell pipe for osascript) | |
# – default to MultiMarkdown 3 executable (instead of MMD 2 Perl script) | |
# – make smart typography processing optional (set SMARTY to 'false' to bypass processing; | |
# note smart typography cannot be disabled in MMD 3.0 and 3.0.1 | |
# – handle both smart typography processing scripts (ie. SmartyPants.pl) | |
# and (Multi)Markdown processor extensions (i.e. '--smart' and MMD's upcoming '--nosmart') | |
# – handle both command line switches to (Multi)Markdown processor and separate smart typography processors | |
# – restrict parsing for metadata (note title, tags, target notebook) to the metadata block as per MMD spec | |
# note atx style 1st level headings will be parsed anywhere, as they terminate the metadata block anyway | |
# – specify note title in metadata block MMD style, using 'Title: <name>' (not just as 1st level heading) | |
# – specify target notebook in metadata block either with '= <name>' or MMD style, using 'Notebook: <name>' | |
# – specify tags in metadata block either with '@ <tag list>' or MMD style, using 'Keywords: <tag list>' | |
# – correctly parse tag names with punctuation (no commas) and other “special” characters | |
# – correctly assign multiple tags (instead of quietly failing) | |
# – correctly parse all flavors of 1st level atx headings (not just those following the hash with a space) | |
# – use localized date time stamp from AppleScript (instead of US formatted) as fallback note title | |
# – use Evernote default notebook if none is indicated in input (instead of 'Unfiled') | |
# – added minimal sanity checks (like make sure Markdown executable actually exists and is executable) | |
# – runs on Ruby 1.8 and 1.9 | |
# To do: – process settext 1st level headings | |
# – ignore Markdown 1st level headings when a 'Title:' metadata line is found? | |
# Markdown executable path | |
# – edit to match your install location if non-default | |
# – pre-version 3 MMD script usually is '~/Application Support/MultiMarkDown/bin/MultiMarkDown.pl' | |
MARKDOWN = '/usr/local/bin/multimarkdown' | |
Process.exit unless File.executable?(MARKDOWN) | |
# Smart typography (aka SmartyPants) switch | |
SMARTY = true | |
# – Smart typography processing via MARKDOWN extension | |
# enable with '--smart' for PEG Markdown, disable using '--nosmart' in upcoming MMD version 3 | |
SMARTY_EXT_ON = '--smart' | |
SMARTY_EXT_OFF = '--nosmart' | |
# – Separate smart typography processor (i.e. SmartyPants.pl) | |
# set to path to SmartyPants.pl (for classic Markdown and MMD pre-version 3, usually same dir as (Multi)MarkDown.pl) | |
# set to '' to use SMARTY_EXT instead | |
SMARTY_PATH = '' | |
if SMARTY && !SMARTY_PATH.empty? then Process.exit unless File.executable?(SMARTY_PATH) end | |
# utility function: escape double quotes and backslashes (for AppleScript) | |
def escape(str) | |
str.to_s.gsub(/(?=["\\])/, '\\') | |
end | |
# utility function: enclose string in double quotes | |
def quote(str) | |
'"' << str.to_s << '"' | |
end | |
# buffer | |
input = '' | |
# processed data | |
contents = '' | |
title = '' | |
tags = '' | |
notebook = '' | |
# operation switches | |
metadata = true | |
# parse for metadata and pass all non-metadata to input | |
ARGF.each_line do |line| | |
case line | |
# note title (either MMD metadata 'Title' – must occur before the first blank line – or atx style 1st level heading) | |
when /^Title:\s.*?/ | |
if metadata then title = line[7..-1].strip else input << line end | |
# strip all 1st level headings as logically, note title is 1st level | |
when /^#[^#]+?/ | |
if title.empty? then title = line[line.index(/[^#]/)..-1].strip end | |
# note tags (either MMD metadata 'Keywords' or '@ <tag list>'; must occur before the first blank line) | |
when /^(Keywords:|@)\s.*?/ | |
if metadata then tags = line[line.index(/\s/)+1..-1].split(',').map {|tag| tag = tag.strip} else input << line end | |
# notebook (either MMD metadata 'Notebook' or '= <name>'; must occur before the first blank line) | |
when /^(Notebook:|=)\s.*?/ | |
if metadata then notebook = line[line.index(/\s/)+1..-1].strip else input << line end | |
# metadata block ends at first blank line | |
when /^\s?$/ | |
if metadata then metadata = false end | |
input << line | |
# anything else is appended to input | |
else | |
input << line | |
end | |
end | |
# Markdown processing | |
mmd_cmd = "#{quote MARKDOWN}" | |
mmd_cmd << if SMARTY_PATH.empty? then SMARTY ? " #{SMARTY_EXT_ON}" : " #{SMARTY_EXT_OFF}" else "|#{quote SMARTY_PATH}" end unless !SMARTY | |
IO.popen(mmd_cmd, 'r+') do |io| | |
input.each_line {|line| io << line} | |
io.close_write | |
io.each_line {|line| contents << line} | |
end | |
# create note, using localized date and time stamp as fallback for title | |
if title.empty? then title = %x{osascript -e 'get (current date) as text'}.chomp end | |
osa_cmd = "tell application #{quote 'Evernote'} to create note with html #{quote escape contents}" | |
osa_cmd << " title #{quote escape title}" | |
if tags.length > 1 then osa_cmd << " tags #{'{' << tags.map {|tag| tag = quote escape tag}.join(",") << '}'}" end | |
if tags.length == 1 then osa_cmd << " tags #{quote escape tags[0]}" end | |
osa_cmd << " notebook #{quote escape notebook}" unless notebook.empty? | |
require 'tempfile' | |
Tempfile.open('md2evernote') do |file| | |
file.puts osa_cmd | |
file.rewind | |
%x{osascript "#{file.path}"} | |
end |
Broken link, but I suspect it's here: http://nsuserview.kopischke.net/post/6223792409/i-can-has-some-markdown
@algesten thanks for catching that. Link corrected.
No thank you for fixing/further-developing the script. It's great!
great script! Is there any easy way of getting it to batch process a folder of markdown documents?
@svsmailus hi and thanks – I’m planning (well, more like pondering) to do some rewriting of the code soonish, which will make this easier. As it stands, the answer is no, as the current code is designed to work inside an OS X Service. However, you can always do
for f in (/path/to/dir/*); do echo "$f" | ruby /path/to/gist; done
as a quick workaround.
Has any one tested this on Mountain Lion? I can't seam to get it working. I've got the service in the right place, MMD installed with the support tools but when I use the service from a text editor it doesn't do anything.
Oddly, if I select a file in Path Finder and run the service it creates an Evernote note with the path and filename, it just won't work with selected text in Byword/FoldingText/Path Finder's text editor.
I created a modified version that uses RubyCocoa and ScriptingBridge instead of calling osascript
via the command line. More robust to strange characters, less processing of input. Also changed some things for efficiency/simplicity.
Edits:
@christopherdwhite: My version was written on Mountain Lion, so should work there. Although I installed multimarkdown through brew instead of the packages on the site.
@svsmailus: You can pass a filename to the script, but passing multiple files will just result in one big note. I would suggest the following:
for f in (/path/to/dir/*); do ruby /path/to/gist "$f"; done
Props for the work!
I'm an avid user of it, but it is now broken under 10.9, and I have no clue if it's my workflow or if there is something to be changed in the code. I'm running it as a Sercive, built with Automator as a Service.
Any clue?
Added support in this fork for setting the creation date via the MMD metadata.
The applescript date
class is used to do the text-->object conversion, which is pretty flexible, but no error handling is in place should if fail.
Find more info on this in this NSUserView post.