-
-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathscrivomatic
executable file
·262 lines (244 loc) · 10.7 KB
/
scrivomatic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#!/usr/bin/env ruby
# encoding: utf-8
# scrivomatic is a wrapper script that adds tools to the path (as Scrivener
# does not use the user's path), and enables some other tweaks to optimise
# the workflow when scrivener calls pandocomatic.
# `scrivomatic --help` for details…
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
require 'open3' # ruby standard library class to handle stderr and stdout
require 'optparse' # ruby standard option parser
require 'fileutils' # ruby standard library to deal with files
require 'shellwords' # escapes strings to run in the shell
#require 'debug/open_nonstop' # debugger, use binding.break to stop
##
# main scrivomatic class
class Scrivomatic
##
# Sets up the path and performs other functions to improve running
# pandocomatic from scrivener...
attr_accessor :options
attr_reader :version, :cmd, :runLog
VER = '1.0.40'.freeze
OPT = Struct.new(:input, :output, :to, :yaml, :command, :envpath, :build, :cleanup, :verbose, :dry_run, :open_log, :data_dir)
DEFENVPATH = ENV['HOME'] + '/bin'
#-------------------------------class constructor
def initialize
@options = OPT.new(nil, nil, nil, nil, 'pandocomatic', DEFENVPATH, false, false, false, false, false, nil)
@version = VER
@fileext = ''
@cmd = ''
@toolPath = ''
@runLog = ''
@latexEngine = 'xelatex'
end
#-------------------------------run all options
def run
preBuild # removes old tex file if we are building
makePath # build the path
printInfo # print some information
buildCommand # build the pandoc[omatic] command
runCommand # run the pandoc[omatic] command
postBuild # run latexmk if we are building
end
#--------------------------------checks if a file is less than 3 minutes old
def isRecent(infile)
File.exist?(infile) && (Time.now - File.mtime(infile)) <= 180
end
#--------------------------------if we will run latexmk, remove old tex file before running pandoc
def preBuild
return unless options.build && !(options.dry_run)
texPath = options.input.gsub(/#{@fileext}$/, '.tex')
texFile = texPath.gsub(/\\+/, '')
if File.exist?(texFile)
puts "\n===------ Delete OLD TeX File: #{texFile}: ------===" if @options[:verbose] == true
system("latexmk -quiet -c #{texFile} >/dev/null 2>&1")
File.delete(texFile)
end
end
#-------------------------------make env path
def makePath
home = ENV['HOME'] + '/'
pathtest = [home+'.pixi/bin', home+'.rbenv/shims', home+'.pyenv/shims',
home+'bin', '/opt/homebrew/bin', '/usr/local/bin',
'/usr/local/opt/ruby/bin', '/usr/local/lib/ruby/gems/2.7.0/bin',
home+'Library/TinyTeX/bin/universal-darwin', '/Library/TeX/texbin',
home+'anaconda/bin', home+'anaconda3/bin',
home+'miniconda/bin', home+'miniconda3/bin', home+'micromamba/bin',
home+'.cabal/bin', home+'.local/bin']
pathtest.each { |p| @options[:envpath] = @options[:envpath] + ':' + p if File.directory?(p) }
ENV['LANG'] = 'en_GB.UTF-8' if ENV['LANG'].nil? # Just in case we have no LANG, which breaks UTF8 encoding
@options[:envpath].gsub!(%r{(//)}, '/') # remove double slash
@options[:envpath].gsub!(/(::)/, ':') # remove double colon
@options[:envpath].gsub!(/:$/, '') # remove final colon
ENV['PATH'] = @options[:envpath] + ':' + ENV['PATH']
end # end makePath()
#-------------------------------print initial report
def printInfo
return if @options[:verbose] == false
puts "\n=== ------------------------------------------------------ ==="
puts '=== Scrivomatic V' + @version + ' Report @ ' + Time.now.to_s + ' ==='
puts '=== ------------------------------------------------------ ==='
puts ' Running shell: ' + `printf $SHELL`
puts ' Working directory: ' + `pwd`
puts " Initiating with Ruby #{RUBY_VERSION}"
puts '===------ Input Options: ------==='
puts @options
puts '===------ Final ENV PATH: ------==='
puts ENV['PATH']
puts '===------ TOOL PATHS: ------==='
puts `echo "---pandoc: $(which pandoc) | V: $(pandoc -v | sed -nE '1 s/^pandoc // gp')"`
puts `echo "---ruby: $(which ruby) | V: $(ruby -v)"`
puts '!--ruby version incompatible with new pandocomatic, see https://github.com/iandol/scrivomatic/blob/master/Installing-Ruby.md' if `ruby -v` =~ /ruby 2\.3\.\d/
puts `echo "---pandocomatic: $(which pandocomatic) | V: $(pandocomatic -v | sed -En '1s/^Pandocomatic version /''/p')"`
puts '---paru library: V: ' + `ruby -e 'require "paru"; puts Paru::VERSION.join(".")'`
puts `echo "---rbenv versions:"; [[ -x $(which rbenv) ]] && rbenv versions`
%w[pixi rbenv rvm gem python xelatex latexmk].each do |c|
location = `which #{c}`.chomp
puts "---#{c}: #{location}" unless location.empty?
end
puts "\n … running #{@options[:command]}, please wait …\n"
end
#-------------------------------build the command line
def buildCommand
@cmd += ' --data-dir="' + @options[:data_dir] + '"' unless @options[:data_dir].nil?
@cmd += ' --output ' + @options[:output] + ' ' unless @options[:output].nil?
@cmd += ' --to "' + @options[:to] + '"' unless @options[:to].nil?
@cmd += ' -c ' + @options[:yaml] if !@options[:yaml].nil? && @options[:command] == 'pandocomatic'
@cmd += ' --enable pandoc-verbose --log pandocomatic.log --log-level debug' if @options[:verbose] == true && @options[:command] == 'pandocomatic'
@toolPath = `which #{@options[:command]}`.chomp
@cmd = @toolPath + @cmd + ' ' + @options[:input] + ' ' unless @options[:input].nil?
end
#-------------------------------run the command
def runCommand
puts '===------ COMMAND OUTPUT: ------===' if @options[:verbose] == true
if File.exist?(@toolPath) && !options.dry_run
puts ":: Running: #{@cmd}\n" if @options[:verbose] == true
Open3.popen2e(@cmd) do |_stdin, oe, thread|
while (line = oe.gets)
puts '::: ' + line.chomp if @options[:verbose] == true
check_engine = line.match(/pdf-engine=(\w+)/)
@latexEngine = check_engine[1] unless check_engine.nil?
end
exit_status = thread.value
puts ':: exit status: ' + exit_status.to_s if @options[:verbose] == true
unless exit_status.success?
puts "\n!!!---scrivomatic::runCommand() RETURN non-zero value: #{cmd}!!!"
end
end
`open scrivomatic.log` if @options[:open_log] && isRecent('scrivomatic.log')
`open pandocomatic.log` if @options[:open_log] && isRecent('pandocomatic.log')
elsif !options.dry_run
puts "Tool doesn't exist!!!" if @options[:verbose] == true
puts "\n!!!---scrivomatic::runCommand() Couldn't find #{@toolPath} to run, please supply a proper path!"
elsif @options[:verbose] == true
puts 'Dry run, nothing actually executed...'
end
end
#-------------------------------parse inputs
def parseInputs(_arg)
optparse = OptionParser.new do |opts|
opts.banner = 'Scrivomatic V' + @version + "\n"
opts.banner += "=======================\n"
opts.banner += "Scrivomatic is a wrapper to set up the shell environment, enforces UTF8 encoding and other settings.\n\n"
opts.banner += 'Usage: scrivomatic [additional options] FILE'
opts.on('-i', '--input FILE', 'Input file') do |v|
v.gsub!(/(\A'|'\Z)/, '')
@options[:input] = v.shellescape
@fileext = Regexp.escape(File.extname(@options[:input]))
end
opts.on('-o', '--output [file]', 'Output file. Optional for pandocomatic.') do |v|
@options[:output] = v.shellescape
end
opts.on('-t', '--to [format]', 'Pandoc Format. Optional for pandocomatic.') do |v|
@options[:to] = v
end
opts.on('-y', '--yaml [file]', 'Specify which YAML file for pandocomatic.') do |v|
@options[:yaml] = v.strip.shellescape
end
opts.on('-p', '--path [dirpath]', 'Additional Path to Search for Commands.') do |v|
@options[:envpath] = v.strip.shellescape + ':' + @options[:envpath]
end
opts.on('-b', '--build', 'For LaTeX output, run latexmk') do |v|
@options[:build] = v
end
opts.on('-B', '--buildclean', 'For LaTeX output, run latexmk and cleanup') do |v|
@options[:build] = v
@options[:cleanup] = v
end
opts.on('-d', '--dry-run', 'Dry run.') do |v|
@options[:dry_run] = v
end
opts.on('-z', '--data-dir [file]', 'Pandoc data dir.') do |v|
@options[:data_dir] = v.strip.shellescape
end
opts.on('-v', '--[no-]verbose', 'Verbose output.') do |v|
@options[:verbose] = v
end
opts.on('-l', '--[no-]log', 'View log in Console.app.') do |v|
@options[:open_log] = v
end
opts.on('-h', '--help', 'Prints this help!') do
puts optparse
exit(0)
end
end # end OptionParser
optparse.parse!
# make sure we have an input file
return unless @options[:input].nil?
# otherwise check if we got passed the file
if ARGV.nil? || ARGV[0].nil?
puts optparse
abort "\n\n!!!---scrivomatic::parseInputs requires valid input file: --input"
else
v = ARGV[0].gsub(/(\A'|'\Z)/, '') # scrivener sometimes passes the file wrapped in '
@options[:input] = v.shellescape # we assume it was passed without -i flag
@fileext = Regexp.escape(File.extname(@options[:input]))
end
end # end parseInputs
#------------------------------check if we want to run latexmk
def postBuild
return unless options.build && !(options.dry_run)
texPath = options.input.gsub(/#{@fileext}$/, '.tex')
texFile = texPath.gsub(/\\+/, '')
pdfFile = texPath.gsub(/\.tex/, '.pdf')
if isRecent(texFile)
if File.exist?(pdfFile)
puts "\n===------ Remove old #{pdfFile} ------===" if @options[:verbose] == true
File.delete(pdfFile)
end
puts "\n===------ RUN LATEXMK on #{texPath}: ------===" if @options[:verbose] == true
@latexEngine = 'pdf' if @latexEngine =~ /pdflatex/
xcmd = "latexmk -logfilewarnings -interaction=nonstopmode -f -pv -time -#{@latexEngine} -f #{texPath}"
puts ":: directory: #{Dir.pwd}" if @options[:verbose] == true
puts ":: command: #{xcmd}" if @options[:verbose] == true
begin
Open3.popen2e(xcmd) do |_stdin, oe, wait_thr|
while (line = oe.gets)
if line.chomp.to_s =~ /^(Latexmk:|Run|LaTeX|This is|===|Accumulated|Missing|! )/
puts '::: ' + line.chomp if @options[:verbose] == true
end
end
exit_status = wait_thr.value
puts ':: exit status: ' + exit_status.to_s if @options[:verbose] == true
if exit_status.success? && @options[:cleanup] == true
logPath = File.basename(options.input, '.*') + '.log'
FileUtils.cp(logPath, 'latexlog_' + logPath) if File.file?(logPath)
`latexmk -C -quiet`
puts ":: Clean-up: used latexmk -c, but kept the latex build log as #{'latexlog_' + logPath}" if @options[:verbose] == true
elsif !exit_status.success?
puts "!!!---Scrivomatic: errors on build #{xcmd}, check logs!!!"
end
end
rescue StandardError => e
puts e
end
else
puts "!!!---Scrivomatic postBuild: could not find #{texPath}"
end
end
end #--------------- end Scrivomatic class
#binding.break
scriv = Scrivomatic.new
scriv.parseInputs(ARGV)
scriv.run