Created
January 25, 2012 15:05
-
-
Save ttscoff/1676667 to your computer and use it in GitHub Desktop.
Watch a Scrivener project for changes and update Marked.app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
# scrivwatch.rb by Brett Terpstra, 2011 | |
# Modifications to merge into one file for Markdown viewing in Marked by BooneJS, 2012 | |
# Modified to use REXML and add titles by Brett Terpstra, 2012 <https://gist.github.com/1676667> | |
# | |
# Watch a Scrivener project for changes and update a preview file for Marked | |
# Usage: scrivwatch.rb /path/to/document.scriv | |
# <http://markedapp.com> | |
require 'fileutils' | |
require 'rexml/document' | |
require 'optparse' | |
$version = "1.5" | |
$out = STDOUT | |
$progressbar = STDERR | |
$options = {} | |
optparse = OptionParser.new do|opts| | |
opts.banner = "ScrivWatcher version #{$version}\nUsage: scrivwatcher.rb [-dqt] [--cache-dir PATH] /path/to/document.scriv" | |
$options[:debug] = false | |
opts.on( '-d','--debug', 'Debug mode' ) do | |
$options[:debug] = true | |
end | |
$options[:progress] = false | |
opts.on('-p','--progress','Send progress bar commands for Platypus') do | |
$options[:progress] = true | |
end | |
$options[:quiet] = false | |
opts.on( '-q','--quiet', 'Run quietly (no progress bar/messages)' ) do | |
$options[:quiet] = true unless $options[:progress] | |
end | |
$options[:titles] = false | |
opts.on( '-t', '--titles', 'Generate Markdown headers from Scrivener page titles' ) do | |
$options[:titles] = true | |
end | |
$options[:cachedir] = File.expand_path("~/ScrivWatcher") | |
opts.on( '-c','--cache-dir PATH', 'Define the directory for cache files (default "~/ScrivWatcher"') do|cachedir| | |
$options[:cachedir] = File.expand_path(cachedir).gsub(/\/$/,'') | |
end | |
$options[:openscriv] = false | |
opts.on( '--open-scrivener', 'When loading a file in Marked, open it in Scrivener automatically') do | |
$options[:openscriv] = true | |
end | |
opts.on( '-v', 'Display version number and exit') do | |
puts "ScrivWatcher v#{$version}" | |
exit | |
end | |
opts.on( '-h', '--help', 'Display this screen' ) do | |
puts opts | |
exit | |
end | |
end | |
optparse.parse! | |
def check_running() | |
newpid = '' | |
newpid = %x{ps Ao pid,comm|grep "Marked.app"|grep -v grep|awk '{print $1}'} | |
return newpid.empty? ? false : true | |
end | |
def get_children(ele,path,files,depth) | |
ele.elements.each('*/BinderItem') do |child| | |
# Ignore docs set to not include in compile | |
includetag = REXML::XPath.first( child, "MetaData/IncludeInCompile" ) | |
if !includetag.nil? && includetag.text == "Yes" | |
id = child.attributes["ID"] | |
# passing type, would eventually use to control header/title output | |
type = child.attributes["Type"] | |
title = child.elements.to_a[0].text | |
file = "#{path}/Files/Docs/#{id}.rtf" | |
filepath = File.exists?(file) ? file : false | |
files << { 'path' => filepath, 'title' => title, 'depth' => depth, 'type' => type } | |
end | |
get_children(child,path,files,depth+1) | |
end | |
end | |
# Take the path to the scrivener file and open the internal XML file. | |
def get_rtf_file_array(scrivpath) | |
scrivpath = File.expand_path(scrivpath) | |
scrivx = File.basename("#{scrivpath}", ".scriv") + ".scrivx" | |
scrivxpath = "#{scrivpath}/#{scrivx}" | |
# handle cases where the package has been renamed after creation | |
unless File.exists?(scrivxpath) | |
scrivxpath = %x{ls -1 #{scrivpath}/*.scrivx|head -n 1}.strip | |
end | |
files = [] | |
doc = REXML::Document.new File.new(scrivxpath) | |
doc.elements.each('ScrivenerProject/Binder/BinderItem') do |ele| | |
if ele.attributes['Type'] == "DraftFolder" | |
get_children(ele,scrivpath,files,1) | |
end | |
end | |
return files | |
end | |
def usage | |
puts "Usage: scrivwatcher.rb /path/to/document.scriv" | |
puts "Use `scrivwatcher -h` for help." | |
exit | |
end | |
trap("SIGINT") { exit } | |
unless $options[:quiet] || $options[:debug] | |
if (ARGV[0] !~ /\.scriv\/?$/) | |
usage | |
end | |
end | |
if $options[:debug] && ARGV.length == 0 | |
path = File.expand_path("~/Dropbox/ScrivTest.scriv") | |
else | |
if ARGV[0] =~ /\.scriv\/?$/ | |
path = File.expand_path(ARGV[0].gsub(/\/$/,'')) | |
else | |
usage | |
end | |
end | |
sw_name = File.basename(path,".scriv") | |
$out.printf("Watching %s:\n",sw_name) unless $options[:quiet] | |
sw_target = File.expand_path($options[:cachedir]) | |
Dir.mkdir(sw_target) unless File.exists?(sw_target) | |
sw_cache_dir = File.expand_path("#{$options[:cachedir]}/cache") | |
Dir.mkdir(sw_cache_dir) unless File.exists?(sw_cache_dir) | |
sw_cache = File.expand_path("#{sw_cache_dir}/#{sw_name}") | |
# Clear cache | |
if File.exists?(sw_cache) | |
File.delete(*Dir["#{sw_cache}/*"]) if Dir.glob("#{sw_cache}/*").length > 0 | |
Dir.rmdir(sw_cache) | |
end | |
Dir.mkdir(sw_cache) | |
sw_note = "#{sw_target}/ScrivWatcher - #{sw_name}.md" | |
File.delete(sw_note) if File.exists?(sw_note) | |
FileUtils.touch(sw_note) | |
%x{open -a Scrivener "#{path}"} if $options[:openscriv] | |
%x{open -a /Applications/Marked.app "#{sw_note}"} | |
first = true | |
files = [] | |
while true do # repeat infinitely | |
unless check_running | |
puts "Marked quit, exiting" | |
exit | |
end | |
notetext = "" | |
# tracking the xml file for ordering changes as well | |
new_xml_time = File.stat(path).mtime.to_i | |
xml_time ||= new_xml_time | |
diff_xml_time = new_xml_time - xml_time | |
unless diff_xml_time == 0 && first == false | |
files = get_rtf_file_array(path) | |
end | |
# track any file changes in folder | |
new_hash = files.collect {|f| [ f['path'], File.stat(f['path']).mtime.to_i ] if f['path'] } # create a hash of timestamps | |
hash ||= new_hash | |
diff_hash = new_hash - hash # check for changes | |
arr = [0,10,20,30,40,50,60,70,80,90,100] | |
unless first == false && diff_hash.empty? && diff_xml_time == 0 # if changes were found in the timestamps | |
$out.print("change detected\n") unless first || $options[:quiet] | |
hash = new_hash | |
xml_time = new_xml_time | |
cachefiles = [] | |
total = files.length | |
current = 0 | |
files.each{ |f| | |
current += 1 | |
if f['path'] | |
cachefile = sw_cache + "/" + File.basename(f['path'],'.rtf') + ".md" | |
if !File.exists?(cachefile) || (File.stat(f['path']).mtime.to_i > File.stat(cachefile).mtime.to_i) | |
# $stdout.puts "Caching #{f['path']}" if $options[:debug] | |
note = f['path'] ? %x{/usr/bin/textutil -convert txt -stdout "#{f['path']}"} : '' | |
leader = "" | |
if $options[:titles] && !f['depth'].nil? | |
f['depth'].times { leader += "#" } | |
leader = "#{leader} #{f['title']}\n\n" | |
end | |
notetext = leader + note + "\n\n" | |
File.open(cachefile,"w"){|cf| cf.puts notetext } | |
end | |
cachefiles.push(cachefile) | |
unless $options[:quiet] | |
#progress bar | |
percent = (current * 100 / total).ceil | |
progress = arr.select{|item| item <= percent }.max | |
cache_message = first ? "Building cache:" : "Updating cache:" | |
if $options[:progress] | |
$progressbar.printf("%s\n",cache_message) | |
$progressbar.printf("PROGRESS:%d\n",progress) | |
else | |
$out.printf("%s [",cache_message) | |
$out.print("=="*(progress/10)) | |
$out.print(" "*(10-(progress/10))) unless progress == 100 | |
$out.print("]") | |
$out.printf(" %d%%\r",percent) | |
$out.flush | |
end | |
end | |
end | |
} | |
$out.print("\n") unless $options[:quiet] | |
first = false | |
# write the result to the preview file | |
concat_note = "Concatenating #{cachefiles.length} sections to #{sw_note}..." | |
if $options[:progress] | |
$progressbar.printf("%s\n",concat_note) | |
else | |
$out.print(concat_note) unless $options[:quiet] | |
end | |
File.open(sw_note,"w"){|f| f.puts cachefiles.map{|s| IO.read(s)} } | |
if $options[:progress] | |
$progressbar.printf("PROGRESS:0\n") | |
$progressbar.printf("Watching %s\n",sw_name) | |
else | |
$out.puts("Done.\nWatching...") unless $options[:quiet] | |
end | |
end | |
sleep 1 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment