From 85b7bc695629926641c7cb752fd478adfdf374f3 Mon Sep 17 00:00:00 2001 From: Marius Date: Sun, 4 Jul 2010 15:32:09 +0300 Subject: stable 2010-05-24 13:10 --- scripts/context/ruby/base/ctx.rb | 462 +++++++ scripts/context/ruby/base/exa.rb | 407 ++++++ scripts/context/ruby/base/file.rb | 150 ++ scripts/context/ruby/base/kpse.rb | 389 ++++++ scripts/context/ruby/base/kpse/drb.rb | 57 + scripts/context/ruby/base/kpse/soap.rb | 79 ++ scripts/context/ruby/base/kpse/trees.rb | 84 ++ scripts/context/ruby/base/kpsedirect.rb | 34 + scripts/context/ruby/base/kpsefast.rb | 934 +++++++++++++ scripts/context/ruby/base/kpseremote.rb | 116 ++ scripts/context/ruby/base/kpserunner.rb | 87 ++ scripts/context/ruby/base/logger.rb | 104 ++ scripts/context/ruby/base/merge.rb | 139 ++ scripts/context/ruby/base/mp.rb | 167 +++ scripts/context/ruby/base/pdf.rb | 75 + scripts/context/ruby/base/state.rb | 75 + scripts/context/ruby/base/switch.rb | 635 +++++++++ scripts/context/ruby/base/system.rb | 121 ++ scripts/context/ruby/base/tex.rb | 2299 +++++++++++++++++++++++++++++++ scripts/context/ruby/base/texutil.rb | 1097 +++++++++++++++ scripts/context/ruby/base/tool.rb | 291 ++++ scripts/context/ruby/base/variables.rb | 132 ++ 22 files changed, 7934 insertions(+) create mode 100644 scripts/context/ruby/base/ctx.rb create mode 100644 scripts/context/ruby/base/exa.rb create mode 100644 scripts/context/ruby/base/file.rb create mode 100644 scripts/context/ruby/base/kpse.rb create mode 100644 scripts/context/ruby/base/kpse/drb.rb create mode 100644 scripts/context/ruby/base/kpse/soap.rb create mode 100644 scripts/context/ruby/base/kpse/trees.rb create mode 100644 scripts/context/ruby/base/kpsedirect.rb create mode 100644 scripts/context/ruby/base/kpsefast.rb create mode 100644 scripts/context/ruby/base/kpseremote.rb create mode 100644 scripts/context/ruby/base/kpserunner.rb create mode 100644 scripts/context/ruby/base/logger.rb create mode 100644 scripts/context/ruby/base/merge.rb create mode 100644 scripts/context/ruby/base/mp.rb create mode 100644 scripts/context/ruby/base/pdf.rb create mode 100644 scripts/context/ruby/base/state.rb create mode 100644 scripts/context/ruby/base/switch.rb create mode 100644 scripts/context/ruby/base/system.rb create mode 100644 scripts/context/ruby/base/tex.rb create mode 100644 scripts/context/ruby/base/texutil.rb create mode 100644 scripts/context/ruby/base/tool.rb create mode 100644 scripts/context/ruby/base/variables.rb (limited to 'scripts/context/ruby/base') diff --git a/scripts/context/ruby/base/ctx.rb b/scripts/context/ruby/base/ctx.rb new file mode 100644 index 000000000..13b4045af --- /dev/null +++ b/scripts/context/ruby/base/ctx.rb @@ -0,0 +1,462 @@ +# module : base/ctx +# copyright : PRAGMA Advanced Document Engineering +# version : 2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# todo: write systemcall for mpost to file so that it can be run +# faster + +# report ? + +require 'base/system' +require 'base/file' +require 'base/switch' # has needsupdate, bad place + +require 'rexml/document' + +class CtxRunner + + attr_reader :environments, :modules, :filters, :flags, :modes + + @@suffix = 'prep' + + def initialize(jobname=nil,logger=nil) + if @logger = logger then + def report(str='') + @logger.report(str) + end + else + def report(str='') + puts(str) + end + end + @jobname = jobname + @ctxname = nil + @xmldata = nil + @prepfiles = Hash.new + @environments = Array.new + @modules = Array.new + @filters = Array.new + @flags = Array.new + @modes = Array.new + @local = false + @paths = Array.new + end + + def register_path(str) + @paths << str + end + + def manipulate(ctxname=nil,defaultname=nil) + + if ctxname then + @ctxname = ctxname + @jobname = File.suffixed(@ctxname,'tex') unless @jobname + else + @ctxname = File.suffixed(@jobname,'ctx') if @jobname + end + + if not @ctxname then + report('no ctx file specified') + return + end + + if @ctxname !~ /\.[a-z]+$/ then + @ctxname += ".ctx" + end + + # name can be kpse:res-make.ctx + if not FileTest.file?(@ctxname) then + fullname, done = '', false + if @ctxname =~ /^kpse:/ then + begin + if fullname = Kpse.found(@ctxname.sub(/^kpse:/,'')) then + @ctxname, done = fullname, true + end + rescue + # should not happen + end + else + ['..','../..'].each do |path| + begin + fullname = File.join(path,@ctxname) + if FileTest.file?(fullname) then + @ctxname, done = fullname, true + end + rescue + # probably strange join + end + break if done + end + if ! done then + fullname = Kpse.found(@ctxname) + if FileTest.file?(fullname) then + @ctxname, done = fullname, true + end + end + end + if ! done && defaultname && FileTest.file?(defaultname) then + report("using default ctxfile #{defaultname}") + @ctxname, done = defaultname, true + end + if not done then + report('no ctx file found') + return false + end + end + + if FileTest.file?(@ctxname) then + @xmldata = IO.read(@ctxname) + else + report('no ctx file found') + return false + end + + unless @xmldata =~ /^.*<\?xml.*?\?>/moi then + report("ctx file #{@ctxname} is no xml file, skipping") + return + else + report("loading ctx file #{@ctxname}") + end + + if @xmldata then + # out if a sudden rexml started to be picky about namespaces + @xmldata.gsub!(//,"") + end + + begin + @xmldata = REXML::Document.new(@xmldata) + rescue + report('provide valid ctx file (xml error)') + return + else + include(@xmldata,'ctx:include','name') + end + + begin + variables = Hash.new + if @jobname then + variables['job'] = @jobname + end + root = @xmldata.root + REXML::XPath.each(root,"/ctx:job//ctx:flags/ctx:flag") do |flg| + @flags << justtext(flg) + end + REXML::XPath.each(root,"/ctx:job//ctx:resources/ctx:environment") do |sty| + @environments << justtext(sty) + end + REXML::XPath.each(root,"/ctx:job//ctx:resources/ctx:module") do |mod| + @modules << justtext(mod) + end + REXML::XPath.each(root,"/ctx:job//ctx:resources/ctx:filter") do |fil| + @filters << justtext(fil) + end + REXML::XPath.each(root,"/ctx:job//ctx:resources/ctx:mode") do |fil| + @modes << justtext(fil) + end + begin + REXML::XPath.each(root,"//ctx:block") do |blk| + if @jobname && blk.attributes['pattern'] then + root.delete(blk) unless @jobname =~ /#{blk.attributes['pattern']}/ + else + root.delete(blk) + end + end + rescue + end + REXML::XPath.each(root,"//ctx:value[@name='job']") do |val| + substititute(val,variables['job']) + end + REXML::XPath.each(root,"/ctx:job//ctx:message") do |mes| + report("preprocessing: #{justtext(mes)}") + end + REXML::XPath.each(root,"/ctx:job//ctx:process/ctx:resources/ctx:environment") do |sty| + @environments << justtext(sty) + end + REXML::XPath.each(root,"/ctx:job//ctx:process/ctx:resources/ctx:module") do |mod| + @modules << justtext(mod) + end + REXML::XPath.each(root,"/ctx:job//ctx:process/ctx:resources/ctx:filter") do |fil| + @filters << justtext(fil) + end + REXML::XPath.each(root,"/ctx:job//ctx:process/ctx:resources/ctx:mode") do |fil| + @modes << justtext(fil) + end + REXML::XPath.each(root,"/ctx:job//ctx:process/ctx:flags/ctx:flag") do |flg| + @flags << justtext(flg) + end + commands = Hash.new + REXML::XPath.each(root,"/ctx:job//ctx:preprocess/ctx:processors/ctx:processor") do |pre| + begin + commands[pre.attributes['name']] = pre + rescue + end + end + suffix = @@suffix + begin + suffix = REXML::XPath.match(root,"/ctx:job//ctx:preprocess/@suffix").to_s + rescue + suffix = @@suffix + else + if suffix && suffix.empty? then suffix = @@suffix end + end + if (REXML::XPath.first(root,"/ctx:job//ctx:preprocess/ctx:processors/@local").to_s =~ /(yes|true)/io rescue false) then + @local = true + else + @local = false + end + REXML::XPath.each(root,"/ctx:job//ctx:preprocess/ctx:files") do |files| + REXML::XPath.each(files,"ctx:file") do |pattern| + suffix = @@suffix + begin + suffix = REXML::XPath.match(root,"/ctx:job//ctx:preprocess/@suffix").to_s + rescue + suffix = @@suffix + else + if suffix && suffix.empty? then suffix = @@suffix end + end + preprocessor = pattern.attributes['processor'] + if preprocessor and not preprocessor.empty? then + begin + variables['old'] = @jobname + variables['new'] = "" + REXML::XPath.each(pattern,"ctx:value") do |value| + if name = value.attributes['name'] then + substititute(value,variables[name.to_s]) + end + end + rescue + report('unable to resolve file pattern') + return + end + pattern = justtext(pattern) + oldfiles = Dir.glob(pattern) + pluspath = false + if oldfiles.length == 0 then + report("no files match #{pattern}") + if @paths.length > 0 then + @paths.each do |p| + oldfiles = Dir.glob("#{p}/#{pattern}") + if oldfiles.length > 0 then + pluspath = true + break + end + end + if oldfiles.length == 0 then + report("no files match #{pattern} on path") + end + end + end + oldfiles.each do |oldfile| + newfile = "#{oldfile}.#{suffix}" + newfile = File.basename(newfile) if @local # or pluspath + if File.expand_path(oldfile) != File.expand_path(newfile) && File.needsupdate(oldfile,newfile) then + report("#{oldfile} needs preprocessing") + begin + File.delete(newfile) + rescue + # hope for the best + end + # there can be a sequence of processors + preprocessor.split(',').each do |pp| + if command = commands[pp] then + # a lie: no + command = REXML::Document.new(command.to_s) # don't infect original + # command = command.deep_clone() # don't infect original + command = command.elements["ctx:processor"] + if suf = command.attributes['suffix'] then + newfile = "#{oldfile}.#{suf}" + end + begin + newfile = File.basename(newfile) if @local + rescue + end + REXML::XPath.each(command,"ctx:old") do |value| replace(value,oldfile) end + REXML::XPath.each(command,"ctx:new") do |value| replace(value,newfile) end + report("preprocessing #{oldfile} into #{newfile} using #{pp}") + variables['old'] = oldfile + variables['new'] = newfile + REXML::XPath.each(command,"ctx:value") do |value| + if name = value.attributes['name'] then + substititute(value,variables[name.to_s]) + end + end + command = justtext(command) + report(command) + unless ok = System.run(command) then + report("error in preprocessing file #{oldfile}") + end + begin + oldfile = File.basename(oldfile) if @local + rescue + end + end + end + if FileTest.file?(newfile) then + File.syncmtimes(oldfile,newfile) + else + report("check target location of #{newfile}") + end + else + report("#{oldfile} needs no preprocessing (same file)") + end + @prepfiles[oldfile] = FileTest.file?(newfile) + end + end + end + end + rescue + report("fatal error in preprocessing #{@ctxname}: #{$!}") + end + end + + def savelog(ctlname=nil) + unless ctlname then + if @jobname then + ctlname = File.suffixed(@jobname,'ctl') + elsif @ctxname then + ctlname = File.suffixed(@ctxname,'ctl') + else + return + end + end + if @prepfiles.length > 0 then + if log = File.open(ctlname,'w') then + log << "\n\n" + if @local then + log << "\n" + else + log << "\n" + end + @prepfiles.keys.sort.each do |prep| + # log << "\t#{File.basename(prep)}\n" + log << "\t#{prep}\n" + end + log << "\n" + log.close + end + else + begin + File.delete(ctlname) + rescue + end + end + end + + private + + def include(xmldata,element='ctx:include',attribute='name') + loop do + begin + more = false + REXML::XPath.each(xmldata.root,element) do |e| + begin + name = e.attributes.get_attribute(attribute).to_s + name = e.text.to_s if name.empty? + name.strip! if name + done = false + if name and not name.empty? then + ['.',File.dirname(@ctxname),'..','../..'].each do |path| + begin + fullname = if path == '.' then name else File.join(path,name) end + if FileTest.file?(fullname) then + if f = File.open(fullname,'r') and i = REXML::Document.new(f) then + report("including ctx file #{name}") + REXML::XPath.each(i.root,"*") do |ii| + xmldata.root.insert_before(e,ii) + more = true + end + end + done = true + end + rescue + end + break if done + end + end + report("no valid ctx inclusion file #{name}") unless done + rescue Exception + # skip this file + ensure + xmldata.root.delete(e) + end + end + break unless more + rescue Exception + break # forget about inclusion + end + end + end + + private + + def yes_or_no(b) + if b then 'yes' else 'no' end + end + + private # copied from rlxtools.rb + + def justtext(str) + str = str.to_s + str.gsub!(/<[^>]*?>/o, '') + str.gsub!(/\s+/o, ' ') + str.gsub!(/</o, '<') + str.gsub!(/>/o, '>') + str.gsub!(/&/o, '&') + str.gsub!(/"/o, '"') + str.gsub!(/[\/\\]+/o, '/') + return str.strip + end + + def substititute(value,str) + if str then + begin + if value.attributes.key?('method') then + str = filtered(str.to_s,value.attributes['method'].to_s) + end + if str.empty? && value.attributes.key?('default') then + str = value.attributes['default'].to_s + end + value.insert_after(value,REXML::Text.new(str.to_s)) + rescue Exception + end + end + end + + def replace(value,str) + if str then + begin + value.insert_after(value,REXML::Text.new(str.to_s)) + rescue Exception + end + end + end + + def filtered(str,method) + str = str.to_s # to be sure + case method + when 'name' then # no path, no suffix + case str + when /^.*[\\\/](.+?)\..*?$/o then $1 + when /^.*[\\\/](.+?)$/o then $1 + when /^(.*)\..*?$/o then $1 + else str + end + when 'path' then if str =~ /^(.+)([\\\/])(.*?)$/o then $1 else '' end + when 'suffix' then if str =~ /^.*\.(.*?)$/o then $1 else '' end + when 'nosuffix' then if str =~ /^(.*)\..*?$/o then $1 else str end + when 'nopath' then if str =~ /^.*[\\\/](.*?)$/o then $1 else str end + when 'base' then if str =~ /^.*[\\\/](.*?)$/o then $1 else str end + when 'full' then str + when 'complete' then str + when 'expand' then File.expand_path(str).gsub(/\\/,"/") + else str + end + end + +end diff --git a/scripts/context/ruby/base/exa.rb b/scripts/context/ruby/base/exa.rb new file mode 100644 index 000000000..7ba990cf9 --- /dev/null +++ b/scripts/context/ruby/base/exa.rb @@ -0,0 +1,407 @@ +# \setuplayout[width=3cm] +# +# tex.setup.setuplayout.width.[integer|real|dimension|string|key] +# tex.[mp]var.whatever.width.[integer|real|dimension|string|key] + +require 'fileutils' +# require 'ftools' +require 'digest/md5' + +# this can become a lua thing + +# no .*? but 0-9a-z\:\. because other too slow (and greedy) + +class Hash + + def subset(pattern) + h = Hash.new + r = /^#{pattern.gsub('.','\.')}/ + self.keys.each do |k| + h[k] = self[k].dup if k =~ r + end + return h + end + +end + +module ExaEncrypt + + def ExaEncrypt.encrypt_base(logger, oldfilename, newfilename) + if FileTest.file?(oldfilename) then + logger.report("checking #{oldfilename}") if logger + if data = IO.read(oldfilename) then + done = false + # cfg file: + # + # banner : exa configuration file + # user : domain, name = password, projectlist + # + if data =~ /^\s*banner\s*\:\s*exa\s*configuration\s*file/ then + data.gsub!(/^(\s*user\s*\:\s*.+?\s*\,\s*.+?\s*\=\s*)(.+?)(\s*\,\s*.+\s*)$/) do + pre, password, post = $1, $2, $3 + unless password =~ /MD5:/i then + done = true + password = "MD5:" + Digest::MD5.hexdigest(password).upcase + end + "#{pre}#{password}#{post}" + end + else + data.gsub!(/]*?)>(.*?)<\/exa:password>/moi) do + attributes, password = $1, $2 + unless password =~ /^([0-9A-F][0-9A-F])+$/ then + done = true + password = Digest::MD5.hexdigest(password).upcase + attributes = " encryption='md5'#{attributes}" + end + "#{password}" + end + end + begin + File.open(newfilename,'w') do |f| + f.puts(data) + end + rescue + logger.report("#{newfilename} cannot be written") if logger + else + logger.report("#{oldfilename} encrypted into #{newfilename}") if done and logger + end + end + end + end + +end + +module ExaModes + + @@modefile = 'examodes.tex' + + @@request = /(.*?<\/exa:request>)/mo + @@redone = /]*?texified=([\'\"])yes\1.*?>/mo + @@reload = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@recalc = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)([\.\:]calcmath)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@rename = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@refile = /<(exa:filename|exa:filelist)>(.*?)<(\/\1)>/mo + + def ExaModes.cleanup_request(logger,filename='request.exa',modefile=@@modefile) + begin File.delete(filename+'.raw') ; rescue ; end + begin File.delete(modefile) ; rescue ; end + if FileTest.file?(filename) then + data, done = nil, false + begin + data = IO.read(filename) + rescue + data = nil + end + if data =~ @@request and data !~ @@redone then + data.gsub!(@@rename) do + done = true + '<' + $1 + $2 + $3 + $4 + $5 + '>' + + texifiedstr($4,$6) + + '<' + $7 + '>' + end + data.gsub!(@@refile) do + done = true + '<' + $1 + '>' + + cleanpath($2) + + '<' + $3 + '>' + end + data.gsub!(@@recalc) do + done = true + '<' + $1 + $2 + $3 + $4 + ":raw" + $6 + '>' + $7 + '<' + $8 + '>' + + '<' + $1 + $2 + $3 + $4 + $6 + '>' + + calculatortexmath($7,false) + + '<' + $8 + '>' + end + if done then + data.gsub!(@@request) do + $1 + " texified='yes'" + $2 + end + begin File.copy(filename, filename+'.raw') ; rescue ; end + begin + logger.report("rewriting #{filename}") if logger + File.open(filename,'w') do |f| + f.puts(data) + end + rescue + logger.report("#{filename} cannot be rewritten") if logger + end + end + else + logger.report("#{filename} is already ok") if logger + end + @variables = Hash.new + data.scan(@@reload) do + @variables[$4] = $5 + end + vars = @variables.subset('data.tex.var') + mpvars = @variables.subset('data.tex.mpvar') + modes = @variables.subset('data.tex.mode') + setups = @variables.subset('data.tex.setup') + if not (modes.empty? and setups.empty? and vars.empty? and mpvars.empty?) then + begin + File.open(modefile,'w') do |mod| + logger.report("saving modes and setups in #{modefile}") if logger + if not modes.empty? then + for key in modes.keys do + k = key.dup + k.gsub!(/\./,'-') + mod.puts("\\enablemode[#{k}-#{modes[key]}]\n") + if modes[key] =~ /(on|yes|start)/o then # ! ! ! ! ! + mod.puts("\\enablemode[#{k}]\n") + end + end + mod.puts("\n\\readfile{cont-mod}{}{}\n") + end + if not setups.empty? then + for key in setups.keys + if key =~ /^(.+?)\.(.+?)\.(.+?)$/o then + command, key, type, value = $1, $2, $3, setups[key] + value = cleanedup(key,type,value) + mod.puts("\\#{$1}[#{key}=#{value}]\n") + elsif key =~ /^(.+?)\.(.+?)$/o then + command, type, value = $1, $2, setups[key] + mod.puts("\\#{$1}[#{value}]\n") + end + end + end + savevaroptions(vars, 'setvariables', mod) + savevaroptions(mpvars,'setMPvariables',mod) + end + rescue + logger.report("#{modefile} cannot be saved") if logger + end + else + logger.report("#{modefile} is not created") if logger + end + end + end + + private + + def ExaModes.autoparenthesis(str) + if str =~ /[\+\-]/o then '[1]' + str + '[1]' else str end + end + + def ExaModes.cleanedup(key,type,value) + if type == 'dimension' then + unless value =~ /(cm|mm|in|bp|sp|pt|dd|em|ex)/o + value + 'pt' + else + value + end + elsif type == 'calcmath' then + '{' + calculatortexmath(value,true) + '}' + elsif type =~ /^filename|filelist$/ or key =~ /^filename|filelist$/ then + cleanpath(value) + else + value + end + end + + def ExaModes.cleanpath(str) + (str ||'').gsub(/\\/o,'/') + end + + def ExaModes.texifiedstr(key,val) + case key + when 'filename' then + cleanpath(val) + when 'filelist' then + cleanpath(val) + else + val + end + end + + def ExaModes.savevaroptions(vars,setvariables,mod) + if not vars.empty? then + for key in vars.keys do + # var.whatever.width.dimension.value + if key =~ /^(.+?)\.(.+?)\.(.+?)$/o then + tag, key, type, value = $1, $2, $3, vars[key] + value = cleanedup(key,type,value) + mod.puts("\\#{setvariables}[#{tag}][#{key}=#{value}]\n") + elsif key =~ /^(.+?)\.(.+?)$/o then + tag, key, value = $1, $2, vars[key] + mod.puts("\\#{setvariables}[#{tag}][#{key}=#{value}]\n") + end + end + end + end + + def ExaModes.calculatortexmath(str,tx=true) + if tx then + bdisp, edisp = "\\displaymath\{", "\}" + binln, einln = "\\inlinemath\{" , "\}" + egraf = "\\endgraf" + else + bdisp, edisp = "", "" + binln, einln = "" , "" + egraf = "

" + end + str.gsub!(/\n\s*\n+/moi, "\\ENDGRAF ") + str.gsub!(/(\[\[)\s*(.*?)\s*(\]\])/mos) do + $1 + docalculatortexmath($2) + $3 + end + str.gsub!(/(\\ENDGRAF)+\s*(\[\[)\s*(.*?)\s*(\]\])/moi) do + $1 + bdisp + $3 + edisp + end + str.gsub!(/(\[\[)\s*(.*?)\s*(\]\])/o) do + binln + $2 + einln + end + str.gsub!(/\\ENDGRAF/mos, egraf) + str + end + + def ExaModes.docalculatortexmath(str) + str.gsub!(/\n/o) { ' ' } + str.gsub!(/\s+/o) { ' ' } + str.gsub!(/>/o) { '>' } + str.gsub!(/</o) { '<' } + str.gsub!(/&.*?;/o) { } + level = 0 + str.gsub!(/([\(\)])/o) do |chr| + if chr == '(' then + level = level + 1 + chr = '[' + level.to_s + ']' + elsif chr == ')' then + chr = '[' + level.to_s + ']' + level = level - 1 + end + chr + end + # ...E... + loop do + break unless str.gsub!(/([\d\.]+)E([\-\+]{0,1}[\d\.]+)/o) do + "\{\\SCINOT\{#{$1}\}\{#{$2}\}\}" + end + end + # ^-.. + loop do + break unless str.gsub!(/\^([\-\+]*\d+)/o) do + "\^\{#{$1}\}" + end + end + # ^(...) + loop do + break unless str.gsub!(/\^(\[\d+\])(.*?)\1/o) do + "\^\{#{$2}\}" + end + end + # 1/x^2 + loop do + break unless str.gsub!(/([\d\w\.]+)\/([\d\w\.]+)\^([\d\w\.]+)/o) do + "@\{#{$1}\}\{#{$2}\^\{#{$3}\}\}" + end + end + # int(a,b,c) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?),(.*?),(.*?)\2/o) do + "\\#{$1.upcase}\^\{#{$4}\}\_\{#{$5}\}\{#{autoparenthesis($3)}\}" + end + end + # int(a,b) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?),(.*?)\2/o) do + "\\#{$1.upcase}\_\{#{$4}\}\{#{autoparenthesis($3)}\}" + end + end + # int(a) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?)\2/o) do + "\\#{$1.upcase}\{#{autoparenthesis($3)}\}" + end + end + # sin(x) => {\sin(x)} + loop do + break unless str.gsub!(/(median|min|max|round|sqrt|sin|cos|tan|sinh|cosh|tanh|ln|log)\s*(\[\d+\])(.*?)\2/o) do + "\{\\#{$1.upcase}\{#{$2}#{$3}#{$2}\}\}" + end + end + # mean + str.gsub!(/(mean)(\[\d+\])(.*?)\2/o) do + "\{\\OVERLINE\{#{$3}\}\}" + end + # sin x => {\sin(x)} + # ... + # (1+x)/(1+x) => \frac{1+x}{1+x} + loop do + break unless str.gsub!(/(\[\d+\])(.*?)\1\/(\[\d+\])(.*?)\3/o) do + "@\{#{$2}\}\{#{$4}\}" + end + end + # (1+x)/x => \frac{1+x}{x} + loop do + break unless str.gsub!(/(\[\d+\])(.*?)\1\/([a-zA-Z0-9]+)/o) do + "@\{#{$2}\}\{#{$3}\}" + end + end + # 1/(1+x) => \frac{1}{1+x} + loop do + break unless str.gsub!(/([a-zA-Z0-9]+)\/(\[\d+\])(.*?)\2/o) do + "@\{#{$1}\}\{#{$3}\}" + end + end + # 1/x => \frac{1}{x} + loop do + break unless str.gsub!(/([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)/o) do + "@\{#{$1}\}\{#{$2}\}" + end + end + # + str.gsub!(/\@/o) do + "\\FRAC " + end + str.gsub!(/\*/o) do + " " + end + str.gsub!(/\<\=/o) do + "\\LE " + end + str.gsub!(/\>\=/o) do + "\\GE " + end + str.gsub!(/\=/o) do + "\\EQ " + end + str.gsub!(/\/) do + "\\GT " + end + str.gsub!(/(D)(\[\d+\])(.*?)\2/o) do + "\{\\FRAC\{\\MBOX{d}\}\{\\MBOX{d}x\}\{#{$2}#{$3}#{$2}\}\}" + end + str.gsub!(/(exp)(\[\d+\])(.*?)\2/o) do + "\{e^\{#{$3}\}\}" + end + str.gsub!(/(abs)(\[\d+\])(.*?)\2/o) do + "\{\\left\|#{$3}\\right\|\}" + end + str.gsub!(/D([x|y])/o) do + "\\FRAC\{\{\\rm d\}#{$1}\}\{\{\\rm d\}x\}" + end + str.gsub!(/D([f|g])(\[\d+\])(.*?)\2/o) do + "\{\\rm #{$1}\}'#{$2}#{$3}#{$2}" + end + str.gsub!(/([f|g])(\[\d+\])(.*?)\2/o) do + "\{\\rm #{$1}\}#{$2}#{$3}#{$2}" + end + str.gsub!(/(pi|inf)/io) do + "\\#{$1} " + end + loop do + break unless str.gsub!(/(\[\d+?\])(.*?)\1/o) do + "\\left(#{$2}\\right)" + end + end + str.gsub!(/\\([A-Z]+?)([\s\{\^\_\\])/io) do + "\\#{$1.downcase}#{$2}" + end + str + end + +end + +# ExaModes.cleanup_request() diff --git a/scripts/context/ruby/base/file.rb b/scripts/context/ruby/base/file.rb new file mode 100644 index 000000000..1aeac5fd6 --- /dev/null +++ b/scripts/context/ruby/base/file.rb @@ -0,0 +1,150 @@ +# module : base/file +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +require 'fileutils' +# require 'ftools' + +class File + + def File.suffixed(name,sufa,sufb=nil) + if sufb then + if sufa.empty? then + unsuffixed(name) + ".#{sufb}" + else + unsuffixed(name) + "-#{sufa}.#{sufb}" + end + else + unsuffixed(name) + ".#{sufa}" + end + end + + def File.unsuffixed(name) + name.sub(/\.[^\.]*?$/o, '') + end + + def File.suffix(name,default='') + if name =~ /\.([^\.]*?)$/o then + $1 + else + default + end + end + + def File.splitname(name,suffix='') + if name =~ /^(.*)\.([^\.]*?)$/o then + [$1, $2] + else + [name, suffix] + end + end + +end + +class File + + def File.silentopen(name,method='r') + begin + f = File.open(name,method) + rescue + return nil + else + return f + end + end + + def File.silentread(name) + begin + data = IO.read(name) + rescue + return nil + else + return data + end + end + + def File.atleast?(name,n=0) + begin + size = FileTest.size(name) + rescue + return false + else + return size > n + end + end + + def File.appended(name,str='') + if FileTest.file?(name) then + begin + if f = File.open(name,'a') then + f << str + f.close + return true + end + rescue + end + end + return false + end + + def File.written(name,str='') + begin + if f = File.open(name,'w') then + f << str + f.close + return true + end + rescue + end + return false + end + + def File.silentdelete(filename) + File.delete(filename) rescue false + end + + def File.silentcopy(oldname,newname) + return if File.expand_path(oldname) == File.expand_path(newname) + FileUtils.makedirs(File.dirname(newname)) rescue false + File.copy(oldname,newname) rescue false + end + + def File.silentrename(oldname,newname) + # in case of troubles, we just copy the file; we + # maybe working over multiple file systems or + # apps may have mildly locked files (like gs does) + return if File.expand_path(oldname) == File.expand_path(newname) + File.delete(newname) rescue false + begin + File.rename(oldname,newname) + rescue + FileUtils.makedirs(File.dirname(newname)) rescue false + File.copy(oldname,newname) rescue false + end + end + +end + +class File + + # handles "c:\tmp\test.tex" as well as "/${TEMP}/test.tex") + + def File.unixfied(filename) + begin + str = filename.gsub(/\$\{*([a-z0-9\_]+)\}*/oi) do + if ENV.key?($1) then ENV[$1] else $1 end + end + str.gsub(/[\/\\]+/o, '/') + rescue + filename + end + end + +end + diff --git a/scripts/context/ruby/base/kpse.rb b/scripts/context/ruby/base/kpse.rb new file mode 100644 index 000000000..0f9868784 --- /dev/null +++ b/scripts/context/ruby/base/kpse.rb @@ -0,0 +1,389 @@ +# module : base/kpse +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# rename this one to environment +# +# todo: web2c vs miktex module and include in kpse + +require 'rbconfig' +require 'fileutils' + +# beware $engine is lowercase in kpse +# +# miktex has mem|fmt|base paths + +class String + + def split_path + if self =~ /\;/o || self =~ /^[a-z]\:/io then + self.split(";") + else + self.split(":") + end + end + + def sane_path + self.gsub(/\\/,'/') + end + +end + +class Array + + def join_path + self.join(File::PATH_SEPARATOR) + end + + def non_empty + self.delete_if do |i| + (i == nil || i.empty?) rescue false + end + end + +end + +module Kpse + + @@located = Hash.new + @@paths = Hash.new + @@scripts = Hash.new + @@formats = ['tex','texmfscripts','other text files'] + @@progname = 'context' + @@ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') + @@problems = false + @@tracing = false + @@distribution = 'web2c' + @@crossover = true + @@mswindows = Config::CONFIG['host_os'] =~ /mswin/ + + # @@distribution = 'miktex' if ENV['PATH'] =~ /miktex[\\\/]bin/o + + # if ENV['PATH'] =~ /(.*?)miktex[\\\/]bin/i then + # @@distribution = 'miktex' unless $1 =~ /(texmf\-mswin[\/\\]bin|bin[\/\\]win32)/i + # end + + if @@mswindows && (ENV['PATH'] =~ /(.*?)miktex[\\\/]bin/i) then + @@distribution = 'miktex' unless $1 =~ /(texmf\-mswin[\/\\]bin|bin[\/\\]win32)/i + end + + @@re_true = /yes|on|true|1/i + + if (ENV['KPSEFAST'] =~ @@re_true) || (ENV['CTXMINIMAL'] =~ @@re_true) then + @@usekpserunner = true + require 'base/kpsefast' + require 'base/kpserunner' + else + @@usekpserunner = false + end + + if @@crossover then + ENV.keys.each do |k| + case k + when /\_CTX\_KPSE\_V\_(.*?)\_/io then @@located[$1] = ENV[k].dup + when /\_CTX\_KPSE\_P\_(.*?)\_/io then @@paths [$1] = ENV[k].dup.split(';') + when /\_CTX\_KPSE\_S\_(.*?)\_/io then @@scripts[$1] = ENV[k].dup + end + end + end + + def Kpse.distribution + @@distribution + end + + def Kpse.miktex? + @@distribution == 'miktex' + end + + def Kpse.web2c? + @@distribution == 'web2c' + end + + def Kpse.inspect + @@located.keys.sort.each do |k| puts("located : #{k} -> #{@@located[k]}\n") end + @@paths .keys.sort.each do |k| puts("paths : #{k} -> #{@@paths [k]}\n") end + @@scripts.keys.sort.each do |k| puts("scripts : #{k} -> #{@@scripts[k]}\n") end + end + + def Kpse.used_path(varname) + begin + if @@mswindows then + path = run("--expand-path=\$#{varname}") rescue '' + else + path = run("--expand-path='$#{varname}'") rescue '' + end + rescue + path = '' + end + return path.sane_path + end + + def Kpse.found(filename, progname=nil, format=nil) + begin + tag = Kpse.key(filename) # all + if @@located.key?(tag) then + return @@located[tag].sane_path + elsif FileTest.file?(filename) then + setvariable(tag,filename) + return filename + elsif FileTest.file?(File.join(@@ownpath,filename)) then + setvariable(tag,File.join(@@ownpath,filename)) + return @@located[tag].sane_path + else + [progname,@@progname].flatten.compact.uniq.each do |prg| + [format,@@formats].flatten.compact.uniq.each do |fmt| + begin + tag = Kpse.key(filename,prg,fmt) + if @@located.key?(tag) then + return @@located[tag].sane_path + elsif p = Kpse.kpsewhich(filename,prg,fmt) then + setvariable(tag,p.chomp) + return @@located[tag].sane_path + end + rescue + end + end + end + setvariable(tag,filename) + return filename.sane_path + end + rescue + filename.sane_path + end + end + + def Kpse.kpsewhich(filename,progname,format) + p = if progname && ! progname.empty? then "-progname=#{progname}" else '' end + f = if format && ! format.empty? then "-format=\"#{format}\"" else '' end + Kpse.run("#{p} #{f} #{filename}") + end + + def Kpse.which + Kpse.kpsewhich + end + + def Kpse.run(arguments) + puts arguments if @@tracing + begin + if @@problems then + results = '' + elsif @@usekpserunner then + results = KpseRunner.kpsewhich(arguments).chomp + else + results = `kpsewhich #{arguments}`.chomp + end + rescue + puts "unable to run kpsewhich" if @@tracing + @@problems, results = true, '' + end + puts results if @@tracing + return results + end + + + def Kpse.formatpaths + # maybe we should check for writeability + unless @@paths.key?('formatpaths') then + begin + setpath('formatpaths',run("--show-path=fmt").sane_path.split_path) + rescue + setpath('formatpaths',[]) + end + end + return @@paths['formatpaths'] + end + + def Kpse.key(filename='',progname='all',format='all') + [progname,format,filename].join('-') + end + + def Kpse.formatpath(engine='pdftex',enginepath=true) + + # because engine support in distributions is not always + # as we expect, we need to check for it; + + # todo: miktex + + if miktex? then + return '.' + else + unless @@paths.key?(engine) then + # savedengine = ENV['engine'] + if ENV['TEXFORMATS'] && ! ENV['TEXFORMATS'].empty? then + # make sure that we have a lowercase entry + ENV['TEXFORMATS'] = ENV['TEXFORMATS'].sub(/\$engine/io,"\$engine") + # well, we will append anyway, so we could also strip it + # ENV['TEXFORMATS'] = ENV['TEXFORMATS'].sub(/\$engine/io,"") + end + # use modern method + if enginepath then + formatpath = run("--engine=#{engine} --show-path=fmt") + else + # ENV['engine'] = engine if engine + formatpath = run("--show-path=fmt") + end + # use ancient method + if formatpath.empty? then + if enginepath then + if @@mswindows then + formatpath = run("--engine=#{engine} --expand-path=\$TEXFORMATS") + else + formatpath = run("--engine=#{engine} --expand-path=\\\$TEXFORMATS") + end + end + # either no enginepath or failed run + if formatpath.empty? then + if @@mswindows then + formatpath = run("--expand-path=\$TEXFORMATS") + else + formatpath = run("--expand-path=\\\$TEXFORMATS") + end + end + end + # locate writable path + if ! formatpath.empty? then + formatpaths, done = formatpath.split_path, false + formatpaths.collect! do |fp| + fp.gsub!(/\\/o,'/') + fp.gsub!(/\/\/$/o,'/') + # remove funny patterns + fp.sub!(/^!!/o,'') + fp.sub!(/\/+$/o,'') + fp.sub!(/(unsetengine|unset)/o,if enginepath then engine else '' end) + fp + end + formatpaths.delete_if do |fp| + fp.empty? || fp == '.' + end + # the engine path may not yet be present, find first writable + formatpaths.each do |fp| + # strip (possible engine) and test for writeability + fpp = fp.sub(/#{engine}\/*$/o,'') + if FileTest.directory?(fpp) && FileTest.writable?(fpp) then + # use this path + formatpath, done = fp.dup, true + break + end + end + unless done then + formatpaths.each do |fp| + fpp = fp.sub(/#{engine}\/*$/o,'') + FileUtils.makedirs(fpp) rescue false # maybe we don't have an path yet + if FileTest.directory?(fpp) && FileTest.writable?(fpp) then + # use this path + formatpath, done = fp.dup, true + break + end + end + end + unless done then + formatpath = '.' + end + end + # needed ! + FileUtils.makedirs(formatpath) rescue false + # fall back to current path + formatpath = '.' if formatpath.empty? || ! FileTest.writable?(formatpath) + # append engine but prevent duplicates + formatpath = File.join(formatpath.sub(/\/*#{engine}\/*$/,''), engine) if enginepath + FileUtils.makedirs(formatpath) rescue false + setpath(engine,formatpath) + # ENV['engine'] = savedengine + end + return @@paths[engine].first + end + end + + def Kpse.update + system('initexmf -u') if Kpse.miktex? + system('mktexlsr') + end + + # engine support is either broken of not implemented in some + # distributions, so we need to take care of it ourselves (without + # delays due to kpse calls); there can be many paths in the string + # + # in a year or so, i will drop this check + + def Kpse.fixtexmfvars(engine=nil) + ENV['ENGINE'] = engine if engine + texformats = if ENV['TEXFORMATS'] then ENV['TEXFORMATS'].dup else '' end + if texformats.empty? then + if engine then + if @@mswindows then + texformats = `kpsewhich --engine=#{engine} --expand-var=\$TEXFORMATS`.chomp + else + texformats = `kpsewhich --engine=#{engine} --expand-var=\\\$TEXFORMATS`.chomp + end + else + if @@mswindows then + texformats = `kpsewhich --expand-var=\$TEXFORMATS`.chomp + else + texformats = `kpsewhich --expand-var=\\\$TEXFORMATS`.chomp + end + end + end + if engine then + texformats.sub!(/unsetengine/,engine) + else + texformats.sub!(/unsetengine/,"\$engine") + end + if engine && (texformats =~ /web2c[\/\\].*#{engine}/o) then + # ok, engine is seen + return false + elsif texformats =~ /web2c[\/\\].*\$engine/io then + # shouldn't happen + return false + else + ENV['TEXFORMATS'] = texformats.gsub(/(web2c\/\{)(,\})/o) do + "#{$1}\$engine#{$2}" + end + if texformats !~ /web2c[\/\\].*\$engine/io then + ENV['TEXFORMATS'] = texformats.gsub(/web2c\/*/, "web2c/{\$engine,}") + end + return true + end + end + + def Kpse.runscript(name,filename=[],options=[]) + setscript(name,`texmfstart --locate #{name}`) unless @@scripts.key?(name) + cmd = "#{@@scripts[name]} #{[options].flatten.join(' ')} #{[filename].flatten.join(' ')}" + system(cmd) + end + + def Kpse.pipescript(name,filename=[],options=[]) + setscript(name,`texmfstart --locate #{name}`) unless @@scripts.key?(name) + cmd = "#{@@scripts[name]} #{[options].flatten.join(' ')} #{[filename].flatten.join(' ')}" + `#{cmd}` + end + + def Kpse.searchmethod + if @@usekpserunner then 'kpsefast' else 'kpsewhich' end + end + + private + + def Kpse.setvariable(key,value) + @@located[key] = value + ENV["_CTX_K_V_#{key}_"] = @@located[key] if @@crossover + end + + def Kpse.setscript(key,value) + @@scripts[key] = value + ENV["_CTX_K_S_#{key}_"] = @@scripts[key] if @@crossover + end + + def Kpse.setpath(key,value) + @@paths[key] = [value].flatten.uniq.collect do |p| + p.sub(/^!!/,'').sub(/\/*$/,'') + end + ENV["_CTX_K_P_#{key}_"] = @@paths[key].join(';') if @@crossover + end + +end diff --git a/scripts/context/ruby/base/kpse/drb.rb b/scripts/context/ruby/base/kpse/drb.rb new file mode 100644 index 000000000..db1ce0eec --- /dev/null +++ b/scripts/context/ruby/base/kpse/drb.rb @@ -0,0 +1,57 @@ +require 'drb' +require 'base/kpse/trees' + +class KpseServer + + attr_accessor :port + + def initialize(port=7000) + @port = port + end + + def start + puts "starting drb service at port #{@port}" + DRb.start_service("druby://localhost:#{@port}", KpseTrees.new) + trap(:INT) do + DRb.stop_service + end + DRb.thread.join + end + + def stop + # todo + end + +end + +class KpseClient + + attr_accessor :port + + def initialize(port=7000) + @port = port + @kpse = nil + end + + def start + # only needed when callbacks are used / slow, due to Socket::getaddrinfo + # DRb.start_service + end + + def object + @kpse = DRbObject.new(nil,"druby://localhost:#{@port}") + end + +end + + +# SERVER_URI="druby://localhost:8787" +# +# # Start a local DRbServer to handle callbacks. +# # +# # Not necessary for this small example, but will be required +# # as soon as we pass a non-marshallable object as an argument +# # to a dRuby call. +# DRb.start_service +# +# timeserver = DRbObject.new_with_uri(SERVER_URI) diff --git a/scripts/context/ruby/base/kpse/soap.rb b/scripts/context/ruby/base/kpse/soap.rb new file mode 100644 index 000000000..c9ed75c44 --- /dev/null +++ b/scripts/context/ruby/base/kpse/soap.rb @@ -0,0 +1,79 @@ +require 'soap/rpc/standaloneServer' +require 'soap/rpc/driver' + +require 'base/kpse/trees' + +class KpseService < SOAP::RPC::StandaloneServer + + def on_init + kpse = KpseTrees.new + add_method(kpse, 'choose', 'files', 'environment') + add_method(kpse, 'load', 'files', 'environment') + add_method(kpse, 'expand_variables', 'tree') + add_method(kpse, 'expand_braces', 'tree', 'str') + add_method(kpse, 'expand_path', 'tree', 'str') + add_method(kpse, 'expand_var', 'tree', 'str') + add_method(kpse, 'show_path', 'tree', 'str') + add_method(kpse, 'var_value', 'tree', 'str') + add_method(kpse, 'find_file', 'tree', 'filename') + add_method(kpse, 'find_files', 'tree', 'filename', 'first') + end + +end + +class KpseServer + + @@url = 'http://kpse.thismachine.org/KpseService' + + attr_accessor :port + + def initialize(port=7000) + @port = port + @server = nil + end + + def start + puts "starting soap service at port #{@port}" + @server = KpseService.new('KpseServer', @@url, '0.0.0.0', @port.to_i) + trap(:INT) do + @server.shutdown + end + status = @server.start + end + + def stop + @server.shutdown rescue false + end + +end + +class KpseClient + + @@url = 'http://kpse.thismachine.org/KpseService' + + attr_accessor :port + + def initialize(port=7000) + @port = port + @kpse = nil + end + + def start + @kpse = SOAP::RPC::Driver.new("http://localhost:#{port}/", @@url) + @kpse.add_method('choose','files', 'environment') + @kpse.add_method('load','files', 'environment') + @kpse.add_method('expand_variables', 'tree') + @kpse.add_method('expand_braces', 'tree', 'str') + @kpse.add_method('expand_path', 'tree', 'str') + @kpse.add_method('expand_var', 'tree', 'str') + @kpse.add_method('show_path', 'tree', 'str') + @kpse.add_method('var_value', 'tree', 'str') + @kpse.add_method('find_file', 'tree', 'filename') + @kpse.add_method('find_files', 'tree', 'filename', 'first') + end + + def object + @kpse + end + +end diff --git a/scripts/context/ruby/base/kpse/trees.rb b/scripts/context/ruby/base/kpse/trees.rb new file mode 100644 index 000000000..9c872eb18 --- /dev/null +++ b/scripts/context/ruby/base/kpse/trees.rb @@ -0,0 +1,84 @@ +require 'monitor' +require 'base/kpsefast' + +class KpseTrees < Monitor + + def initialize + @trees = Hash.new + end + + def pattern(filenames) + filenames.join('|').gsub(/\\+/o,'/').downcase + end + + def choose(filenames,environment) + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "enabling tree #{current}" + current + end + + def fetch(filenames,environment) # will send whole object ! + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "fetching tree #{current}" + @trees[current] + end + + def load(filenames,environment) + current = pattern(filenames) + puts "loading tree #{current}" + @trees[current] = KpseFast.new + @trees[current].push_environment(environment) + @trees[current].load_cnf(filenames) + @trees[current].expand_variables + @trees[current].load_lsr + end + + def set(tree,key,value) + case key + when 'progname' then @trees[tree].progname = value + when 'engine' then @trees[tree].engine = value + when 'format' then @trees[tree].format = value + end + end + def get(tree,key) + case key + when 'progname' then @trees[tree].progname + when 'engine' then @trees[tree].engine + when 'format' then @trees[tree].format + end + end + + def load_cnf(tree) + @trees[tree].load_cnf + end + def load_lsr(tree) + @trees[tree].load_lsr + end + def expand_variables(tree) + @trees[tree].expand_variables + end + def expand_braces(tree,str) + @trees[tree].expand_braces(str) + end + def expand_path(tree,str) + @trees[tree].expand_path(str) + end + def expand_var(tree,str) + @trees[tree].expand_var(str) + end + def show_path(tree,str) + @trees[tree].show_path(str) + end + def var_value(tree,str) + @trees[tree].var_value(str) + end + def find_file(tree,filename) + @trees[tree].find_file(filename) + end + def find_files(tree,filename,first) + @trees[tree].find_files(filename,first) + end + +end diff --git a/scripts/context/ruby/base/kpsedirect.rb b/scripts/context/ruby/base/kpsedirect.rb new file mode 100644 index 000000000..6fa8c8601 --- /dev/null +++ b/scripts/context/ruby/base/kpsedirect.rb @@ -0,0 +1,34 @@ +class KpseDirect + + attr_accessor :progname, :format, :engine + + def initialize + @progname, @format, @engine = '', '', '' + end + + def expand_path(str) + clean_name(`kpsewhich -expand-path=#{str}`.chomp) + end + + def expand_var(str) + clean_name(`kpsewhich -expand-var=#{str}`.chomp) + end + + def find_file(str) + clean_name(`kpsewhich #{_progname_} #{_format_} #{str}`.chomp) + end + + def _progname_ + if @progname.empty? then '' else "-progname=#{@progname}" end + end + def _format_ + if @format.empty? then '' else "-format=\"#{@format}\"" end + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end diff --git a/scripts/context/ruby/base/kpsefast.rb b/scripts/context/ruby/base/kpsefast.rb new file mode 100644 index 000000000..8a9f89593 --- /dev/null +++ b/scripts/context/ruby/base/kpsefast.rb @@ -0,0 +1,934 @@ +# module : base/kpsefast +# copyright : PRAGMA Advanced Document Engineering +# version : 2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# todo: multiple cnf files +# +# todo: cleanup, string or table store (as in lua variant) + +class String + + def split_path + if self =~ /\;/o || self =~ /^[a-z]\:/io then + self.split(";") + else + self.split(":") + end + end + +end + +class Array + + def join_path + self.join(File::PATH_SEPARATOR) + end + +end + +class File + + def File.locate_file(path,name) + begin + files = Dir.entries(path) + if files.include?(name) then + fullname = File.join(path,name) + return fullname if FileTest.file?(fullname) + end + files.each do |p| + fullname = File.join(path,p) + if p != '.' and p != '..' and FileTest.directory?(fullname) and result = locate_file(fullname,name) then + return result + end + end + rescue + # bad path + end + return nil + end + + def File.glob_file(pattern) + return Dir.glob(pattern).first + end + +end + +module KpseUtil + + # to be adapted, see loading cnf file + + @@texmftrees = ['texmf-local','texmf.local','../..','texmf'] # '../..' is for gwtex + @@texmfcnf = 'texmf.cnf' + + def KpseUtil::identify + # we mainly need to identify the local tex stuff and wse assume that + # the texmfcnf variable is set; otherwise we need to expand the + # TEXMF variable and that takes time since it may involve more + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + ENV['SELFAUTOPARENT'] = ownpath + else + ENV['SELFAUTOPARENT'] = '.' # fall back + # may be too tricky: + # + # (ENV['PATH'] ||'').split_path.each do |p| + # if p.gsub!(/texmf.*?$/o, '') then + # ENV['SELFAUTOPARENT'] = p + # break + # end + # end + end + filenames = Array.new + if ENV['TEXMFCNF'] && ! ENV['TEXMFCNF'].empty? then + ENV['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif ENV['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + @@texmftrees.each do |tree| + filenames << File.join(ENV['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end + end + loop do + busy = false + filenames.collect! do |f| + f.gsub(/\$([a-zA-Z0-9\_\-]+)/o) do + if (! ENV[$1]) || (ENV[$1] == $1) then + "$#{$1}" + else + busy = true + ENV[$1] + end + end + end + break unless busy + end + filenames.delete_if do |f| + ! FileTest.file?(f) + end + return filenames + end + + def KpseUtil::environment + Hash.new.merge(ENV) + end + +end + +class KpseFast + + # formats are an incredible inconsistent mess + + @@suffixes = Hash.new + @@formats = Hash.new + @@suffixmap = Hash.new + + @@texmfcnf = 'texmf.cnf' + + @@suffixes['gf'] = ['.gf'] # todo + @@suffixes['pk'] = ['.pk'] # todo + @@suffixes['tfm'] = ['.tfm'] + @@suffixes['afm'] = ['.afm'] + @@suffixes['base'] = ['.base'] + @@suffixes['bib'] = ['.bib'] + @@suffixes['bst'] = ['.bst'] + @@suffixes['cnf'] = ['.cnf'] + @@suffixes['ls-R'] = ['ls-R', 'ls-r'] + @@suffixes['fmt'] = ['.fmt', '.efmt', '.efm', '.ofmt', '.ofm', '.oft', '.eofmt', '.eoft', '.eof', '.pfmt', '.pfm', '.epfmt', '.epf', '.xpfmt', '.xpf', '.afmt', '.afm'] + @@suffixes['map'] = ['.map'] + @@suffixes['mem'] = ['.mem'] + @@suffixes['mf'] = ['.mf'] + @@suffixes['mfpool'] = ['.pool'] + @@suffixes['mft'] = ['.mft'] + @@suffixes['mp'] = ['.mp'] + @@suffixes['mppool'] = ['.pool'] + @@suffixes['ocp'] = ['.ocp'] + @@suffixes['ofm'] = ['.ofm', '.tfm'] + @@suffixes['opl'] = ['.opl'] + @@suffixes['otp'] = ['.otp'] + @@suffixes['ovf'] = ['.ovf'] + @@suffixes['ovp'] = ['.ovp'] + @@suffixes['graphic/figure'] = ['.eps', '.epsi'] + @@suffixes['tex'] = ['.tex'] + @@suffixes['texpool'] = ['.pool'] + @@suffixes['PostScript header'] = ['.pro'] + @@suffixes['type1 fonts'] = ['.pfa', '.pfb'] + @@suffixes['vf'] = ['.vf'] + @@suffixes['ist'] = ['.ist'] + @@suffixes['truetype fonts'] = ['.ttf', '.ttc'] + @@suffixes['web'] = ['.web', '.ch'] + @@suffixes['cweb'] = ['.w', '.web', '.ch'] + @@suffixes['enc files'] = ['.enc'] + @@suffixes['cmap files'] = ['.cmap'] + @@suffixes['subfont definition files'] = ['.sfd'] + @@suffixes['lig files'] = ['.lig'] + @@suffixes['bitmap font'] = [] + @@suffixes['MetaPost support'] = [] + @@suffixes['TeX system documentation'] = [] + @@suffixes['TeX system sources'] = [] + @@suffixes['Troff fonts'] = [] + @@suffixes['dvips config'] = [] + @@suffixes['type42 fonts'] = [] + @@suffixes['web2c files'] = [] + @@suffixes['other text files'] = [] + @@suffixes['other binary files'] = [] + @@suffixes['misc fonts'] = [] + @@suffixes['opentype fonts'] = [] + @@suffixes['pdftex config'] = [] + @@suffixes['texmfscripts'] = [] + + # replacements + + @@suffixes['fmt'] = ['.fmt'] + @@suffixes['type1 fonts'] = ['.pfa', '.pfb', '.pfm'] + @@suffixes['tex'] = ['.tex', '.xml'] + @@suffixes['texmfscripts'] = ['rb','lua','py','pl'] + + @@suffixes.keys.each do |k| @@suffixes[k].each do |s| @@suffixmap[s] = k end end + + # TTF2TFMINPUTS + # MISCFONTS + # TEXCONFIG + # DVIPDFMINPUTS + # OTFFONTS + + @@formats['gf'] = '' + @@formats['pk'] = '' + @@formats['tfm'] = 'TFMFONTS' + @@formats['afm'] = 'AFMFONTS' + @@formats['base'] = 'MFBASES' + @@formats['bib'] = '' + @@formats['bst'] = '' + @@formats['cnf'] = '' + @@formats['ls-R'] = '' + @@formats['fmt'] = 'TEXFORMATS' + @@formats['map'] = 'TEXFONTMAPS' + @@formats['mem'] = 'MPMEMS' + @@formats['mf'] = 'MFINPUTS' + @@formats['mfpool'] = 'MFPOOL' + @@formats['mft'] = '' + @@formats['mp'] = 'MPINPUTS' + @@formats['mppool'] = 'MPPOOL' + @@formats['ocp'] = 'OCPINPUTS' + @@formats['ofm'] = 'OFMFONTS' + @@formats['opl'] = 'OPLFONTS' + @@formats['otp'] = 'OTPINPUTS' + @@formats['ovf'] = 'OVFFONTS' + @@formats['ovp'] = 'OVPFONTS' + @@formats['graphic/figure'] = '' + @@formats['tex'] = 'TEXINPUTS' + @@formats['texpool'] = 'TEXPOOL' + @@formats['PostScript header'] = 'TEXPSHEADERS' + @@formats['type1 fonts'] = 'T1FONTS' + @@formats['vf'] = 'VFFONTS' + @@formats['ist'] = '' + @@formats['truetype fonts'] = 'TTFONTS' + @@formats['web'] = '' + @@formats['cweb'] = '' + @@formats['enc files'] = 'ENCFONTS' + @@formats['cmap files'] = 'CMAPFONTS' + @@formats['subfont definition files'] = 'SFDFONTS' + @@formats['lig files'] = 'LIGFONTS' + @@formats['bitmap font'] = '' + @@formats['MetaPost support'] = '' + @@formats['TeX system documentation'] = '' + @@formats['TeX system sources'] = '' + @@formats['Troff fonts'] = '' + @@formats['dvips config'] = '' + @@formats['type42 fonts'] = 'T42FONTS' + @@formats['web2c files'] = 'WEB2C' + @@formats['other text files'] = '' + @@formats['other binary files'] = '' + @@formats['misc fonts'] = '' + @@formats['opentype fonts'] = 'OPENTYPEFONTS' + @@formats['pdftex config'] = 'PDFTEXCONFIG' + @@formats['texmfscripts'] = 'TEXMFSCRIPTS' + + attr_accessor :progname, :engine, :format, :rootpath, :treepath, + :verbose, :remember, :scandisk, :diskcache, :renewcache + + @@cacheversion = '1' + + def initialize + @rootpath = '' + @treepath = '' + @progname = 'kpsewhich' + @engine = 'pdftex' + @variables = Hash.new + @expansions = Hash.new + @files = Hash.new + @found = Hash.new + @kpsevars = Hash.new + @lsrfiles = Array.new + @cnffiles = Array.new + @verbose = true + @remember = true + @scandisk = true + @diskcache = true + @renewcache = false + @isolate = false + + @diskcache = false + @cachepath = nil + @cachefile = 'tmftools.log' + + @environment = ENV + end + + def set(key,value) + case key + when 'progname' then @progname = value + when 'engine' then @engine = value + when 'format' then @format = value + end + end + + def push_environment(env) + @environment = env + end + + # {$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c} + # + # $SELFAUTOLOC : /usr/tex/bin/platform + # $SELFAUTODIR : /usr/tex/bin + # $SELFAUTOPARENT : /usr/tex + # + # since we live in scriptpath we need a slightly different method + + def load_cnf(filenames=nil) + unless filenames then + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + @environment['SELFAUTOPARENT'] = ownpath + else + @environment['SELFAUTOPARENT'] = '.' + end + unless @treepath.empty? then + unless @rootpath.empty? then + @treepath = @treepath.split(',').collect do |p| File.join(@rootpath,p) end.join(',') + end + @environment['TEXMF'] = @treepath + # only the first one + @environment['TEXMFCNF'] = File.join(@treepath.split(',').first,'texmf/web2c') + end + unless @rootpath.empty? then + @environment['TEXMFCNF'] = File.join(@rootpath,'texmf/web2c') + @environment['SELFAUTOPARENT'] = @rootpath + @isolate = true + end + filenames = Array.new + if @environment['TEXMFCNF'] and not @environment['TEXMFCNF'].empty? then + @environment['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif @environment['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + ['texmf-local','texmf'].each do |tree| + filenames << File.join(@environment['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end + end + end + # /texmf/web2c/texmf.cnf + filenames = _expanded_path_(filenames) + @rootpath = filenames.first + 3.times do + @rootpath = File.dirname(@rootpath) + end + filenames.collect! do |f| + f.gsub("\\", '/') + end + filenames.each do |fname| + if FileTest.file?(fname) and f = File.open(fname) then + @cnffiles << fname + while line = f.gets do + loop do + # concatenate lines ending with \ + break unless line.sub!(/\\\s*$/o) do + f.gets || '' + end + end + case line + when /^[\%\#]/o then + # comment + when /^\s*(.*?)\s*\=\s*(.*?)\s*$/o then + key, value = $1, $2 + unless @variables.key?(key) then + value.sub!(/\%.*$/,'') + value.sub!(/\~/, "$HOME") + @variables[key] = value + end + @kpsevars[key] = true + end + end + f.close + end + end + end + + def load_lsr + @lsrfiles = [] + simplified_list(expansion('TEXMF')).each do |p| + ['ls-R','ls-r'].each do |f| + filename = File.join(p,f) + if FileTest.file?(filename) then + @lsrfiles << [filename,File.size(filename)] + break + end + end + end + @files = Hash.new + if @diskcache then + ['HOME','TEMP','TMP','TMPDIR'].each do |key| + if @environment[key] then + if FileTest.directory?(@environment[key]) then + @cachepath = @environment[key] + @cachefile = [@rootpath.gsub(/[^A-Z0-9]/io, '-').gsub(/\-+/,'-'),File.basename(@cachefile)].join('-') + break + end + end + end + if @cachepath and not @renewcache and FileTest.file?(File.join(@cachepath,@cachefile)) then + begin + if f = File.open(File.join(@cachepath,@cachefile)) then + cacheversion = Marshal.load(f) + if cacheversion == @@cacheversion then + lsrfiles = Marshal.load(f) + if lsrfiles == @lsrfiles then + @files = Marshal.load(f) + end + end + f.close + end + rescue + @files = Hash.new + end + end + end + return if @files.size > 0 + @lsrfiles.each do |filedata| + filename, filesize = filedata + filepath = File.dirname(filename) + begin + path = '.' + data = IO.readlines(filename) + if data[0].chomp =~ /% ls\-R \-\- filename database for kpathsea\; do not change this line\./io then + data.each do |line| + case line + when /^[a-zA-Z0-9]/o then + line.chomp! + if @files[line] then + @files[line] << path + else + @files[line] = [path] + end + when /^\.\/(.*?)\:$/o then + path = File.join(filepath,$1) + end + end + end + rescue + # sorry + end + end + if @diskcache and @cachepath and f = File.open(File.join(@cachepath,@cachefile),'wb') then + f << Marshal.dump(@@cacheversion) + f << Marshal.dump(@lsrfiles) + f << Marshal.dump(@files) + f.close + end + end + + def expand_variables + @expansions = Hash.new + if @isolate then + @variables['TEXMFCNF'] = @environment['TEXMFCNF'].dup + @variables['SELFAUTOPARENT'] = @environment['SELFAUTOPARENT'].dup + else + @environment.keys.each do |e| + if e =~ /^([a-zA-Z]+)\_(.*)\s*$/o then + @expansions["#{$1}.#{$2}"] = (@environment[e] ||'').dup + else + @expansions[e] = (@environment[e] ||'').dup + end + end + end + @variables.keys.each do |k| + @expansions[k] = @variables[k].dup unless @expansions[k] + end + loop do + busy = false + @expansions.keys.each do |k| + @expansions[k].gsub!(/\$([a-zA-Z0-9\_\-]*)/o) do + busy = true + @expansions[$1] || '' + end + @expansions[k].gsub!(/\$\{([a-zA-Z0-9\_\-]*)\}/o) do + busy = true + @expansions[$1] || '' + end + end + break unless busy + end + @expansions.keys.each do |k| + @expansions[k] = @expansions[k].gsub("\\", '/') + end + end + + def variable(name='') + (name and not name.empty? and @variables[name.sub('$','')]) or '' + end + + def expansion(name='') + (name and not name.empty? and @expansions[name.sub('$','')]) or '' + end + + def variable?(name='') + name and not name.empty? and @variables.key?(name.sub('$','')) + end + + def expansion?(name='') + name and not name.empty? and @expansions.key?(name.sub('$','')) + end + + def simplified_list(str) + lst = str.gsub(/^\{/o,'').gsub(/\}$/o,'').split(",") + lst.collect do |l| + l.sub(/^[\!]*/,'').sub(/[\/\\]*$/o,'') + end + end + + def original_variable(variable) + if variable?("#{@progname}.#{variable}") then + variable("#{@progname}.#{variable}") + elsif variable?(variable) then + variable(variable) + else + '' + end + end + + def expanded_variable(variable) + if expansion?("#{variable}.#{@progname}") then + expansion("#{variable}.#{@progname}") + elsif expansion?(variable) then + expansion(variable) + else + '' + end + end + + def original_path(filename='') + _expanded_path_(original_variable(var_of_format_or_suffix(filename)).split(";")) + end + + def expanded_path(filename='') + _expanded_path_(expanded_variable(var_of_format_or_suffix(filename)).split(";")) + end + + def _expanded_path_(pathlist) + i, n = 0, 0 + pathlist.collect! do |mainpath| + mainpath.gsub(/([\{\}])/o) do + if $1 == "{" then + i += 1 ; n = i if i > n ; "<#{i}>" + else + i -= 1 ; "" + end + end + end + n.times do |i| + loop do + more = false + newlist = [] + pathlist.each do |path| + unless path.sub!(/^(.*?)<(#{n-i})>(.*?)<\/\2>(.*?)$/) do + pre, mid, post = $1, $3, $4 + mid.gsub!(/\,$/,',.') + mid.split(',').each do |m| + more = true + if m == '.' then + newlist << "#{pre}#{post}" + else + newlist << "#{pre}#{m}#{post}" + end + end + end then + newlist << path + end + end + if more then + pathlist = [newlist].flatten # copy -) + else + break + end + end + end + pathlist = pathlist.uniq.collect do |path| + p = path + # p.gsub(/^\/+/o) do '' end + # p.gsub!(/(.)\/\/(.)/o) do "#{$1}/#{$2}" end + # p.gsub!(/\/\/+$/o) do '//' end + p.gsub!(/\/\/+/o) do '//' end + p + end + pathlist + end + + # todo: ignore case + + def var_of_format(str) + @@formats[str] || '' + end + + def var_of_suffix(str) # includes . + if @@suffixmap.key?(str) then @@formats[@@suffixmap[str]] else '' end + end + + def var_of_format_or_suffix(str) + if @@formats.key?(str) then + @@formats[str] + elsif @@suffixmap.key?(File.extname(str)) then # extname includes . + @@formats[@@suffixmap[File.extname(str)]] # extname includes . + else + '' + end + end + +end + +class KpseFast + + # test things + + def list_variables(kpseonly=true) + @variables.keys.sort.each do |k| + if kpseonly then + puts("#{k} = #{@variables[k]}") if @kpsevars[k] + else + puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@variables[k]}") + end + end + end + + def list_expansions(kpseonly=true) + @expansions.keys.sort.each do |k| + if kpseonly then + puts("#{k} = #{@expansions[k]}") if @kpsevars[k] + else + puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@expansions[k]}") + end + end + end + + def list_lsr + puts("files = #{@files.size}") + end + + def set_test_patterns + @variables["KPSE_TEST_PATTERN_A"] = "foo/{1,2}/bar//" + @variables["KPSE_TEST_PATTERN_B"] = "!!x{A,B{1,2}}y" + @variables["KPSE_TEST_PATTERN_C"] = "x{A,B//{1,2}}y" + @variables["KPSE_TEST_PATTERN_D"] = "x{A,B//{1,2,}}//y" + end + + def show_test_patterns + ['A','B','D'].each do |i| + puts "" + puts @variables ["KPSE_TEST_PATTERN_#{i}"] + puts "" + puts expand_path("KPSE_TEST_PATTERN_#{i}").split_path + puts "" + end + end + +end + +class KpseFast + + # kpse stuff + + def expand_braces(str) # output variable and brace expansion of STRING. + _expanded_path_(original_variable(str).split_path).join_path + end + + def expand_path(str) # output complete path expansion of STRING. + _expanded_path_(expanded_variable(str).split_path).join_path + end + + def expand_var(str) # output variable expansion of STRING. + expanded_variable(str) + end + + def show_path(str) # output search path for file type NAME + expanded_path(str).join_path + end + + def var_value(str) # output the value of variable $STRING. + original_variable(str) + end + +end + +class KpseFast + + def _is_cnf_?(filename) + filename == File.basename((@cnffiles.first rescue @@texmfcnf) || @@texmfcnf) + end + + def find_file(filename) + if _is_cnf_?(filename) then + @cnffiles.first rescue '' + else + [find_files(filename,true)].flatten.first || '' + end + end + + def find_files(filename,first=false) + if _is_cnf_?(filename) then + result = @cnffiles.dup + else + if @remember then + # stamp = "#{filename}--#{@format}--#{@engine}--#{@progname}" + stamp = "#{filename}--#{@engine}--#{@progname}" + return @found[stamp] if @found.key?(stamp) + end + pathlist = expanded_path(filename) + result = [] + filelist = if @files.key?(filename) then @files[filename].uniq else nil end + done = false + if pathlist.size == 0 then + if FileTest.file?(filename) then + done = true + result << '.' + end + else + pathlist.each do |path| + doscan = if path =~ /^\!\!/o then false else true end + recurse = if path =~ /\/\/$/o then true else false end + pathname = path.dup + pathname.gsub!(/^\!+/o, '') + done = false + if not done and filelist then + # checking for exact match + if filelist.include?(pathname) then + result << pathname + done = true + end + if not done and recurse then + # checking for fuzzy // + pathname.gsub!(/\/+$/o, '/.*') + # pathname.gsub!(/\/\//o,'/[\/]*/') + pathname.gsub!(/\/\//o,'/.*?/') + re = /^#{pathname}/ + filelist.each do |f| + if re =~ f then + result << f # duplicates will be filtered later + done = true + end + break if done + end + end + end + if not done and doscan then + # checking for path itself + pname = pathname.sub(/\.\*$/,'') + if not pname =~ /\*/o and FileTest.file?(File.join(pname,filename)) then + result << pname + done = true + end + end + break if done and first + end + end + if not done and @scandisk then + pathlist.each do |path| + pathname = path.dup + unless pathname.gsub!(/^\!+/o, '') then # !! prevents scan + recurse = pathname.gsub!(/\/+$/o, '') + complex = pathname.gsub!(/\/\//o,'/*/') + if recurse then + if complex then + if ok = File.glob_file("#{pathname}/**/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif ok = File.locate_file(pathname,filename) then + result << File.dirname(ok) + done = true + end + elsif complex then + if ok = File.glob_file("#{pathname}/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif FileTest.file?(File.join(pathname,filename)) then + result << pathname + done = true + end + break if done and first + end + end + end + result = result.uniq.collect do |pathname| + File.join(pathname,filename) + end + @found[stamp] = result if @remember + end + return result # redundant + end + +end + +class KpseFast + + class FileData + attr_accessor :tag, :name, :size, :date + def initialize(tag=0,name=nil,size=nil,date=nil) + @tag, @name, @size, @date = tag, name, size, date + end + def FileData.sizes(a) + a.collect do |aa| + aa.size + end + end + def report + case @tag + when 1 then "deleted | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 2 then "present | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 3 then "obsolete | #{' '*8} | #{' '*16} | #{@name}" + end + end + end + + def analyze_files(filter='',strict=false,sort='',delete=false) + puts("command line = #{ARGV.join(' ')}") + puts("number of files = #{@files.size}") + puts("filter pattern = #{filter}") + puts("loaded cnf files = #{@cnffiles.join(' ')}") + puts('') + if filter.gsub!(/^not:/,'') then + def the_same(filter,filename) + not filter or filter.empty? or /#{filter}/ !~ filename + end + else + def the_same(filter,filename) + not filter or filter.empty? or /#{filter}/ =~ filename + end + end + @files.keys.each do |name| + if @files[name].size > 1 then + data = Array.new + @files[name].each do |path| + filename = File.join(path,name) + # if not filter or filter.empty? or /#{filter}/ =~ filename then + if the_same(filter,filename) then + if FileTest.file?(filename) then + if delete then + data << FileData.new(1,filename,File.size(filename),File.mtime(filename)) + begin + File.delete(filename) if delete + rescue + end + else + data << FileData.new(2,filename,File.size(filename),File.mtime(filename)) + end + else + # data << FileData.new(3,filename) + end + end + end + if data.length > 1 then + if strict then + # if data.collect do |d| d.size end.uniq! then + # data.sort! do |a,b| b.size <=> a.size end + # data.each do |d| puts d.report end + # puts '' + # end + data.sort! do |a,b| + if a.size and b.size then + b.size <=> a.size + else + 0 + end + end + bunch = Array.new + done = false + data.each do |d| + if bunch.size == 0 then + bunch << d + elsif bunch[0].size == d.size then + bunch << d + else + if bunch.size > 1 then + bunch.each do |b| + puts b.report + end + done = true + end + bunch = [d] + end + end + puts '' if done + else + case sort + when 'size' then data.sort! do |a,b| a.size <=> b.size end + when 'revsize' then data.sort! do |a,b| b.size <=> a.size end + when 'date' then data.sort! do |a,b| a.date <=> b.date end + when 'revdate' then data.sort! do |a,b| b.date <=> a.date end + end + data.each do |d| puts d.report end + puts '' + end + end + end + end + end + +end + +# if false then + + # k = KpseFast.new # (root) + # k.set_test_patterns + # k.load_cnf + # k.expand_variables + # k.load_lsr + + # k.show_test_patterns + + # puts k.list_variables + # puts k.list_expansions + # k.list_lsr + # puts k.expansion("$TEXMF") + # puts k.expanded_path("TEXINPUTS","context") + + # k.progname, k.engine, k.format = 'context', 'pdftex', 'tfm' + # k.scandisk = false # == must_exist + # k.expand_variables + + # 10.times do |i| puts k.find_file('texnansi-lmr10.tfm') end + + # puts "expand braces $TEXMF" + # puts k.expand_braces("$TEXMF") + # puts "expand path $TEXMF" + # puts k.expand_path("$TEXMF") + # puts "expand var $TEXMF" + # puts k.expand_var("$TEXMF") + # puts "expand path $TEXMF" + # puts k.show_path('tfm') + # puts "expand value $TEXINPUTS" + # puts k.var_value("$TEXINPUTS") + # puts "expand value $TEXINPUTS.context" + # puts k.var_value("$TEXINPUTS.context") + + # exit + +# end diff --git a/scripts/context/ruby/base/kpseremote.rb b/scripts/context/ruby/base/kpseremote.rb new file mode 100644 index 000000000..9a73b88b0 --- /dev/null +++ b/scripts/context/ruby/base/kpseremote.rb @@ -0,0 +1,116 @@ +require 'base/kpsefast' + +case ENV['KPSEMETHOD'] + when /soap/o then require 'base/kpse/soap' + when /drb/o then require 'base/kpse/drb' + else require 'base/kpse/drb' +end + +class KpseRemote + + @@port = ENV['KPSEPORT'] || 7000 + @@method = ENV['KPSEMETHOD'] || 'drb' + + def KpseRemote::available? + @@method && @@port + end + + def KpseRemote::start_server(port=nil) + kpse = KpseServer.new(port || @@port) + kpse.start + end + + def KpseRemote::start_client(port=nil) # keeps object in server + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpse = kpseclient.object + tree = kpse.choose(KpseUtil::identify, KpseUtil::environment) + [kpse, tree] + end + + def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil + end + + def initialize(port=nil) + if KpseRemote::available? then + begin + @kpse, @tree = KpseRemote::start_client(port) + rescue + @kpse, @tree = nil, nil + end + else + @kpse, @tree = nil, nil + end + end + + def progname=(value) + @kpse.set(@tree,'progname',value) + end + def format=(value) + @kpse.set(@tree,'format',value) + end + def engine=(value) + @kpse.set(@tree,'engine',value) + end + + def progname + @kpse.get(@tree,'progname') + end + def format + @kpse.get(@tree,'format') + end + def engine + @kpse.get(@tree,'engine') + end + + def load + @kpse.load(KpseUtil::identify, KpseUtil::environment) + end + def okay? + @kpse && @tree + end + def set(key,value) + @kpse.set(@tree,key,value) + end + def load_cnf + @kpse.load_cnf(@tree) + end + def load_lsr + @kpse.load_lsr(@tree) + end + def expand_variables + @kpse.expand_variables(@tree) + end + def expand_braces(str) + clean_name(@kpse.expand_braces(@tree,str)) + end + def expand_path(str) + clean_name(@kpse.expand_path(@tree,str)) + end + def expand_var(str) + clean_name(@kpse.expand_var(@tree,str)) + end + def show_path(str) + clean_name(@kpse.show_path(@tree,str)) + end + def var_value(str) + clean_name(@kpse.var_value(@tree,str)) + end + def find_file(filename) + clean_name(@kpse.find_file(@tree,filename)) + end + def find_files(filename,first=false) + # dodo: each filename + @kpse.find_files(@tree,filename,first) + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end diff --git a/scripts/context/ruby/base/kpserunner.rb b/scripts/context/ruby/base/kpserunner.rb new file mode 100644 index 000000000..cfc2ad4fb --- /dev/null +++ b/scripts/context/ruby/base/kpserunner.rb @@ -0,0 +1,87 @@ +require 'base/kpsefast' + +module KpseRunner + + @@kpse = nil + + def KpseRunner.kpsewhich(arg='') + options, arguments = split_args(arg) + unless @@kpse then + if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then + require 'base/kpseremote' + @@kpse = KpseRemote.new + else + @@kpse = nil + end + if @@kpse && @@kpse.okay? then + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + else + require 'base/kpsefast' + @@kpse = KpseFast.new + @@kpse.load_cnf + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + @@kpse.expand_variables + @@kpse.load_lsr + end + else + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + @@kpse.expand_variables + end + if option = options['expand-braces'] and not option.empty? then + @@kpse.expand_braces(option) + elsif option = options['expand-path'] and not option.empty? then + @@kpse.expand_path(option) + elsif option = options['expand-var'] and not option.empty? then + @@kpse.expand_var(option) + elsif option = options['show-path'] and not option.empty? then + @@kpse.show_path(option) + elsif option = options['var-value'] and not option.empty? then + @@kpse.expand_var(option) + elsif arguments.size > 0 then + files = Array.new + arguments.each do |option| + if file = @@kpse.find_file(option) and not file.empty? then + files << file + end + end + files.join("\n") + else + '' + end + end + + def KpseRunner.kpsereset + @@kpse = nil + end + + private + + def KpseRunner.split_args(arg) + vars, args = Hash.new, Array.new + arg.gsub!(/([\"\'])(.*?)\1/o) do + $2.gsub(' ','') + end + arg = arg.split(/\s+/o) + arg.collect! do |a| + a.gsub('',' ') + end + arg.each do |a| + if a =~ /^(.*?)\=(.*?)$/o then + k, v = $1, $2 + vars[k.sub(/^\-+/,'')] = v + else + args << a + end + end + # puts vars.inspect + # puts args.inspect + return vars, args + end + +end diff --git a/scripts/context/ruby/base/logger.rb b/scripts/context/ruby/base/logger.rb new file mode 100644 index 000000000..2526cdb0e --- /dev/null +++ b/scripts/context/ruby/base/logger.rb @@ -0,0 +1,104 @@ +# module : base/logger +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +require 'thread' + +# The next calls are valid: + +# @log.report('a','b','c', 'd') +# @log.report('a','b',"c #{d}") +# @log.report("a b c #{d}") + +# Keep in mind that "whatever #{something}" is two times faster than +# 'whatever ' + something or ['whatever',something].join and that +# when verbosity is not needed the following is much faster too: + +# @log.report('a','b','c', 'd') if @log.verbose? +# @log.report('a','b',"c #{d}") if @log.verbose? +# @log.report("a b c #{d}") if @log.verbose? + +# The last three cases are equally fast when verbosity is turned off. + +# Under consideration: verbose per instance + +class Logger + + @@length = 0 + @@verbose = false + + def initialize(tag=nil,length=0,verbose=false) + @tag = tag || '' + @@verbose = @@verbose || verbose + @@length = @tag.length if @tag.length > @@length + @@length = length if length > @@length + end + + def report(*str) + begin + case str.length + when 0 + print("\n") + return true + when 1 + message = str.first + else + message = [str].flatten.collect{|s| s.to_s}.join(' ').chomp + end + if @tag.empty? then + print("#{message}\n") + else + # try to avoid too many adjustments + @tag = @tag.ljust(@@length) unless @tag.length == @@length + print("#{@tag} | #{message}\n") + end + rescue + end + return true + end + + def reportlines(*str) + unless @tag.empty? then + @tag = @tag.ljust(@@length) unless @tag.length == @@length + end + report([str].flatten.collect{|s| s.gsub(/\n/,"\n#{@tag} | ")}.join(' ')) + end + + def debug(*str) + report(str) if @@verbose + end + + def error(*str) + if ! $! || $!.to_s.empty? then + report(str) + else + report(str,$!) + end + end + + def verbose + @@verbose = true + end + + def silent + @@verbose = false + end + + def verbose? + @@verbose + end + + # attr_reader :tag + + # alias fatal error + # alias info debug + # alias warn debug + # alias debug? :verbose? + +end diff --git a/scripts/context/ruby/base/merge.rb b/scripts/context/ruby/base/merge.rb new file mode 100644 index 000000000..a66b97e91 --- /dev/null +++ b/scripts/context/ruby/base/merge.rb @@ -0,0 +1,139 @@ +# module : base/merge +# copyright : PRAGMA Advanced Document Engineering +# version : 2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# --selfmerg ewill create stand alone script (--selfcleanup does the opposite) + +# this module will package all the used modules in the file itself +# so that we can relocate the file at wish, usage: +# +# merge: +# +# unless SelfMerge::ok? && SelfMerge::merge then +# puts("merging should happen on the path were the base inserts reside") +# end +# +# cleanup: +# +# unless SelfMerge::cleanup then +# puts("merging should happen on the path were the base inserts reside") +# end + +module SelfMerge + + @@kpsemergestart = "\# kpse_merge_start" + @@kpsemergestop = "\# kpse_merge_stop" + @@kpsemergefile = "\# kpse_merge_file: " + @@kpsemergedone = "\# kpse_merge_done: " + + @@filename = File.basename($0) + @@ownpath = File.expand_path(File.dirname($0)) + @@modroot = '(base|graphics|rslb|www)' # needed in regex in order not to mess up SelfMerge + @@modules = $".collect do |file| File.expand_path(file) end + + @@modules.delete_if do |file| + file !~ /^#{@@ownpath}\/#{@@modroot}.*$/i + end + + def SelfMerge::ok? + begin + @@modules.each do |file| + return false unless FileTest.file?(file) + end + rescue + return false + else + return true + end + end + + def SelfMerge::merge + begin + if SelfMerge::ok? && rbfile = IO.read(@@filename) then + begin + inserts = "#{@@kpsemergestart}\n\n" + @@modules.each do |file| + inserts << "#{@@kpsemergefile}'#{file}'\n\n" + inserts << IO.read(file).gsub(/^#.*?\n$/,'') + inserts << "\n\n" + end + inserts << "#{@@kpsemergestop}\n\n" + # no gsub! else we end up in SelfMerge + rbfile.sub!(/#{@@kpsemergestart}\s*#{@@kpsemergestop}/moi) do + inserts + end + rbfile.gsub!(/^(.*)(require [\"\'].*?#{@@modroot}.*)$/) do + pre, post = $1, $2 + if pre =~ /#{@@kpsemergedone}/ then + "#{pre}#{post}" + else + "#{pre}#{@@kpsemergedone}#{post}" + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::cleanup + begin + if rbfile = IO.read(@@filename) then + begin + rbfile.sub!(/#{@@kpsemergestart}(.*)#{@@kpsemergestop}\s*/moi) do + "#{@@kpsemergestart}\n\n#{@@kpsemergestop}\n\n" + end + rbfile.gsub!(/^(.*#{@@kpsemergedone}.*)$/) do + str = $1 + if str =~ /require [\"\']/ then + str.gsub(/#{@@kpsemergedone}/, '') + else + str + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::replace + if SelfMerge::ok? then + SelfMerge::cleanup + SelfMerge::merge + end + end + +end diff --git a/scripts/context/ruby/base/mp.rb b/scripts/context/ruby/base/mp.rb new file mode 100644 index 000000000..d168bde1d --- /dev/null +++ b/scripts/context/ruby/base/mp.rb @@ -0,0 +1,167 @@ +# module : base/mp +# copyright : PRAGMA Advanced Document Engineering +# version : 2005-2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +module MPTools + + @@definitions, @@start, @@stop, @@before, @@after = Hash.new, Hash.new, Hash.new, Hash.new, Hash.new + + + @@definitions['plain'] = <0 \\vrule width1sp height\\dimen1 depth\\dimen2 + \\else \\vrule width1sp height1sp depth0sp\\relax + \\fi\\egroup + \\ht0=0pt \\dp0=0pt \\box0 \\egroup} +EOT + + @@start ['plain'] = "" + @@before['plain'] = "\\mpxshipout" + @@after ['plain'] = "\\stopmpxshipout" + @@stop ['plain'] = "\\end{document}" + + @@definitions['context'] = <0 + \\vrule width 1sp height \\dimen1 depth \\dimen2 + \\else + \\vrule width 1sp height 1sp depth 0sp \\relax + \\fi + \\egroup + \\ht0=0pt + \\dp0=0pt + \\loadallfontmapfiles + \\box0 + \\egroup} + +\\fi + +\\ifx\\starttext\\undefined + + \\let\\starttext\\relax + \\def\\stoptext{\\end{document}} + +\\fi +EOT + + @@start ['context'] = "\\starttext" + @@before['context'] = "\\startMPXpage" + @@after ['context'] = "\\stopMPXpage" + @@stop ['context'] = "\\stoptext" + + # todo: \usemodule[m-mpx ] and test fo defined + + def MPTools::mptotex(from,to=nil,method='plain') + begin + if from && data = IO.read(from) then + f = if to then File.open(to,'w') else $stdout end + f.puts("% file: #{from}") + f.puts("") + f.puts(@@definitions[method]) + unless @@start[method].empty? then + f.puts("") + f.puts(@@start[method]) + end + data.gsub!(/([^\\])%.*?$/mo) do + $1 + end + data.scan(/(verbatim|b)tex\s*(.*?)\s*etex/mo) do + tag, text = $1, $2 + f.puts("") + if tag == 'b' then + f.puts(@@before[method]) + f.puts("#{text}%") + f.puts(@@after [method]) + else + f.puts("#{text}") + end + f.puts("") + end + f.puts("") + f.puts(@@stop[method]) + f.close + else + return false + end + rescue + File.delete(to) rescue false + return false + else + return true + end + end + + @@splitMPlines = false + + def MPTools::splitmplines(str) + if @@splitMPlines then + btex, verbatimtex, strings, result = Array.new, Array.new, Array.new, str.dup + # protect texts + result.gsub!(/btex\s*(.*?)\s*etex/) do + btex << $1 + "btex(#{btex.length-1})" + end + result.gsub!(/verbatimtex\s*(.*?)\s*etex/) do + verbatimtex << $1 + "verbatimtex(#{verbatimtex.length-1})" + end + result.gsub!(/\"(.*?)\"/) do + strings << $1 + "\"#{strings.length-1}\"" + end + result.gsub!(/\;/) do + ";\n" + end + result.gsub!(/(.{80,})(\-\-\-|\-\-|\.\.\.|\.\.)/) do + "#{$1}#{$2}\n" + end + result.gsub!(/\n[\s\n]+/moi) do + "\n" + end + result.gsub!(/btex\((\d+)\)/) do + "btex #{btex[$1.to_i]} etex" + end + result.gsub!(/verbatimtex\((\d+)\)/) do + "verbatimtex #{verbatimtex[$1.to_i]} etex" + end + result.gsub!(/\"(\d+)\"/) do + "\"#{strings[$1.to_i]}\"" + end + # return result # let's catch xetex bug + return result.gsub(/\^\^(M|J)/o, "\n") + else + # return str # let's catch xetex bug + return str.gsub(/\^\^(M|J)/o, "\n") + end + end + +end diff --git a/scripts/context/ruby/base/pdf.rb b/scripts/context/ruby/base/pdf.rb new file mode 100644 index 000000000..5aec06fc5 --- /dev/null +++ b/scripts/context/ruby/base/pdf.rb @@ -0,0 +1,75 @@ +module PDFview + + @files = Hash.new + @opencalls = Hash.new + @closecalls = Hash.new + @allcalls = Hash.new + + @method = 'default' # 'xpdf' + + @opencalls['default'] = "pdfopen --file" # "pdfopen --back --file" + @opencalls['xpdf'] = "xpdfopen" + + @closecalls['default'] = "pdfclose --file" + @closecalls['xpdf'] = nil + + @allcalls['default'] = "pdfclose --all" + @allcalls['xpdf'] = nil + + def PDFview.setmethod(method) + @method = method + end + + def PDFview.open(*list) + begin + [*list].flatten.each do |file| + filename = fullname(file) + if FileTest.file?(filename) then + if @opencalls[@method] then + result = `#{@opencalls[@method]} #{filename} 2>&1` + @files[filename] = true + end + end + end + rescue + end + end + + def PDFview.close(*list) + [*list].flatten.each do |file| + filename = fullname(file) + begin + if @files.key?(filename) then + if @closecalls[@method] then + result = `#{@closecalls[@method]} #{filename} 2>&1` + end + else + closeall + return + end + rescue + end + @files.delete(filename) + end + end + + def PDFview.closeall + begin + if @allcalls[@method] then + result = `#{@allcalls[@method]} 2>&1` + end + rescue + end + @files.clear + end + + def PDFview.fullname(name) + name + if name =~ /\.pdf$/ then '' else '.pdf' end + end + +end + +# PDFview.open("t:/document/show-exa.pdf") +# PDFview.open("t:/document/show-gra.pdf") +# PDFview.close("t:/document/show-exa.pdf") +# PDFview.close("t:/document/show-gra.pdf") diff --git a/scripts/context/ruby/base/state.rb b/scripts/context/ruby/base/state.rb new file mode 100644 index 000000000..76ef50b25 --- /dev/null +++ b/scripts/context/ruby/base/state.rb @@ -0,0 +1,75 @@ +require 'digest/md5' + +# todo: register omissions per file + +class FileState + + def initialize + @states = Hash.new + @omiter = Hash.new + end + + def reset + @states.clear + @omiter.clear + end + + def register(filename,omit=nil) + unless @states.key?(filename) then + @states[filename] = Array.new + @omiter[filename] = omit + end + @states[filename] << checksum(filename,@omiter[filename]) + end + + def update(filename=nil) + [filename,@states.keys].flatten.compact.uniq.each do |fn| + register(fn) + end + end + + def inspect(filename=nil) + result = '' + [filename,@states.keys].flatten.compact.uniq.sort.each do |fn| + if @states.key?(fn) then + result += "#{fn}: #{@states[fn].inspect}\n" + end + end + result + end + + def changed?(filename) + if @states.key?(filename) then + n = @states[filename].length + if n>1 then + changed = @states[filename][n-1] != @states[filename][n-2] + else + changed = true + end + else + changed = true + end + return changed + end + + def checksum(filename,omit=nil) + sum = '' + begin + if FileTest.file?(filename) && (data = IO.read(filename)) then + data.gsub!(/\n.*?(#{[omit].flatten.join('|')}).*?\n/) do "\n" end if omit + sum = Digest::MD5.hexdigest(data).upcase + end + rescue + sum = '' + end + return sum + end + + def stable? + @states.keys.each do |s| + return false if changed?(s) + end + return true + end + +end diff --git a/scripts/context/ruby/base/switch.rb b/scripts/context/ruby/base/switch.rb new file mode 100644 index 000000000..19eced424 --- /dev/null +++ b/scripts/context/ruby/base/switch.rb @@ -0,0 +1,635 @@ +# module : base/switch +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# we cannot use getoptlong because we want to be more +# tolerant; also we want to be case insensitive (2002). + +# we could make each option a class itself, but this is +# simpler; also we can put more in the array + +# beware: regexps/o in methods are optimized globally + +require "rbconfig" + +$mswindows = Config::CONFIG['host_os'] =~ /mswin/ +$separator = File::PATH_SEPARATOR + +class String + + def has_suffix?(suffix) + self =~ /\.#{suffix}$/i + end + +end + +# may move to another module + +class File + + @@update_eps = 1 + + def File.needsupdate(oldname,newname) + begin + oldtime = File.stat(oldname).mtime.to_i + newtime = File.stat(newname).mtime.to_i + if newtime >= oldtime then + return false + elsif oldtime-newtime < @@update_eps then + return false + else + return true + end + rescue + return true + end + end + + def File.syncmtimes(oldname,newname) + return + begin + if $mswindows then + # does not work (yet) / gives future timestamp + # t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here + # File.utime(0,t,oldname,newname) + else + t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here + File.utime(0,t,oldname,newname) + end + rescue + end + end + + def File.timestamp(name) + begin + "#{File.stat(name).mtime}" + rescue + return 'unknown' + end + end + +end + +# main thing + +module CommandBase + + # this module can be used as a mixin in a command handler + + $stdout.sync = true + + def initialize(commandline,logger,banner) + @commandline, @logger, @banner = commandline, logger, banner + @forcenewline, @versiondone, @error = false, false, false + version if @commandline.option('version') + end + + def reportlines(*str) + @logger.reportlines(str) + end + + # only works in 1.8 + # + # def report(*str) + # @logger.report(str) + # end + # + # def version # just a bit of playing with defs + # report(@banner.join(' - ')) + # def report(*str) + # @logger.report + # @logger.report(str) + # def report(*str) + # @logger.report(str) + # end + # end + # def version + # end + # end + + def report(*str) + initlogger ; @logger.report(str) + end + + def seterror + @error = true + end + + def error? + return @error + end + + def exit + if @error then Kernel.exit(1) else Kernel.exit(0) end + end + + def execute(str=nil) + send(str || action || 'main') + exit + end + + def debug(*str) + initlogger ; @logger.debug(str) + end + + def error(*str) + initlogger ; @logger.error(str) + end + + def initlogger + if @forcenewline then + @logger.report + @forcenewline = false + end + end + + def logger + @logger + end + + def version # just a bit of playing with defs + unless @versiondone then + report(@banner.join(' - ')) + @forcenewline = true + @versiondone = true + end + end + + def help + version # is nilled when already given + @commandline.helpkeys.each do |k| + if @commandline.help?(k) then + kstr = ('--'+k).ljust(@commandline.helplength+2) + message = @commandline.helptext(k) + message = '' if message == CommandLine::NOHELP + message = message.split(/\s*\n\s*/) + loop do + report("#{kstr} #{message.shift}") + kstr = ' '*kstr.length + break if message.length == 0 + end + end + end + end + + def option(key) + @commandline.option(key) + end + def oneof(*key) + @commandline.oneof(*key) + end + + def globfiles(pattern='*',suffix=nil) + @commandline.setarguments([pattern].flatten) + if files = findfiles(suffix) then + @commandline.setarguments(files) + else + @commandline.setarguments + end + end + + private + + def findfiles(suffix=nil) + + if @commandline.arguments.length>1 then + return @commandline.arguments + else + pattern = @commandline.argument('first') + pattern = '*' if pattern.empty? + if suffix && ! pattern.match(/\..+$/o) then + suffix = '.' + suffix + pattern += suffix unless pattern =~ /#{suffix}$/ + end + # not {} safe + pattern = '**/' + pattern if @commandline.option('recurse') + files = Dir[pattern] + if files && files.length>0 then + return files + else + pattern = @commandline.argument('first') + if FileTest.file?(pattern) then + return [pattern] + else + report("no files match pattern #{pattern}") + return nil + end + end + end + + end + + def globbed(pattern,recurse=false) + + files = Array.new + pattern.split(' ').each do |p| + if recurse then + if p =~ /^(.*)(\/.*?)$/i then + p = $1 + '/**' + $2 + else + p = '**/' + p + end + p.gsub!(/[\\\/]+/, '/') + end + files.push(Dir.glob(p)) + end + files.flatten.sort do |a,b| + pathcompare(a,b) + end + end + + def pathcompare(a,b) + + aa, bb = a.split('/'), b.split('/') + if aa.length == bb.length then + aa.each_index do |i| + if aa[i]bb[i] then + return +1 + end + end + return 0 + else + return aa.length <=> bb.length + end + + end + +end + +class CommandLine + + VALUE, FLAG = 1, 2 + NOHELP = 'no arguments' + + def initialize(prefix='-') + + @registered = Array.new + @options = Hash.new + @unchecked = Hash.new + @arguments = Array.new + @original = ARGV.join(' ') + @helptext = Hash.new + @mandated = Hash.new + @provided = Hash.new + @prefix = prefix + @actions = Array.new + + # The quotes in --switch="some value" get lost in ARGV, so we need to do some trickery here. + + @original = '' + ARGV.each do |a| + aa = a.strip.gsub(/^([#{@prefix}]+\w+\=)([^\"].*?\s+.*[^\"])$/) do + $1 + "\"" + $2 + "\"" + end + @original += if @original.empty? then '' else ' ' end + aa + end + + end + + def setarguments(args=[]) + @arguments = if args then args else [] end + end + + def register(option,shortcut,kind,default=false,action=false,helptext='') + if kind == FLAG then + @options[option] = default + elsif not default then + @options[option] = '' + else + @options[option] = default + end + @registered.push([option,shortcut,kind]) + @mandated[option] = false + # @provided[option] = false + @helptext[option] = helptext + @actions.push(option) if action + end + + def registerflag(option,default=false,helptext='') + if default.class == String then + register(option,'',FLAG,false,false,default) + else + register(option,'',FLAG,false,false,helptext) + end + end + + def registervalue(option,default='',helptext='') + register(option,'',VALUE,default,false,helptext) + end + + def registeraction(option,helptext='') + register(option,'',FLAG,false,true,helptext) + end + + def registermandate(*option) + [*option].each do |o| + [o].each do |oo| + @mandated[oo] = true + end + end + end + + def actions + a = @actions.delete_if do |t| + ! option(t) + end + if a && a.length>0 then + return a + else + return nil + end + end + + def action + @actions.each do |t| + return t if option(t) + end + return nil + end + + def forgotten + @mandated.keys.sort - @provided.keys.sort + end + + def registerhelp(option,text='') + @helptext['unknown'] = if text.empty? then option else text end + end + + def helpkeys(option='.*') + @helptext.keys.sort.grep(/#{option}/) + end + + def helptext(option) + @helptext.fetch(option,'') + end + + def help?(option) + @helptext[option] && ! @helptext[option].empty? + end + + def helplength + n = 0 + @helptext.keys.each do |h| + n = h.length if h.length>n + end + return n + end + + def expand + + # todo : '' or false, depending on type + # @options.clear + # @arguments.clear + + dirtyvalue(@original).split(' ').each do |arg| + case arg + when /^[#{@prefix}][#{@prefix}](.+?)\=(.*?)$/ then locatedouble($1,$2) + when /^[#{@prefix}][#{@prefix}](.+?)$/ then locatedouble($1,false) + when /^[#{@prefix}](.)\=(.)$/ then locatesingle($1,$2) + when /^[#{@prefix}](.+?)$/ then locateseries($1,false) + when /^[\+\-]+/o then # do nothing + else + arguments.push(arg) + end + end + + @options or @unchecked or @arguments + + end + + def extend (str) + @original = @original + ' ' + str + end + + def replace (str) + @original = str + end + + def show + # print "-- options --\n" + @options.keys.sort.each do |key| + print "option: #{key} -> #{@options[key]}\n" + end + # print "-- arguments --\n" + @arguments.each_index do |key| + print "argument: #{key} -> #{@arguments[key]}\n" + end + end + + def option(str,default=nil) + if @options.key?(str) then + @options[str] + elsif default then + default + else + @options[str] + end + end + + def checkedoption(str,default='') + if @options.key?(str) then + if @options[str].empty? then default else @options[str] end + else + default + end + end + + def foundoption(str,default='') + str = str.split(',') if str.class == String + str.each do |s| + return str if @options.key?(str) + end + return default + end + + def oneof(*key) + [*key].flatten.compact.each do |k| + return true if @options.key?(k) && @options[k] + end + return false + end + + def setoption(str,value) + @options[str] = value + end + + def getoption(str,value='') # value ? + @options[str] + end + + def argument(n=0) + if n.class == String then + case n + when 'first' then argument(0) + when 'second' then argument(1) + when 'third' then argument(2) + else + argument(0) + end + elsif @arguments[n] then + @arguments[n] + else + '' + end + end + + # a few local methods, cannot be defined nested (yet) + + private + + def dirtyvalue(value) + if value then + value.gsub(/([\"\'])(.*?)\1/) do + $2.gsub(/\s+/o, "\xFF") + end + else + '' + end + end + + def cleanvalue(value) + if value then + # value.sub(/^([\"\'])(.*?)\1$/) { $2.gsub(/\xFF/o, ' ') } + value.gsub(/\xFF/o, ' ') + else + '' + end + end + + def locatedouble(key, value) + + foundkey, foundkind = nil, nil + + @registered.each do |option, shortcut, kind| + if option == key then + foundkey, foundkind = option, kind + break + end + end + unless foundkey then + @registered.each do |option, shortcut, kind| + n = 0 + begin + re = /^#{key}/i + rescue + key = key.inspect.sub(/^\"(.*)\"$/) do $1 end + re = /^#{key}/i + ensure + if option =~ re then + case n + when 0 + foundkey, foundkind, n = option, kind, 1 + when 1 + # ambiguous matches, like --fix => --fixme --fixyou + foundkey, foundkind = nil, nil + break + end + end + end + end + end + if foundkey then + @provided[foundkey] = true + if foundkind == VALUE then + @options[foundkey] = cleanvalue(value) + else + @options[foundkey] = true + end + else + if value.class == FalseClass then + @unchecked[key] = true + else + @unchecked[key] = cleanvalue(value) + end + end + + end + + def locatesingle(key, value) + + @registered.each do |option, shortcut, kind| + if shortcut == key then + @provided[option] = true + @options[option] = if kind == VALUE then '' else cleanvalue(value) end + break + end + end + + end + + def locateseries(series, value) + + series.each do |key| + locatesingle(key,cleanvalue(value)) + end + + end + + public + + attr_reader :arguments, :options, :original, :unchecked + +end + +# options = CommandLine.new +# +# options.register("filename", "f", CommandLine::VALUE) +# options.register("request" , "r", CommandLine::VALUE) +# options.register("verbose" , "v", CommandLine::FLAG) +# +# options.expand +# options.extend(str) +# options.show +# +# c = CommandLine.new +# +# c.registervalue('aaaa') +# c.registervalue('test') +# c.registervalue('zzzz') +# +# c.registerhelp('aaaa','some aaaa to enter') +# c.registerhelp('test','some text to enter') +# c.registerhelp('zzzz','some zzzz to enter') +# +# c.registermandate('test') +# +# c.expand +# +# class CommandLine +# +# def showhelp (banner,*str) +# if helpkeys(*str).length>0 +# print banner +# helpkeys(*str).each do |h| +# print helptext(h) + "\n" +# end +# true +# else +# false +# end +# end +# +# def showmandate(banner) +# if forgotten.length>0 +# print banner +# forgotten.each do |f| +# print helptext(f) + "\n" +# end +# true +# else +# false +# end +# end +# +# end +# +# c.showhelp("you can provide:\n\n") +# c.showmandate("you also need to provide:\n\n") diff --git a/scripts/context/ruby/base/system.rb b/scripts/context/ruby/base/system.rb new file mode 100644 index 000000000..c3fb08645 --- /dev/null +++ b/scripts/context/ruby/base/system.rb @@ -0,0 +1,121 @@ +# module : base/system +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +require "rbconfig" + +module System + + @@mswindows = Config::CONFIG['host_os'] =~ /mswin/ + @@binpaths = ENV['PATH'].split(File::PATH_SEPARATOR) + @@binsuffixes = if $mswindows then ['.exe','.com','.bat'] else ['','.sh','.csh'] end + @@located = Hash.new + @@binnames = Hash.new + + if @@mswindows then + @@binnames['ghostscript'] = ['gswin32c.exe','gs.cmd','gs.bat'] + @@binnames['imagemagick'] = ['imagemagick.exe','convert.exe'] + @@binnames['inkscape'] = ['inkscape.exe'] + else + @@binnames['ghostscript'] = ['gs'] + @@binnames['imagemagick'] = ['convert'] + @@binnames['inkscape'] = ['inkscape'] + end + + + def System.null + if @@mswindows then 'nul' else '/dev/null' end + end + + def System.unix? + not @@mswindows + end + def System.mswin? + @@mswindows + end + + def System.binnames(str) + if @@binnames.key?(str) then + @@binnames[str] + else + [str] + end + end + + def System.prependengine(str) + if str =~ /^\S+\.(pl|rb|lua|py)/io then + case $1 + when 'pl' then return "perl #{str}" + when 'rb' then return "ruby #{str}" + when 'lua' then return "lua #{str}" + when 'py' then return "python #{str}" + end + end + return str + end + + def System.locatedprogram(program) + if @@located.key?(program) then + return @@located[program] + else + System.binnames(program).each do |binname| + if binname =~ /\..*$/io then + @@binpaths.each do |path| + if FileTest.file?(str = File.join(path,binname)) then + return @@located[program] = System.prependengine(str) + end + end + end + binname.gsub!(/\..*$/io, '') + @@binpaths.each do |path| + @@binsuffixes.each do |suffix| + if FileTest.file?(str = File.join(path,"#{binname}#{suffix}")) then + return @@located[program] = System.prependengine(str) + end + end + end + end + end + return @@located[program] = "texmfstart #{program}" + end + + def System.command(program,arguments='') + if program =~ /^(.*?) (.*)$/ then + program = System.locatedprogram($1) + ' ' + $2 + else + program = System.locatedprogram(program) + end + program = program + ' ' + arguments if ! arguments.empty? + program.gsub!(/\s+/io, ' ') + #program.gsub!(/(\/\.\/)+/io, '/') + program.gsub!(/\\/io, '/') + return program + end + + def System.run(program,arguments='',pipe=false,collect=false) + if pipe then + if collect then + `#{System.command(program,arguments)} 2>&1` + else + `#{System.command(program,arguments)}` + end + else + system(System.command(program,arguments)) + end + end + + def System.pipe(program,arguments='',collect=false) + System.run(program,arguments,true) + end + + def System.safepath(path) + if path.match(/ /o) then "\"#{path}\"" else path end + end + +end diff --git a/scripts/context/ruby/base/tex.rb b/scripts/context/ruby/base/tex.rb new file mode 100644 index 000000000..84025693b --- /dev/null +++ b/scripts/context/ruby/base/tex.rb @@ -0,0 +1,2299 @@ +# module : base/tex +# copyright : PRAGMA Advanced Document Engineering +# version : 2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# todo: +# +# - write systemcall for mpost to file so that it can be run faster +# - use -8bit and -progname +# + +# report ? + +require 'fileutils' + +require 'base/variables' +require 'base/kpse' +require 'base/system' +require 'base/state' +require 'base/pdf' +require 'base/file' +require 'base/ctx' +require 'base/mp' + +class String + + def standard? + begin + self == 'standard' + rescue + false + end + end + +end + +# class String + # def utf_bom? + # self.match(/^\357\273\277/o).length>0 rescue false + # end +# end + +class Array + + def standard? + begin + self.include?('standard') + rescue + false + end + end + + def join_path + self.join(File::PATH_SEPARATOR) + end + +end + +class TEX + + # The make-part of this class was made on a rainy day while listening + # to "10.000 clowns on a rainy day" by Jan Akkerman. Unfortunately the + # make method is not as swinging as this live cd. + + include Variables + + @@texengines = Hash.new + @@mpsengines = Hash.new + @@backends = Hash.new + @@mappaths = Hash.new + @@runoptions = Hash.new + @@tcxflag = Hash.new + @@draftoptions = Hash.new + @@synctexcoptions = Hash.new + @@texformats = Hash.new + @@mpsformats = Hash.new + @@prognames = Hash.new + @@texmakestr = Hash.new + @@texprocstr = Hash.new + @@mpsmakestr = Hash.new + @@mpsprocstr = Hash.new + @@texmethods = Hash.new + @@mpsmethods = Hash.new + @@pdftex = 'pdftex' # new default, pdfetex is gone + @@luafiles = "luafiles.tmp" + @@luatarget = "lua/context" + + @@platformslash = if System.unix? then "\\\\" else "\\" end + + ['tex','etex','pdftex','pdfetex','standard'] .each do |e| @@texengines[e] = 'pdftex' end + ['aleph','omega'] .each do |e| @@texengines[e] = 'aleph' end + ['xetex'] .each do |e| @@texengines[e] = 'xetex' end + ['petex'] .each do |e| @@texengines[e] = 'petex' end + ['luatex'] .each do |e| @@texengines[e] = 'luatex' end + + ['metapost','mpost', 'standard'] .each do |e| @@mpsengines[e] = 'mpost' end + + ['pdfetex','pdftex','pdf','pdftex','standard'] .each do |b| @@backends[b] = 'pdftex' end + ['dvipdfmx','dvipdfm','dpx','dpm'] .each do |b| @@backends[b] = 'dvipdfmx' end + ['xetex','xtx'] .each do |b| @@backends[b] = 'xetex' end + ['petex'] .each do |b| @@backends[b] = 'dvipdfmx' end + ['aleph'] .each do |b| @@backends[b] = 'dvipdfmx' end + ['dvips','ps','dvi'] .each do |b| @@backends[b] = 'dvips' end + ['dvipsone'] .each do |b| @@backends[b] = 'dvipsone' end + ['acrobat','adobe','distiller'] .each do |b| @@backends[b] = 'acrobat' end + ['xdv','xdv2pdf'] .each do |b| @@backends[b] = 'xdv2pdf' end + + ['tex','standard'] .each do |b| @@mappaths[b] = 'dvips' end + ['pdftex','pdfetex'] .each do |b| @@mappaths[b] = 'pdftex' end + ['aleph','omega','xetex','petex'] .each do |b| @@mappaths[b] = 'dvipdfmx' end + ['dvipdfm', 'dvipdfmx', 'xdvipdfmx'] .each do |b| @@mappaths[b] = 'dvipdfmx' end + ['xdv','xdv2pdf'] .each do |b| @@mappaths[b] = 'dvips' end + + # todo norwegian (no) + + ['plain'] .each do |f| @@texformats[f] = 'plain' end + ['cont-en','en','english','context','standard'].each do |f| @@texformats[f] = 'cont-en' end + ['cont-nl','nl','dutch'] .each do |f| @@texformats[f] = 'cont-nl' end + ['cont-de','de','german'] .each do |f| @@texformats[f] = 'cont-de' end + ['cont-it','it','italian'] .each do |f| @@texformats[f] = 'cont-it' end + ['cont-fr','fr','french'] .each do |f| @@texformats[f] = 'cont-fr' end + ['cont-cs','cs','cont-cz','cz','czech'] .each do |f| @@texformats[f] = 'cont-cs' end + ['cont-ro','ro','romanian'] .each do |f| @@texformats[f] = 'cont-ro' end + ['cont-gb','gb','cont-uk','uk','british'] .each do |f| @@texformats[f] = 'cont-gb' end + ['cont-pe','pe','persian'] .each do |f| @@texformats[f] = 'cont-pe' end + ['cont-xp','xp','experimental'] .each do |f| @@texformats[f] = 'cont-xp' end + ['mptopdf'] .each do |f| @@texformats[f] = 'mptopdf' end + + ['latex'] .each do |f| @@texformats[f] = 'latex.ltx' end + + ['plain','mpost'] .each do |f| @@mpsformats[f] = 'mpost' end + ['metafun','context','standard'] .each do |f| @@mpsformats[f] = 'metafun' end + + ['pdftex','pdfetex','aleph','omega','petex', + 'xetex','luatex'] .each do |p| @@prognames[p] = 'context' end + ['mpost'] .each do |p| @@prognames[p] = 'metafun' end + ['latex','pdflatex'] .each do |p| @@prognames[p] = 'latex' end + + ['plain','default','standard','mptopdf'] .each do |f| @@texmethods[f] = 'plain' end + ['cont-en','cont-nl','cont-de','cont-it', + 'cont-fr','cont-cs','cont-ro','cont-gb', + 'cont-pe','cont-xp'] .each do |f| @@texmethods[f] = 'context' end + ['latex','pdflatex'] .each do |f| @@texmethods[f] = 'latex' end + + ['plain','default','standard'] .each do |f| @@mpsmethods[f] = 'plain' end + ['metafun'] .each do |f| @@mpsmethods[f] = 'metafun' end + + @@texmakestr['plain'] = @@platformslash + "dump" + @@mpsmakestr['plain'] = @@platformslash + "dump" + + ['cont-en','cont-nl','cont-de','cont-it', + 'cont-fr','cont-cs','cont-ro','cont-gb', + 'cont-pe','cont-xp'] .each do |f| @@texprocstr[f] = @@platformslash + "emergencyend" end + + @@runoptions['aleph'] = ['--8bit'] + @@runoptions['luatex'] = ['--file-line-error'] + @@runoptions['mpost'] = ['--8bit'] + @@runoptions['pdfetex'] = ['--8bit'] # obsolete + @@runoptions['pdftex'] = ['--8bit'] # pdftex is now pdfetex + # @@runoptions['petex'] = [] + @@runoptions['xetex'] = ['--8bit','-output-driver="xdvipdfmx -E -d 4 -V 5"'] + @@draftoptions['pdftex'] = ['--draftmode'] + @@synctexcoptions['pdftex'] = ['--synctex=1'] + @@synctexcoptions['luatex'] = ['--synctex=1'] + @@synctexcoptions['xetex'] = ['--synctex=1'] + + @@tcxflag['aleph'] = true + @@tcxflag['luatex'] = false + @@tcxflag['mpost'] = false + @@tcxflag['pdfetex'] = true + @@tcxflag['pdftex'] = true + @@tcxflag['petex'] = false + @@tcxflag['xetex'] = false + + @@mainbooleanvars = [ + 'batchmode', 'nonstopmode', 'fast', 'final', + 'paranoid', 'notparanoid', 'nobanner', 'once', 'allpatterns', 'draft', + 'nompmode', 'nomprun', 'automprun', 'combine', + 'nomapfiles', 'local', + 'arrange', 'noarrange', + 'forcexml', 'foxet', + 'alpha', 'beta', 'luatex', + 'mpyforce', 'forcempy', + 'forcetexutil', 'texutil', + 'globalfile', 'autopath', + 'purge', 'purgeall', 'keep', 'autopdf', 'xpdf', 'simplerun', 'verbose', + 'nooptionfile', 'nobackend', 'noctx', 'utfbom', + 'mkii','mkiv', + 'synctex', + ] + @@mainstringvars = [ + 'modefile', 'result', 'suffix', 'response', 'path', + 'filters', 'usemodules', 'environments', 'separation', 'setuppath', + 'arguments', 'input', 'output', 'randomseed', 'modes', 'mode', 'filename', + 'ctxfile', 'printformat', 'paperformat', 'paperoffset', + 'timeout', 'passon' + ] + @@mainstandardvars = [ + 'mainlanguage', 'bodyfont', 'language' + ] + @@mainknownvars = [ + 'engine', 'distribution', 'texformats', 'mpsformats', 'progname', 'interface', + 'runs', 'backend' + ] + + @@extrabooleanvars = [] + @@extrastringvars = [] + + def booleanvars + [@@mainbooleanvars,@@extrabooleanvars].flatten.uniq + end + def stringvars + [@@mainstringvars,@@extrastringvars].flatten.uniq + end + def standardvars + [@@mainstandardvars].flatten.uniq + end + def knownvars + [@@mainknownvars].flatten.uniq + end + def allbooleanvars + [@@mainbooleanvars,@@extrabooleanvars].flatten.uniq + end + def allstringvars + [@@mainstringvars,@@extrastringvars,@@mainstandardvars,@@mainknownvars].flatten.uniq + end + + def setextrastringvars(vars) + # @@extrastringvars << vars -- problems in 1.9 + @@extrastringvars = [@@extrastringvars,vars].flatten + end + def setextrabooleanvars(vars) + # @@extrabooleanvars << vars -- problems in 1.9 + @@extrabooleanvars = [@@extrabooleanvars,vars].flatten + end + + # def jobvariables(names=nil) + # if [names ||[]].flatten.size == 0 then + # names = [allbooleanvars,allstringvars].flatten + # end + # data = Hash.new + # names.each do |name| + # if allbooleanvars.include?(name) then + # data[name] = if getvariable(name) then "yes" else "no" end + # else + # data[name] = getvariable(name) + # end + # end + # data + # end + + # def setjobvariables(names=nil) + # assignments = Array.new + # jobvariables(names).each do |k,v| + # assignments << "#{k}=\{#{v}\}" + # end + # "\setvariables[exe][#{assignments.join(", ")}]" + # end + + @@temprunfile = 'texexec' + @@temptexfile = 'texexec.tex' + + def initialize(logger=nil) + if @logger = logger then + def report(str='') + @logger.report(str) + end + else + def report(str='') + puts(str) + end + end + @cleanups = Array.new + @variables = Hash.new + @startuptime = Time.now + # options + booleanvars.each do |k| + setvariable(k,false) + end + stringvars.each do |k| + setvariable(k,'') + end + standardvars.each do |k| + setvariable(k,'standard') + end + setvariable('distribution', Kpse.distribution) + setvariable('texformats', defaulttexformats) + setvariable('mpsformats', defaultmpsformats) + setvariable('progname', 'standard') # or '' + setvariable('interface', 'standard') + setvariable('engine', 'standard') # replaced by tex/mpsengine + setvariable('backend', 'pdftex') + setvariable('runs', '8') + setvariable('randomseed', rand(1440).to_s) # we want the same seed for one run + # files + setvariable('files', []) + # defaults + setvariable('texengine', 'standard') + setvariable('mpsengine', 'standard') + setvariable('backend', 'standard') + setvariable('error', '') + end + + def error? + not getvariable('error').empty? + end + + def runtime + Time.now - @startuptime + end + + def reportruntime + report("runtime: #{runtime}") + end + + def runcommand(something) + command = [something].flatten.join(' ') + report("running: #{command}") if getvariable('verbose') + system(command) + end + + def inspect(name=nil) + if ! name || name.empty? then + name = [booleanvars,stringvars,standardvars,knownvars] + end + str = '' # allocate + [name].flatten.each do |n| + if str = getvariable(n) then + str = str.join(" ") if str.class == Array + unless (str.class == String) && str.empty? then + report("option '#{n}' is set to '#{str}'") + end + end + end + end + + def tempfilename(suffix='') + @@temprunfile + if suffix.empty? then '' else ".#{suffix}" end + end + + def cleanup + @cleanups.each do |name| + begin + File.delete(name) if FileTest.file?(name) + rescue + report("unable to delete #{name}") + end + end + end + + def cleanuptemprunfiles + begin + Dir.glob("#{@@temprunfile}*").each do |name| + if File.file?(name) && (File.splitname(name)[1] !~ /(pdf|dvi)/o) then + File.delete(name) rescue false + end + end + rescue + end + ['mpgraph.mp'].each do |file| + (File.delete(file) if (FileTest.size?(file) rescue 10) < 10) rescue false + end + end + + def backends() @@backends.keys.sort end + + def texengines() @@texengines.keys.sort end + def mpsengines() @@mpsengines.keys.sort end + def texformats() @@texformats.keys.sort end + def mpsformats() @@mpsformats.keys.sort end + + def defaulttexformats() ['en','nl','mptopdf'] end + def defaultmpsformats() ['metafun'] end + + def texmakeextras(format) @@texmakestr[format] || '' end + def mpsmakeextras(format) @@mpsmakestr[format] || '' end + def texprocextras(format) @@texprocstr[format] || '' end + def mpsprocextras(format) @@mpsprocstr[format] || '' end + + def texmethod(format) @@texmethods[str] || @@texmethods['standard'] end + def mpsmethod(format) @@mpsmethods[str] || @@mpsmethods['standard'] end + + def runoptions(engine) + options = if getvariable('draft') then @@draftoptions[engine] else [] end + options = if getvariable('synctex') then @@synctexcoptions[engine] else [] end + begin + if str = getvariable('passon') then + options = [options,str.split(' ')].flatten + end + rescue + end + if @@runoptions.key?(engine) then + [options,@@runoptions[engine]].flatten.join(' ') + else + options.join(' ') + end + end + + # private + + def cleanuplater(name) + begin + @cleanups.push(File.expand_path(name)) + rescue + @cleanups.push(name) + end + end + + def openedfile(name) + begin + f = File.open(name,'w') + rescue + report("file '#{File.expand_path(name)}' cannot be opened for writing") + return nil + else + cleanuplater(name) if f + return f + end + end + + def prefixed(format,engine) + # format + case engine + when /etex|pdftex|pdfetex|aleph|xetex|luatex/io then + "*#{format}" + else + format + end + end + + def quoted(str) + if str =~ /^[^\"].* / then "\"#{str}\"" else str end + end + + def getarrayvariable(str='') + str = getvariable(str) + if str.class == String then str.split(',') else str.flatten end + end + + def validtexformat(str) validsomething(str,@@texformats,'tex') end + def validmpsformat(str) validsomething(str,@@mpsformats,'mp' ) end + def validtexengine(str) validsomething(str,@@texengines,'pdftex') end + def validmpsengine(str) validsomething(str,@@mpsengines,'mpost' ) end + + def validtexmethod(str) [validsomething(str,@@texmethods)].flatten.first end + def validmpsmethod(str) [validsomething(str,@@mpsmethods)].flatten.first end + + def validsomething(str,something,type=nil) + if str then + list = [str].flatten.collect do |s| + if something[s] then + something[s] + elsif type && s =~ /\.#{type}$/ then + s + else + nil + end + end .compact.uniq + if list.length>0 then + if str.class == String then list.first else list end + else + false + end + else + false + end + end + + def validbackend(str) + if str && @@backends.key?(str) then + @@backends[str] + else + @@backends['standard'] + end + end + + def validprogname(str) + if str then + [str].flatten.each do |s| + s = s.sub(/\.\S*/,"") + return @@prognames[s] if @@prognames.key?(s) + end + return str[0].sub(/\.\S*/,"") + else + return nil + end + end + + # we no longer support the & syntax + + def formatflag(engine=nil,format=nil) + case getvariable('distribution') + when 'standard' then prefix = "--fmt" + when /web2c/io then prefix = web2cformatflag(engine) + when /miktex/io then prefix = "--undump" + else return "" + end + if format then + "#{prefix}=#{format.sub(/\.\S+$/,"")}" + else + prefix + end + end + + def web2cformatflag(engine=nil) + # funny that we've standardized on the fmt suffix (at the cost of + # upward compatibility problems) but stuck to the bas/mem/fmt flags + if engine then + case validmpsengine(engine) + when /mpost/ then "-mem" + when /mfont/ then "-bas" + else "-fmt" + end + else + "-fmt" + end + end + + def prognameflag(progname=nil) + case getvariable('distribution') + when 'standard' then prefix = "-progname" + when /web2c/io then prefix = "-progname" + when /miktex/io then prefix = "-alias" + else return "" + end + if progname and not progname.empty? then + "#{prefix}=#{progname}" + else + prefix + end + end + + def iniflag() # should go to kpse and kpse should become texenv + if Kpse.miktex? then + "-initialize" + else + "--ini" + end + end + def tcxflag(engine) + if @@tcxflag[engine] then + file = "natural.tcx" + if Kpse.miktex? then + "-tcx=#{file}" + else + "-translate-file=#{file}" + end + else + "" + end + end + + def filestate(file) + File.mtime(file).strftime("%d/%m/%Y %H:%M:%S") + end + + # will go to context/process context/listing etc + + def contextversion # ook elders gebruiken + filename = Kpse.found('context.tex') + version = 'unknown' + begin + if FileTest.file?(filename) && IO.read(filename).match(/\\contextversion\{(\d+\.\d+\.\d+.*?)\}/) then + version = $1 + end + rescue + end + return version + end + + def cleanupluafiles + File.delete(@@luafiles) rescue false + end + + def compileluafiles + begin + Dir.glob("lua/context/*.luc").each do |luc| + File.delete(luc) rescue false + end + rescue + end + if data = (IO.readlines(@@luafiles) rescue nil) then + report("compiling lua files (using #{File.expand_path(@@luafiles)})") + begin + FileUtils.makedirs(@@luatarget) rescue false + data.each do |line| + luafile = line.chomp + lucfile = File.basename(luafile).gsub(/\..*?$/,'') + ".luc" + if runcommand(["luac","-s","-o",quoted(File.join(Dir.getwd,@@luatarget,lucfile)),quoted(luafile)]) then + report("#{File.basename(luafile)} converted to #{File.basename(lucfile)}") + else + report("#{File.basename(luafile)} not converted to #{File.basename(lucfile)}") + end + end + rescue + report("fatal error in compilation") + end + else + report("no lua compilations needed") + end + File.delete(@@luafiles) rescue false + end + + # we need engine methods + + def makeformats + + checktestversion + + report("using search method '#{Kpse.searchmethod}'") + if getvariable('fast') then + report('using existing database') + else + report('updating file database') + Kpse.update # obsolete here + if getvariable('luatex') then + begin + runcommand(["luatools","--generate","--verbose"]) + rescue + report("run 'luatools --generate' manualy") + exit + end + end + end + # goody + if getvariable('texformats') == 'standard' then + setvariable('texformats',[getvariable('interface')]) unless getvariable('interface').empty? + end + # prepare + texformats = validtexformat(getarrayvariable('texformats')) + mpsformats = validmpsformat(getarrayvariable('mpsformats')) + texengine = validtexengine(getvariable('texengine')) + mpsengine = validmpsengine(getvariable('mpsengine')) + # save current path + savedpath = Dir.getwd + # generate tex formats + unless texformats || mpsformats then + report('provide valid format (name.tex, name.mp, ...) or format id (metafun, en, nl, ...)') + setvariable('error','no format specified') + end + if texformats && texengine then + report("using tex engine #{texengine}") + texformatpath = if getvariable('local') then '.' else Kpse.formatpath(texengine,true) end + # can be empty, to do + report("using tex format path #{texformatpath}") + Dir.chdir(texformatpath) rescue false + if FileTest.writable?(texformatpath) then + # from now on we no longer support this; we load + # all patterns and if someone wants another + # interface language ... cook up a fmt or usr file + # + # if texformats.length > 0 then + # makeuserfile + # makeresponsefile + # end + if texengine == 'luatex' then + cleanupluafiles + texformats.each do |texformat| + report("generating tex format #{texformat}") + flags = ['--ini','--compile'] + flags << '--verbose' if getvariable('verbose') + flags << '--mkii' if getvariable('mkii') + run_luatools("#{flags.join(" ")} #{texformat}") + end + compileluafiles + else + texformats.each do |texformat| + report("generating tex format #{texformat}") + progname = validprogname([getvariable('progname'),texformat,texengine]) + runcommand([quoted(texengine),prognameflag(progname),iniflag,tcxflag(texengine),prefixed(texformat,texengine),texmakeextras(texformat)]) + end + end + else + report("unable to make format due to lack of permissions") + texformatpath = '' + setvariable('error','no permissions to write') + end + if not mpsformats then + # we want metafun to be in sync + setvariable('mpsformats',defaultmpsformats) + mpsformats = validmpsformat(getarrayvariable('mpsformats')) + end + else + texformatpath = '' + end + # generate mps formats + if mpsformats && mpsengine then + report("using mp engine #{mpsengine}") + mpsformatpath = if getvariable('local') then '.' else Kpse.formatpath(mpsengine,false) end + report("using mps format path #{mpsformatpath}") + Dir.chdir(mpsformatpath) rescue false + if FileTest.writable?(mpsformatpath) then + mpsformats.each do |mpsformat| + report("generating mps format #{mpsformat}") + progname = validprogname([getvariable('progname'),mpsformat,mpsengine]) + # if not runcommand([quoted(mpsengine),prognameflag(progname),iniflag,tcxflag(mpsengine),runoptions(mpsengine),mpsformat,mpsmakeextras(mpsformat)]) then + if not runcommand([quoted(mpsengine),prognameflag(progname),iniflag,runoptions(mpsengine),mpsformat,mpsmakeextras(mpsformat)]) then + setvariable('error','no format made') + end + end + else + report("unable to make format due to lack of permissions") + mpsformatpath = '' + setvariable('error','file permission problem') + end + else + mpsformatpath = '' + end + # check for problems + report("") + report("tex engine path: #{texformatpath}") unless texformatpath.empty? + report("mps engine path: #{mpsformatpath}") unless mpsformatpath.empty? + report("") + [['fmt','tex'],['mem','mps']].each do |f| + [[texformatpath,'global'],[mpsformatpath,'global'],[savedpath,'current']].each do |p| + begin + Dir.chdir(p[0]) + rescue + else + Dir.glob("*.#{f[0]}").each do |file| + report("#{f[1]}: #{filestate(file)} > #{File.expand_path(file)} (#{File.size(file)})") + end + end + end + end + begin + lucdir = File.join(texformatpath,@@luatarget) + Dir.chdir(lucdir) + rescue + else + Dir.glob("*.luc").each do |file| + report("luc: #{filestate(file)} > #{File.expand_path(file)} (#{File.size(file)})") + end + end + # to be sure, go back to current path + begin + Dir.chdir(savedpath) + rescue + end + # finalize + cleanup + report("") + reportruntime + end + + def checkcontext + + # todo : report texmf.cnf en problems + + # basics + report("current distribution: #{Kpse.distribution}") + report("context source date: #{contextversion}") + formatpaths = Kpse.formatpaths + globpattern = "**/{#{formatpaths.join(',')}}/*/*.{fmt,efmt,ofmt,xfmt,mem}" + report("format path: #{formatpaths.join(' ')}") + # utilities + report('start of analysis') + results = Array.new + # ['texexec','texutil','ctxtools'].each do |program| + ['texexec'].each do |program| + result = `texmfstart #{program} --help` + result.sub!(/.*?(#{program}[^\n]+)\n.*/mi) do $1 end + results.push("#{result}") + end + # formats + cleanuptemprunfiles + if formats = Dir.glob(globpattern) then + formats.sort.each do |name| + cleanuptemprunfiles + if f = open(tempfilename('tex'),'w') then + # kind of aleph-run-out-of-par safe + f << "\\starttext\n" + f << " \\relax test \\relax\n" + f << "\\stoptext\n" + f << "\\endinput\n" + f.close + if FileTest.file?(tempfilename('tex')) then + format = File.basename(name) + engine = if name =~ /(pdftex|pdfetex|aleph|xetex|luatex)[\/\\]#{format}/ then $1 else '' end + if engine.empty? then + engineflag = "" + else + engineflag = "--engine=#{$1}" + end + case format + when /cont\-([a-z]+)/ then + interface = $1.sub(/cont\-/,'') + results.push('') + results.push("testing interface #{interface}") + flags = ['--noctx','--process','--batch','--once',"--interface=#{interface}",engineflag] + # result = Kpse.pipescript('texexec',tempfilename,flags) + result = runtexexec([tempfilename], flags, 1) + if FileTest.file?("#{@@temprunfile}.log") then + logdata = IO.read("#{@@temprunfile}.log") + if logdata =~ /^\s*This is (.*?)[\s\,]+(.*?)$/moi then + if validtexengine($1.downcase) then + results.push("#{$1} #{$2.gsub(/\(format.*$/,'')}".strip) + end + end + if logdata =~ /^\s*(ConTeXt)\s+(.*int:\s+[a-z]+.*?)\s*$/moi then + results.push("#{$1} #{$2}".gsub(/\s+/,' ').strip) + end + else + results.push("format #{format} does not work") + end + when /metafun/ then + # todo + when /mptopdf/ then + # todo + end + else + results.push("error in creating #{tempfilename('tex')}") + end + end + cleanuptemprunfiles + end + end + report('end of analysis') + report + results.each do |line| + report(line) + end + cleanuptemprunfiles + + end + + private + + def makeuserfile # not used in luatex (yet) + language = getvariable('language') + mainlanguage = getvariable('mainlanguage') + bodyfont = getvariable('bodyfont') + if f = openedfile("cont-fmt.tex") then + f << "\\unprotect\n" + case language + when 'all' then + f << "\\preloadallpatterns\n" + when '' then + f << "% no language presets\n" + when 'standard' + f << "% using defaults\n" + else + languages = language.split(',') + languages.each do |l| + f << "\\installlanguage[\\s!#{l}][\\c!state=\\v!start]\n" + end + mainlanguage = languages.first + end + unless mainlanguage == 'standard' then + f << "\\setupcurrentlanguage[\\s!#{mainlanguage}]\n"; + end + unless bodyfont == 'standard' then + # ~ will become obsolete when lmr is used + f << "\\definetypescriptsynonym[cmr][#{bodyfont}]" + # ~ is already obsolete for some years now + f << "\\definefilesynonym[font-cmr][font-#{bodyfont}]\n" + end + f << "\\protect\n" + f << "\\endinput\n" + f.close + end + end + + def makeresponsefile + interface = getvariable('interface') + if f = openedfile("mult-def.tex") then + case interface + when 'standard' then + f << "% using default response interface" + else + f << "\\def\\currentresponses\{#{interface}\}\n" + end + f << "\\endinput\n" + f.close + end + end + + private # will become baee/context + + @@preamblekeys = [ + ['tex','texengine'], + ['engine','texengine'], + ['program','texengine'], + ['translate','tcxfilter'], + ['tcx','tcxfilter'], + ['output','backend'], + ['mode','mode'], + ['ctx','ctxfile'], + ['version','contextversion'], + ['format','texformats'], + ['interface','texformats'], + ] + + @@re_utf_bom = /^\357\273\277/o + + def scantexpreamble(filename) + begin + if FileTest.file?(filename) and tex = File.open(filename,'rb') then + bomdone = false + while str = tex.gets and str.chomp! do + unless bomdone then + if str.sub!(@@re_utf_bom, '') + report("utf mode forced (bom found)") + setvariable('utfbom',true) + end + bomdone = true + end + if str =~ /^\%\s*(.*)/o then + # we only accept lines with key=value pairs + vars, ok = Hash.new, true + $1.split(/\s+/o).each do |s| + k, v = s.split('=') + if k && v then + vars[k] = v + else + ok = false + break + end + end + if ok then + # we have a valid line + + @@preamblekeys.each do |v| + setvariable(v[1],vars[v[0]]) if vars.key?(v[0]) && vars[v[0]] + end + +if getvariable('given.backend') == "standard" or getvariable('given.backend') == "" then + setvariable('backend',@@backends[getvariable('texengine')] || 'standard') +end + break + end + else + break + end + end + tex.close + end + rescue + # well, let's not worry too much + end + end + + def scantexcontent(filename) + if FileTest.file?(filename) and tex = File.open(filename,'rb') then + while str = tex.gets do + case str.chomp + when /^\%/o then + # next + # when /\\(starttekst|stoptekst|startonderdeel|startdocument|startoverzicht)/o then + when /\\(starttekst|stoptekst|startonderdeel|startoverzicht)/o then + setvariable('texformats','nl') ; break + when /\\(stelle|verwende|umgebung|benutze)/o then + setvariable('texformats','de') ; break + when /\\(stel|gebruik|omgeving)/o then + setvariable('texformats','nl') ; break + when /\\(use|setup|environment)/o then + setvariable('texformats','en') ; break + when /\\(usa|imposta|ambiente)/o then + setvariable('texformats','it') ; break + when /(height|width|style)=/o then + setvariable('texformats','en') ; break + when /(hoehe|breite|schrift)=/o then + setvariable('texformats','de') ; break + when /(hoogte|breedte|letter)=/o then + setvariable('texformats','nl') ; break + when /(altezza|ampiezza|stile)=/o then + setvariable('texformats','it') ; break + when /externfiguur/o then + setvariable('texformats','nl') ; break + when /externalfigure/o then + setvariable('texformats','en') ; break + when /externeabbildung/o then + setvariable('texformats','de') ; break + when /figuraesterna/o then + setvariable('texformats','it') ; break + end + end + tex.close + end + + end + + private # will become base/context + + def pushresult(filename,resultname) + fname = File.unsuffixed(filename) + rname = File.unsuffixed(resultname) + if ! rname.empty? && (rname != fname) then + report("outputfile #{rname}") + ['tuo','tuc','log','dvi','pdf'].each do |s| + File.silentrename(File.suffixed(fname,s),File.suffixed('texexec',s)) + end + ['tuo','tuc'].each do |s| + File.silentrename(File.suffixed(rname,s),File.suffixed(fname,s)) if FileTest.file?(File.suffixed(rname,s)) + end + end + end + + def popresult(filename,resultname) + fname = File.unsuffixed(filename) + rname = File.unsuffixed(resultname) + if ! rname.empty? && (rname != fname) then + report("renaming #{fname} to #{rname}") + ['tuo','tuc','log','dvi','pdf'].each do |s| + File.silentrename(File.suffixed(fname,s),File.suffixed(rname,s)) + end + report("restoring #{fname}") + unless $fname == 'texexec' then + ['tuo','tuc','log','dvi','pdf'].each do |s| + File.silentrename(File.suffixed('texexec',s),File.suffixed(fname,s)) + end + end + end + end + + def makestubfile(rawname,rawbase,forcexml=false) + if tmp = openedfile(File.suffixed(rawbase,'run')) then + tmp << "\\starttext\n" + if forcexml then + # tmp << checkxmlfile(rawname) + if getvariable('mkiv') then + tmp << "\\xmlprocess{\\xmldocument}{#{rawname}}{}\n" + else + tmp << "\\processXMLfilegrouped{#{rawname}}\n" + end + else + tmp << "\\processfile{#{rawname}}\n" + end + tmp << "\\stoptext\n" + tmp.close + return "run" + else + return File.splitname(rawname)[1] + end + end + + # def checkxmlfile(rawname) + # tmp = '' + # if FileTest.file?(rawname) && (xml = File.open(rawname)) then + # xml.each do |line| + # case line + # when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then + # category, key, value, rest = $1, $2, $3, $4 + # case category + # when 'job' then + # case key + # when 'control' then + # setvariable(value,if rest.empty? then true else rest end) + # when 'mode', 'modes' then + # tmp << "\\enablemode[#{value}]\n" + # when 'stylefile', 'environment' then + # tmp << "\\environment #{value}\n" + # when 'module' then + # tmp << "\\usemodule[#{value}]\n" + # when 'interface' then + # contextinterface = value + # when 'ctxfile' then + # setvariable('ctxfile', value) + # report("using source driven ctxfile #{value}") + # end + # end + # when /<[a-z]+/io then # beware of order, first pi test + # break + # end + # end + # xml.close + # end + # return tmp + # end + + def extendvariable(name,value) + set = getvariable(name).split(',') + set << value + str = set.uniq.join(',') + setvariable(name,str) + end + + def checkxmlfile(rawname) + if FileTest.file?(rawname) && (xml = File.open(rawname,'rb')) then + xml.each do |line| + case line + when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then + category, key, value, rest = $1, $2, $3, $4 + case category + when 'job' then + case key + when 'control' then + setvariable(value,if rest.empty? then true else rest end) + when /^(mode)(s|)$/ then + extendvariable('modes',value) + when /^(stylefile|environment)(s|)$/ then + extendvariable('environments',value) + when /^(use|)(module)(s|)$/ then + extendvariable('usemodules',value) + when /^(filter)(s|)$/ then + extendvariable('filters',value) + when 'interface' then + contextinterface = value + when 'ctxfile' then + setvariable('ctxfile', value) + report("using source driven ctxfile #{value}") + end + end + when /<[a-z]+/io then # beware of order, first pi test + break + end + end + xml.close + end + end + +end + +class TEX + + def timedrun(delay, &block) + delay = delay.to_i rescue 0 + if delay > 0 then + begin + report("job started with timeout '#{delay}'") + timeout(delay) do + yield block + end + rescue TimeoutError + report("job aborted due to timeout '#{delay}'") + setvariable('error','timeout') + rescue + report("job aborted due to error") + setvariable('error','fatal error') + else + report("job finished within timeout '#{delay}'") + end + else + yield block + end + end + + def processtex # much to do: mp, xml, runs etc + setvariable('texformats',[getvariable('interface')]) unless getvariable('interface').empty? + getarrayvariable('files').each do |filename| + setvariable('filename',filename) + report("processing document '#{filename}'") + timedrun(getvariable('timeout')) do + processfile + end + end + reportruntime + end + + def processmptex + getarrayvariable('files').each do |filename| + setvariable('filename',filename) + report("processing graphic '#{filename}'") + runtexmp(filename) + end + reportruntime + end + + private + + def load_map_files(filename) # tui basename + # c \usedmapfile{=}{lm-texnansi} + begin + str = "" + IO.read(filename).scan(/^c\s+\\usedmapfile\{(.*?)\}\{(.*?)\}\s*$/o) do + str << "\\loadmapfile[#{$2}.map]\n" + end + rescue + return "" + else + return str + end + end + + public + + # def run_luatools(args) + # dirty trick: we know that the lua path is relative to the ruby path; of course this + # will not work well when stubs are used + # [(ENV["_CTX_K_S_texexec_"] or ENV["_CTX_K_S_THREAD_"] or ENV["TEXMFSTART.THREAD"]), File.dirname($0)].each do |path| + # if path then + # script = "#{path}/../lua/luatools.lua" + # if FileTest.file?(script) then + # return runcommand("luatex --luaonly #{script} #{args}") + # end + # end + # end + # return runcommand("texmfstart luatools #{args}") + # end + + def run_luatools(args) + return runcommand("luatools #{args}") + end + + def processmpgraphic + getarrayvariable('files').each do |filename| + setvariable('filename',filename) + report("processing graphic '#{filename}'") + runtexmp(filename,'',false) # no purge + mapspecs = load_map_files(File.suffixed(filename,'temp','tui')) + unless getvariable('keep') then + # not enough: purge_mpx_files(filename) + Dir.glob(File.suffixed(filename,'temp*','*')).each do |fname| + File.delete(fname) unless File.basename(filename) == File.basename(fname) + end + end + begin + data = IO.read(File.suffixed(filename,'log')) + basename = filename.sub(/\.mp$/, '') + if data =~ /output files* written\:\s*(.*)$/moi then + files, number, range, list = $1.split(/\s+/), 0, false, [] + files.each do |fname| + if fname =~ /^.*\.(\d+)$/ then + if range then + (number+1 .. $1.to_i).each do |i| + list << i + end + range = false + else + number = $1.to_i + list << number + end + elsif fname =~ /\.\./ then + range = true + else + range = false + next + end + end + begin + if getvariable('combine') then + fullname = "#{basename}.#{number}" + File.open("texexec.tex",'w') do |f| + f << "\\setupoutput[pdftex]\n" + f << "\\setupcolors[state=start]\n" + f << mapspecs + f << "\\starttext\n" + list.each do |number| + f << "\\startTEXpage\n" + f << "\\convertMPtoPDF{#{fullname}}{1}{1}" + f << "\\stopTEXpage\n" + end + f << "\\stoptext\n" + end + report("converting graphic '#{fullname}'") + runtex("texexec.tex") + pdffile = File.suffixed(basename,'pdf') + File.silentrename("texexec.pdf",pdffile) + report ("#{basename}.* converted to #{pdffile}") + else + list.each do |number| + begin + fullname = "#{basename}.#{number}" + File.open("texexec.tex",'w') do |f| + f << "\\setupoutput[pdftex]\n" + f << "\\setupcolors[state=start]\n" + f << mapspecs + f << "\\starttext\n" + f << "\\startTEXpage\n" + f << "\\convertMPtoPDF{#{fullname}}{1}{1}" + f << "\\stopTEXpage\n" + f << "\\stoptext\n" + end + report("converting graphic '#{fullname}'") + runtex("texexec.tex") + if files.length>1 then + pdffile = File.suffixed(basename,number.to_s,'pdf') + else + pdffile = File.suffixed(basename,'pdf') + end + File.silentrename("texexec.pdf",pdffile) + report ("#{fullname} converted to #{pdffile}") + end + end + end + rescue + report ("error when converting #{fullname} (#{$!})") + end + end + rescue + report("error in converting #{filename}") + end + end + reportruntime + end + + def processmpstatic + if filename = getvariable('filename') then + filename += ".mp" unless filename =~ /\..+?$/ + if FileTest.file?(filename) then + begin + data = IO.read(filename) + File.open("texexec.tex",'w') do |f| + f << "\\setupoutput[pdftex]\n" + f << "\\setupcolors[state=start]\n" + data.sub!(/^%mpenvironment\:\s*(.*?)$/moi) do + f << $1 + "\n" + end + f << "\\starttext\n" + f << "\\startMPpage\n" + f << data.gsub(/end\.*\s*$/m, '') # a bit of a hack + f << "\\stopMPpage\n" + f << "\\stoptext\n" + end + report("converting static '#{filename}'") + runtex("texexec.tex") + pdffile = File.suffixed(filename,'pdf') + File.silentrename("texexec.pdf",pdffile) + report ("#{filename} converted to #{pdffile}") + rescue + report("error in converting #{filename} (#{$!}") + end + end + end + reportruntime + end + + def processmpxtex + getarrayvariable('files').each do |filename| + setvariable('filename',filename) + report("processing text of graphic '#{filename}'") + processmpx(filename,false,true,true) + end + reportruntime + end + + def deleteoptionfile(rawname) + ['top','top.keep'].each do |suffix| + begin + File.delete(File.suffixed(rawname,suffix)) + rescue + end + end + end + + def makeoptionfile(rawname, jobname, jobsuffix, finalrun, fastdisabled, kindofrun, currentrun=1) + begin + # jobsuffix = orisuffix + if topname = File.suffixed(rawname,'top') and opt = File.open(topname,'w') then + report("writing option file #{topname}") + # local handies + opt << "\% #{topname}\n" + opt << "\\unprotect\n" + # + # feedback and basic control + # + if getvariable('batchmode') then + opt << "\\batchmode\n" + end + if getvariable('nonstopmode') then + opt << "\\nonstopmode\n" + end + if getvariable('paranoid') then + opt << "\\def\\maxreadlevel{1}\n" + end + if getvariable('nomapfiles') then + opt << "\\disablemapfiles\n" + end + if getvariable('nompmode') || getvariable('nomprun') || getvariable('automprun') then + opt << "\\runMPgraphicsfalse\n" + end + if getvariable('utfbom') then + opt << "\\enableregime[utf]" + end + progname = validprogname(['metafun']) # [getvariable('progname'),mpsformat,mpsengine] + opt << "\\def\\MPOSTformatswitch\{#{prognameflag(progname)} #{formatflag('mpost')}=\}\n" + # + # process info + # + opt << "\\setupsystem[\\c!n=#{kindofrun},\\c!m=#{currentrun}]\n" + if (str = File.unixfied(getvariable('modefile'))) && ! str.empty? then + opt << "\\readlocfile{#{str}}{}{}\n" + end + if (str = File.unixfied(getvariable('result'))) && ! str.empty? then + opt << "\\setupsystem[file=#{str}]\n" + elsif (str = getvariable('suffix')) && ! str.empty? then + opt << "\\setupsystem[file=#{jobname}.#{str}]\n" + end + opt << "\\setupsystem[\\c!method=2]\n" # 1=oldtexexec 2=newtexexec (obsolete) + opt << "\\setupsystem[\\c!type=#{Tool.ruby_platform()}]\n" + if (str = File.unixfied(getvariable('path'))) && ! str.empty? then + opt << "\\usepath[#{str}]\n" unless str.empty? + end + if (str = getvariable('mainlanguage').downcase) && ! str.empty? && ! str.standard? then + opt << "\\setuplanguage[#{str}]\n" + end + if (str = getvariable('arguments')) && ! str.empty? then + opt << "\\setupenv[#{str}]\n" + end + if (str = getvariable('setuppath')) && ! str.empty? then + opt << "\\setupsystem[\\c!directory=\{#{str}\}]\n" + end + if (str = getvariable('randomseed')) && ! str.empty? then + report("using randomseed #{str}") + opt << "\\setupsystem[\\c!random=#{str}]\n" + end + if (str = getvariable('input')) && ! str.empty? then + opt << "\\setupsystem[inputfile=#{str}]\n" + else + opt << "\\setupsystem[inputfile=#{rawname}]\n" + end + # + # modes + # + # we handle both "--mode" and "--modes", else "--mode" is mapped onto "--modefile" + if (str = getvariable('modes')) && ! str.empty? then + opt << "\\enablemode[#{str}]\n" + end + if (str = getvariable('mode')) && ! str.empty? then + opt << "\\enablemode[#{str}]\n" + end + # + # options + # + opt << "\\startsetups *runtime:options\n" + if str = validbackend(getvariable('backend')) then + opt << "\\setupoutput[#{str}]\n" + elsif str = validbackend(getvariable('output')) then + opt << "\\setupoutput[#{str}]\n" + end + if getvariable('color') then + opt << "\\setupcolors[\\c!state=\\v!start]\n" + end + if (str = getvariable('separation')) && ! str.empty? then + opt << "\\setupcolors[\\c!split=#{str}]\n" + end + if (str = getvariable('paperformat')) && ! str.empty? && ! str.standard? then + if str =~ /^([a-z]+\d+)([a-z]+\d+)$/io then # A5A4 A4A3 A2A1 ... + opt << "\\setuppapersize[#{$1.upcase}][#{$2.upcase}]\n" + else # ...*... + pf = str.upcase.split(/[x\*]/o) + pf << pf[0] if pf.size == 1 + opt << "\\setuppapersize[#{pf[0]}][#{pf[1]}]\n" + end + end + if (str = getvariable('background')) && ! str.empty? then + opt << "\\defineoverlay[whatever][{\\externalfigure[#{str}][\\c!factor=\\v!max]}]\n" + opt << "\\setupbackgrounds[\\v!page][\\c!background=whatever]\n" + end + if getvariable('centerpage') then + opt << "\\setuplayout[\\c!location=\\v!middle,\\c!marking=\\v!on]\n" + end + if getvariable('noarrange') then + opt << "\\setuparranging[\\v!disable]\n" + elsif getvariable('arrange') then + arrangement = Array.new + if finalrun then + arrangement << "\\v!doublesided" unless getvariable('noduplex') + case getvariable('printformat') + when '' then arrangement << "\\v!normal" + when /.*up/oi then arrangement << ["2UP","\\v!rotated"] + when /.*down/oi then arrangement << ["2DOWN","\\v!rotated"] + when /.*side/oi then arrangement << ["2SIDE","\\v!rotated"] + end + else + arrangement << "\\v!disable" + end + opt << "\\setuparranging[#{arrangement.flatten.join(',')}]\n" if arrangement.size > 0 + end + if (str = getvariable('pages')) && ! str.empty? then + if str.downcase == 'odd' then + opt << "\\chardef\\whichpagetoshipout=1\n" + elsif str.downcase == 'even' then + opt << "\\chardef\\whichpagetoshipout=2\n" + else + pagelist = Array.new + str.split(/\,/).each do |page| + pagerange = page.split(/\D+/o) + if pagerange.size > 1 then + pagerange.first.to_i.upto(pagerange.last.to_i) do |p| + pagelist << p.to_s + end + else + pagelist << page + end + end + opt << "\\def\\pagestoshipout\{#{pagelist.join(',')}\}\n"; + end + end + opt << "\\stopsetups\n" + # + # styles and modules + # + opt << "\\startsetups *runtime:modules\n" + begin getvariable('filters' ).split(',').uniq.each do |f| opt << "\\useXMLfilter[#{f}]\n" end ; rescue ; end + begin getvariable('usemodules' ).split(',').uniq.each do |m| opt << "\\usemodule [#{m}]\n" end ; rescue ; end + begin getvariable('environments').split(',').uniq.each do |e| opt << "\\environment #{e} \n" end ; rescue ; end + opt << "\\stopsetups\n" + # + opt << "\\protect \\endinput\n" + # + opt.close + else + report("unable to write option file #{topname}") + end + rescue + report("fatal error in writing option file #{topname} (#{$!})") + end + end + + def takeprecautions + ENV['MPXCOMAND'] = '0' # else loop + if getvariable('paranoid') then + ENV['SHELL_ESCAPE'] = ENV['SHELL_ESCAPE'] || 'f' + ENV['OPENOUT_ANY'] = ENV['OPENOUT_ANY'] || 'p' + ENV['OPENIN_ANY'] = ENV['OPENIN_ANY'] || 'p' + elsif getvariable('notparanoid') then + ENV['SHELL_ESCAPE'] = ENV['SHELL_ESCAPE'] || 't' + ENV['OPENOUT_ANY'] = ENV['OPENOUT_ANY'] || 'a' + ENV['OPENIN_ANY'] = ENV['OPENIN_ANY'] || 'a' + end + if ENV['OPENIN_ANY'] && (ENV['OPENIN_ANY'] == 'p') then # first test redundant + setvariable('paranoid', true) + end + if ENV.key?('SHELL_ESCAPE') && (ENV['SHELL_ESCAPE'] == 'f') then + setvariable('automprun',true) + end + done = false + ['TXRESOURCES','MPRESOURCES','MFRESOURCES'].each do |res| + [getvariable('runpath'),getvariable('path')].each do |pat| + unless pat.empty? then + if ENV.key?(res) then + # ENV[res] = if ENV[res].empty? then pat else pat + ":" + ENV[res] end +if ENV[res].empty? then + ENV[res] = pat +elsif ENV[res] == pat || ENV[res] =~ /^#{pat}\:/ || ENV[res] =~ /\:#{pat}\:/ then + # skip +else + ENV[res] = pat + ":" + ENV[res] +end + else + ENV[res] = pat + end + report("setting #{res} to #{ENV[res]}") unless done + end + end + done = true + end + end + + def checktestversion + # + # one can set TEXMFALPHA and TEXMFBETA for test versions + # but keep in mind that the format as well as the test files + # then need the --alpha or --beta flag + # + done, tree = false, '' + ['alpha', 'beta'].each do |what| + if getvariable(what) then + if ENV["TEXMF#{what.upcase}"] then + done, tree = true, ENV["TEXMF#{what.upcase}"] + elsif ENV["TEXMFLOCAL"] then + done, tree = true, File.join(File.dirname(ENV['TEXMFLOCAL']), "texmf-#{what}") + end + end + break if done + end + if done then + tree = tree.strip + ENV['TEXMFPROJECT'] = tree + report("using test tree '#{tree}'") + ['MP', 'MF', 'TX'].each do |ctx| + ENV['CTXDEV#{ctx}PATH'] = '' + end + unless (FileTest.file?(File.join(tree,'ls-r')) || FileTest.file?(File.join(tree,'ls-R'))) then + report("no ls-r/ls-R file for tree '#{tree}' (run: mktexlsr #{tree})") + end + end + # puts `kpsewhich --expand-path=$TEXMF` + # exit + end + + def runtex(filename) + checktestversion + texengine = validtexengine(getvariable('texengine')) + texformat = validtexformat(getarrayvariable('texformats').first) + report("tex engine: #{texengine}") + report("tex format: #{texformat}") + if texengine && texformat then + fixbackendvars(@@mappaths[texengine]) + if texengine == "luatex" then + # currently we use luatools to start luatex but some day we should + # find a clever way to directly call luatex (problem is that we need + # to feed the explicit location of the format and lua initialization + # file) + run_luatools("--fmt=#{texformat} #{filename}") + else + progname = validprogname([getvariable('progname'),texformat,texengine]) + runcommand([quoted(texengine),prognameflag(progname),formatflag(texengine,texformat),tcxflag(texengine),runoptions(texengine),filename,texprocextras(texformat)]) + end + # true + else + false + end + end + + def runmp(mpname,mpx=false) + checktestversion + mpsengine = validmpsengine(getvariable('mpsengine')) + mpsformat = validmpsformat(getarrayvariable('mpsformats').first) + if mpsengine && mpsformat then + ENV["MPXCOMMAND"] = "0" unless mpx + progname = validprogname([getvariable('progname'),mpsformat,mpsengine]) + mpname.gsub!(/\.mp$/,"") # temp bug in mp + # runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),tcxflag(mpsengine),runoptions(mpsengine),mpname,mpsprocextras(mpsformat)]) + runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),runoptions(mpsengine),mpname,mpsprocextras(mpsformat)]) + true + else + false + end + end + + def runtexmp(filename,filetype='',purge=true) + checktestversion + mpname = File.suffixed(filename,filetype,'mp') + if File.atleast?(mpname,10) then + # first run needed + File.silentdelete(File.suffixed(mpname,'mpt')) + doruntexmp(mpname,nil,true,purge) + mpgraphics = checkmpgraphics(mpname) + mplabels = checkmplabels(mpname) + if mpgraphics || mplabels then + # second run needed + doruntexmp(mpname,mplabels,true,purge) + else + # no labels + end + end + end + + def runtexmpjob(filename,filetype='') + checktestversion + mpname = File.suffixed(filename,filetype,'mp') + if File.atleast?(mpname,25) && (data = File.silentread(mpname)) then + textranslation = if data =~ /^\%\s+translate.*?\=([\w\d\-]+)/io then $1 else '' end + mpjobname = if data =~ /collected graphics of job \"(.+?)\"/io then $1 else '' end + if ! mpjobname.empty? and File.unsuffixed(filename) =~ /#{mpjobname}/ then # don't optimize + options = Array.new + options.push("--mptex") + options.push("--nomp") + options.push("--mpyforce") if getvariable('forcempy') || getvariable('mpyforce') + options.push("--translate=#{textranslation}") unless textranslation.empty? + options.push("--batch") if getvariable('batchmode') + options.push("--nonstop") if getvariable('nonstopmode') + options.push("--output=ps") # options.push("--dvi") + options.push("--nobackend") + return runtexexec(mpname,options,2) + end + end + return false + end + + def runtexutil(filename=[], options=['--ref','--ij','--high'], old=false) + [filename].flatten.each do |fname| + if old then + Kpse.runscript('texutil',fname,options) + else + begin + logger = Logger.new('TeXUtil') + if tu = TeXUtil::Converter.new(logger) and tu.loaded(fname) then + ok = tu.processed && tu.saved && tu.finalized + end + rescue + Kpse.runscript('texutil',fname,options) + end + end + end + end + + def runluacheck(jobname) + if false then + # test-pos.tex / 6 meg tua file: 18.6 runtime + old, new = File.suffixed(jobname,'tua'), File.suffixed(jobname,'tuc') + if FileTest.file?(old) then + report("converting #{old} into #{new}") + system("luac -s -o #{new} #{old}") + end + else + # test-pos.tex / 6 meg tua file: 17.5 runtime + old, new = File.suffixed(jobname,'tua'), File.suffixed(jobname,'tuc') + if FileTest.file?(old) then + report("renaming #{old} into #{new}") + File.rename(old,new) rescue false + end + end + end + + # 1=tex 2=mptex 3=mpxtex 4=mpgraphic 5=mpstatic + + def runtexexec(filename=[], options=[], mode=nil) + begin + if mode and job = TEX.new(@logger) then + options.each do |option| + case option + when /^\-*(.*?)\=(.*)$/o then + job.setvariable($1,$2) + when /^\-*(.*?)$/o then + job.setvariable($1,true) + end + end + job.setvariable("files",filename) + case mode + when 1 then job.processtex + when 2 then job.processmptex + when 3 then job.processmpxtex + when 4 then job.processmpgraphic + when 5 then job.processmpstatic + end + job.inspect && Kpse.inspect if getvariable('verbose') + return true + else + Kpse.runscript('texexec',filename,options) + end + rescue + Kpse.runscript('texexec',filename,options) + end + end + + def fixbackendvars(backend) + if backend then + ENV['backend'] = backend ; + ENV['progname'] = backend unless validtexengine(backend) + ENV['TEXFONTMAPS'] = ['.',"\$TEXMF/fonts/{data,map}/{#{backend},pdftex,dvips,}//",'./fonts//'].join_path + report("fixing backend map path for #{backend}: #{ENV['TEXFONTMAPS']}") if getvariable('verbose') + else + report("unable to fix backend map path") if getvariable('verbose') + end + end + + def runbackend(rawname) + unless getvariable('nobackend') then + case validbackend(getvariable('backend')) + when 'dvipdfmx' then + fixbackendvars('dvipdfm') + runcommand("dvipdfmx -d 4 -V 5 #{File.unsuffixed(rawname)}") + when 'xetex' then + # xetex now runs its own backend + xdvfile = File.suffixed(rawname,'xdv') + if FileTest.file?(xdvfile) then + fixbackendvars('dvipdfm') + runcommand("xdvipdfmx -q -d 4 -V 5 -E #{xdvfile}") + end + when 'xdv2pdf' then + xdvfile = File.suffixed(rawname,'xdv') + if FileTest.file?(xdvfile) then + fixbackendvars('xdv2pdf') + runcommand("xdv2pdf #{xdvfile}") + end + when 'dvips' then + fixbackendvars('dvips') + mapfiles = '' + begin + if tuifile = File.suffixed(rawname,'tui') and FileTest.file?(tuifile) then + IO.read(tuifile).scan(/^c \\usedmapfile\{.\}\{(.*?)\}\s*$/o) do + mapfiles += "-u +#{$1} " ; + end + end + rescue + mapfiles = '' + end + runcommand("dvips #{mapfiles} #{File.unsuffixed(rawname)}") + when 'pdftex' then + # no need for postprocessing + else + report("no postprocessing needed") + end + end + end + + def processfile + + takeprecautions + report("using search method '#{Kpse.searchmethod}'") if getvariable('verbose') + + rawname = getvariable('filename') + jobname = getvariable('filename') + + if getvariable('autopath') then + jobname = File.basename(jobname) + inppath = File.dirname(jobname) + else + inppath = '' + end + + jobname, jobsuffix = File.splitname(jobname,'tex') + + jobname = File.unixfied(jobname) + inppath = File.unixfied(inppath) + + orisuffix = jobsuffix # still needed ? + + if jobsuffix =~ /^(htm|html|xhtml|xml|fo|fox|rlg|exa)$/io then + setvariable('forcexml',true) + end + + dummyfile = false + + # fuzzy code snippet: (we kunnen kpse: prefix gebruiken) + + unless FileTest.file?(File.suffixed(jobname,jobsuffix)) then + if FileTest.file?(rawname + '.tex') then + jobname = rawname.dup + jobsuffix = 'tex' + end + end + + # we can have funny names, like 2005.10.10 (given without suffix) + + rawname = jobname + '.' + jobsuffix + rawpath = File.dirname(rawname) + rawbase = File.basename(rawname) + + unless FileTest.file?(rawname) then + inppath.split(',').each do |ip| + break if dummyfile = FileTest.file?(File.join(ip,rawname)) + end + end + + forcexml = getvariable('forcexml') + + if dummyfile || forcexml then # after ctx? + jobsuffix = makestubfile(rawname,rawbase,forcexml) + checkxmlfile(rawname) + end + + # preprocess files + + unless getvariable('noctx') then + ctx = CtxRunner.new(rawname,@logger) + if pth = getvariable('path') then + pth.split(',').each do |p| + ctx.register_path(p) + end + end + if getvariable('ctxfile').empty? then + if rawname == rawbase then + ctx.manipulate(File.suffixed(rawname,'ctx'),'jobname.ctx') + else + ctx.manipulate(File.suffixed(rawname,'ctx'),File.join(rawpath,'jobname.ctx')) + end + else + ctx.manipulate(File.suffixed(getvariable('ctxfile'),'ctx')) + end + ctx.savelog(File.suffixed(rawbase,'ctl')) + + envs = ctx.environments + mods = ctx.modules + flags = ctx.flags + mdes = ctx.modes + + flags.each do |f| + f.sub!(/^\-+/,'') + if f =~ /^(.*?)=(.*)$/ then + setvariable($1,$2) + else + setvariable(f,true) + end + end + + report("using flags #{flags.join(' ')}") if flags.size > 0 + + # merge environment and module specs + + envs << getvariable('environments') unless getvariable('environments').empty? + mods << getvariable('usemodules') unless getvariable('usemodules') .empty? + mdes << getvariable('modes') unless getvariable('modes') .empty? + + envs = envs.uniq.join(',') + mods = mods.uniq.join(',') + mdes = mdes.uniq.join(',') + + report("using search method '#{Kpse.searchmethod}'") if getvariable('verbose') + + report("using environments #{envs}") if envs.length > 0 + report("using modules #{mods}") if mods.length > 0 + report("using modes #{mdes}") if mdes.length > 0 + + setvariable('environments', envs) + setvariable('usemodules', mods) + setvariable('modes', mdes) + end + + # end of preprocessing and merging + + setvariable('nomprun',true) if orisuffix == 'mpx' # else cylic run + PDFview.setmethod('xpdf') if getvariable('xpdf') + PDFview.closeall if getvariable('autopdf') + + runonce = getvariable('once') + finalrun = getvariable('final') || (getvariable('arrange') && ! getvariable('noarrange')) + suffix = getvariable('suffix') + result = getvariable('result') + globalfile = getvariable('globalfile') + forcexml = getvariable('forcexml') # can be set in ctx file + +if dummyfile || forcexml then # after ctx? + jobsuffix = makestubfile(rawname,rawbase,forcexml) + checkxmlfile(rawname) +end + + result = File.unixfied(result) + + if globalfile || FileTest.file?(rawname) then + + if not dummyfile and not globalfile and not forcexml then + scantexpreamble(rawname) + scantexcontent(rawname) if getvariable('texformats').standard? + end + result = File.suffixed(rawname,suffix) unless suffix.empty? + + pushresult(rawbase,result) + + method = validtexmethod(validtexformat(getvariable('texformats'))) + + report("tex processing method: #{method}") + + case method + + when 'context' then + if getvariable('simplerun') || runonce then + makeoptionfile(rawbase,jobname,orisuffix,true,true,3,1) unless getvariable('nooptionfile') + ok = runtex(if dummyfile || forcexml then rawbase else rawname end) + if ok then + ok = runtexutil(rawbase) if getvariable('texutil') || getvariable('forcetexutil') + runluacheck(rawbase) + runbackend(rawbase) + popresult(rawbase,result) + end + if getvariable('keep') then + ['top','log','run'].each do |suffix| + File.silentrename(File.suffixed(rawbase,suffix),File.suffixed(rawbase,suffix+'.keep')) + end + end + else +# goto tmp/jobname when present + mprundone, ok, stoprunning = false, true, false + texruns, nofruns = 0, getvariable('runs').to_i + state = FileState.new + ['tub','tuo','tuc'].each do |s| + state.register(File.suffixed(rawbase,s)) + end + if getvariable('automprun') then # check this + ['mprun','mpgraph'].each do |s| + state.register(File.suffixed(rawbase,s,'mp'),'randomseed') + end + end + while ! stoprunning && (texruns < nofruns) && ok do + texruns += 1 + report("TeX run #{texruns}") + unless getvariable('nooptionfile') then + if texruns == nofruns then + makeoptionfile(rawbase,jobname,orisuffix,false,false,4,texruns) # last + elsif texruns == 1 then + makeoptionfile(rawbase,jobname,orisuffix,false,false,1,texruns) # first + else + makeoptionfile(rawbase,jobname,orisuffix,false,false,2,texruns) # unknown + end + end +# goto . + + ok = runtex(File.suffixed(if dummyfile || forcexml then rawbase else rawname end,jobsuffix)) + +if getvariable('texengine') == "xetex" then + ok = true +end + +############################ + +# goto tmp/jobname when present + if ok && (nofruns > 1) then + unless getvariable('nompmode') then + mprundone = runtexmpjob(rawbase, "mpgraph") + mprundone = runtexmpjob(rawbase, "mprun") + end + ok = runtexutil(rawbase) + runluacheck(rawbase) + state.update + stoprunning = state.stable? + end + end + if not ok then + setvariable('error','error in tex file') + end + if (nofruns == 1) && getvariable('texutil') then + ok = runtexutil(rawbase) + runluacheck(rawbase) + end + if ok && finalrun && (nofruns > 1) then + makeoptionfile(rawbase,jobname,orisuffix,true,finalrun,4,texruns) unless getvariable('nooptionfile') + report("final TeX run #{texruns}") +# goto . + ok = runtex(File.suffixed(if dummyfile || forcexml then rawbase else rawname end,jobsuffix)) +# goto tmp/jobname when present + end + if getvariable('keep') then + ['top','log','run'].each do |suffix| + File.silentrename(File.suffixed(rawbase,suffix),File.suffixed(rawbase,suffix+'.keep')) + end + else + File.silentrename(File.suffixed(rawbase,'top'),File.suffixed(rawbase,'tmp')) + end + # ['tmp','top','log'].each do |s| # previous tuo file / runtime option file / log file + # File.silentdelete(File.suffixed(rawbase,s)) + # end + if ok then +# goto . + runbackend(rawbase) + popresult(rawbase,result) +# goto tmp/jobname when present +# skip next + end + if true then # autopurge + begin + File.open(File.suffixed(rawbase, 'tuo'),'rb') do |f| + ok = 0 + f.each do |line| + case ok + when 1 then + # next line is empty + ok = 2 + when 2 then + if line =~ /^\%\s+\>\s+(.*?)\s+(\d+)/moi then + filename, n = $1, $2 + done = File.delete(filename) rescue false + if done && getvariable('verbose') then + report("deleting #{filename} (#{n} times used)") + end + else + break + end + else + if line =~ /^\%\s+temporary files\:\s+(\d+)/moi then + if $1.to_i == 0 then + break + else + ok = 1 + end + end + end + end + end + rescue + # report("fatal error #{$!}") + end + end + end + + Kpse.runscript('ctxtools',rawbase,'--purge') if getvariable('purge') + Kpse.runscript('ctxtools',rawbase,'--purge --all') if getvariable('purgeall') + + # runcommand('mtxrun','--script','ctxtools',rawbase,'--purge') if getvariable('purge') + # runcommand('mtxrun','--script','ctxtools',rawbase,'--purge --all') if getvariable('purgeall') + + when 'latex' then + + ok = runtex(rawname) + + else + + ok = runtex(rawname) + + end + + if (dummyfile or forcexml) and FileTest.file?(rawbase) then + begin + File.delete(File.suffixed(rawbase,'run')) + rescue + report("unable to delete stub file") + end + end + + if ok and getvariable('autopdf') then + PDFview.open(File.suffixed(if result.empty? then rawbase else result end,'pdf')) + end + + else + report("nothing to process") + end + + end + + # The labels are collected in the mergebe hash. Here we merge the relevant labels + # into beginfig/endfig. We could as well do this in metafun itself. Maybe some + # day ... (it may cost a bit of string space but that is cheap nowadays). + + def doruntexmp(mpname,mergebe=nil,context=true,purge=true) + texfound = false + mpname = File.suffixed(mpname,'mp') + mpcopy = File.suffixed(mpname,'mp.copy') + mpkeep = File.suffixed(mpname,'mp.keep') + setvariable('mp.file',mpname) + setvariable('mp.line','') + setvariable('mp.error','') + if mpdata = File.silentread(mpname) then + # mpdata.gsub!(/^\%.*\n/o,'') + File.silentrename(mpname,mpcopy) + texfound = mergebe || (mpdata =~ /btex .*? etex/mo) + if mp = openedfile(mpname) then + if mergebe then + mpdata.gsub!(/beginfig\s*\((\d+)\)\s*\;(.+?)endfig\s*\;/mo) do + n, str = $1, $2 + if str =~ /^(.*?)(verbatimtex.*?etex)\s*\;(.*)$/mo then + "beginfig(#{n})\;\n#{$1}#{$2}\;\n#{mergebe[n]}\n#{$3}\;endfig\;\n" + else + "beginfig(#{n})\;\n#{mergebe[n]}\n#{str}\;endfig\;\n" + end + end + unless mpdata =~ /beginfig\s*\(\s*0\s*\)/o then + mp << mergebe['0'] if mergebe.key?('0') + end + end + # mp << MPTools::splitmplines(mpdata) + mp << mpdata + mp << "\n" + # mp << "end" + # mp << "\n" + mp.close + end + processmpx(mpname,true,true,purge) if texfound + if getvariable('batchmode') then + options = ' --interaction=batch' + elsif getvariable('nonstopmode') then + options = ' --interaction=nonstop' + else + options = '' + end + # todo plain|mpost|metafun + begin + ok = runmp(mpname) + rescue + end + if f = File.silentopen(File.suffixed(mpname,'log')) then + while str = f.gets do + if str =~ /^l\.(\d+)\s(.*?)\n/o then + setvariable('mp.line',$1) + setvariable('mp.error',$2) + break + end + end + f.close + end + File.silentrename(mpname, mpkeep) + File.silentrename(mpcopy, mpname) + end + end + + # todo: use internal mptotext function and/or turn all btex/etex into textexts + + def processmpx(mpname,force=false,context=true,purge=true) + unless force then + mpname = File.suffixed(mpname,'mp') + if File.atleast?(mpname,10) && (data = File.silentread(mpname)) then + if data =~ /(btex|etex|verbatimtex|textext)/o then + force = true + end + end + end + if force then + begin + mptex = File.suffixed(mpname,'temp','tex') + mpdvi = File.suffixed(mpname,'temp','dvi') + mplog = File.suffixed(mpname,'temp','log') + mpmpx = File.suffixed(mpname,'mpx') + File.silentdelete(mptex) + if true then + report("using internal mptotex converter") + ok = MPTools::mptotex(mpname,mptex,'context') + else + command = "mpto #{mpname} > #{mptex}" + report(command) if getvariable('verbose') + ok = system(command) + end + # not "ok && ..." because of potential problem with return code and redirect (>) + if FileTest.file?(mptex) && File.appended(mptex, "\\end\n") then + # to be replaced by runtexexec([filenames],options,1) + if localjob = TEX.new(@logger) then + localjob.setvariable('files',mptex) + localjob.setvariable('backend','dvips') + localjob.setvariable('engine',getvariable('engine')) unless getvariable('engine').empty? + localjob.setvariable('once',true) + localjob.setvariable('nobackend',true) + if context then + localjob.setvariable('texformats',[getvariable('interface')]) unless getvariable('interface').empty? + elsif getvariable('interface').empty? then + localjob.setvariable('texformats',['plain']) + else + localjob.setvariable('texformats',[getvariable('interface')]) + end + localjob.processtex + ok = true # todo + else + ok = false + end + # so far + command = "dvitomp #{mpdvi} #{mpmpx}" + report(command) if getvariable('verbose') + ok = ok && FileTest.file?(mpdvi) && system(command) + purge_mpx_files(mpname) if purge + end + rescue + # error in processing mpx file + end + end + end + + def purge_mpx_files(mpname) + unless getvariable('keep') then + ['tex', 'log', 'tui', 'tuo', 'tuc', 'top'].each do |suffix| + File.silentdelete(File.suffixed(mpname,'temp',suffix)) + end + end + end + + def checkmpgraphics(mpname) + # in practice the checksums will differ because of multiple instances + # ok, we could save the mpy/mpo files by number, but not now + mpoptions = '' + if getvariable('makempy') then + mpoptions += " --makempy " + end + mponame = File.suffixed(mpname,'mpo') + mpyname = File.suffixed(mpname,'mpy') + pdfname = File.suffixed(mpname,'pdf') + tmpname = File.suffixed(mpname,'tmp') + if getvariable('mpyforce') || getvariable('forcempy') then + mpoptions += " --force " + else + return false unless File.atleast?(mponame,32) + mpochecksum = FileState.new.checksum(mponame) + return false if mpochecksum.empty? + # where does the checksum get into the file? + # maybe let texexec do it? + # solution: add one if not present or update when different + begin + mpydata = IO.read(mpyname) + if mpydata then + if mpydata =~ /^\%\s*mpochecksum\s*\:\s*([A-Z0-9]+)$/mo then + checksum = $1 + if mpochecksum == checksum then + return false + end + end + end + rescue + # no file + end + end + # return Kpse.runscript('makempy',mpname) + # only pdftex + flags = ['--noctx','--process','--batch','--once'] + result = runtexexec([mponame], flags, 1) + runcommand(["pstoedit","-ssp -dt -f mpost", pdfname,tmpname]) + tmpdata = IO.read(tmpname) + if tmpdata then + if mpy = openedfile(mpyname) then + mpy << "% mpochecksum: #{mpochecksum}\n" + tmpdata.scan(/beginfig(.*?)endfig/mo) do |s| + mpy << "begingraphictextfig#{s}endgraphictextfig\n" + end + mpy.close() + end + end + File.silentdelete(tmpname) + File.silentdelete(pdfname) + return true + end + + def checkmplabels(mpname) + mpname = File.suffixed(mpname,'mpt') + if File.atleast?(mpname,10) && (mp = File.silentopen(mpname)) then + labels = Hash.new + while str = mp.gets do + t = if str =~ /^%\s*setup\s*:\s*(.*)$/o then $1 else '' end + if str =~ /^%\s*figure\s*(\d+)\s*:\s*(.*)$/o then + labels[$1] = labels[$1] || '' + unless t.empty? then + labels[$1] += "#{t}\n" + t = '' + end + labels[$1] += "#{$2}\n" + end + end + mp.close + if labels.size>0 then + return labels + else + return nil + end + end + return nil + end + +end diff --git a/scripts/context/ruby/base/texutil.rb b/scripts/context/ruby/base/texutil.rb new file mode 100644 index 000000000..868e3ca16 --- /dev/null +++ b/scripts/context/ruby/base/texutil.rb @@ -0,0 +1,1097 @@ +require "base/file" +require "base/logger" + +class String + + # real dirty, but inspect does a pretty good escaping but + # unfortunately puts quotes around the string so we need + # to strip these + + # def escaped + # self.inspect[1,self.inspect.size-2] + # end + + def escaped + str = self.inspect ; str[1,str.size-2] + end + + def splitdata + if self =~ /^\s*(.*?)\s*\{(.*)\}\s*$/o then + first, second = $1, $2 + if first.empty? then + [second.split(/\} \{/o)].flatten + else + [first.split(/\s+/o)] + [second.split(/\} \{/o)] + end + else + [] + end + end + +end + +class Logger + def banner(str) + report(str) + return "%\n% #{str}\n%\n" + end +end + +class TeXUtil + + class Plugin + + # we need to reset module data for each run; persistent data is + # possible, just don't reinitialize the data structures that need + # to be persistent; we reset afterwards becausethen we know what + # plugins are defined + + def initialize(logger) + @plugins = Array.new + @logger = logger + end + + def report(str) + @logger.report("fatal error in plugin (#{str}): #{$!}") + puts("\n") + $@.each do |line| + puts(" #{line}") + end + puts("\n") + end + + def reset(name) + if @plugins.include?(name) then + begin + eval("#{name}").reset(@logger) + rescue Exception + report("resetting") + end + else + @logger.report("no plugin #{name}") + end + end + + def resets + @plugins.each do |p| + reset(p) + end + end + + def register(name, file=nil) # maybe also priority + if file then + begin + require("#{file.downcase.sub(/\.rb$/,'')}.rb") + rescue Exception + @logger.report("no plugin file #{file} for #{name}") + else + @plugins.push(name) + end + else + @plugins.push(name) + end + return self + end + + def reader(name, data=[]) + if @plugins.include?(name) then + begin + eval("#{name}").reader(@logger,data.flatten) + rescue Exception + report("reading") + end + else + @logger.report("no plugin #{name}") + end + end + + def readers(data=[]) + @plugins.each do |p| + reader(p,data.flatten) + end + end + + def writers(handle) + @plugins.each do |p| + begin + eval("#{p}").writer(@logger,handle) + rescue Exception + report("writing") + end + end + end + + def processors + @plugins.each do |p| + begin + eval("#{p}").processor(@logger) + rescue Exception + report("processing") + end + end + end + + def finalizers + @plugins.each do |p| + begin + eval("#{p}").finalizer(@logger) + rescue Exception + report("finalizing") + end + end + end + + end + + class Sorter + + @@downcase = true + + def initialize(max=12) + @rep, @map, @exp, @div = Hash.new, Hash.new, Hash.new, Hash.new + @max = max + @rexa, @rexb = nil, nil + end + + def replacer(from,to='') # and expand + @max = [@max,to.length+1].max if to + @rep[from.escaped] = to || '' + end + + # sorter.reducer('ch', 'c') + # sorter.reducer('ij', 'y') + + def reducer(from,to='') + @max = [@max,to.length+1].max if to + @map[from] = to || '' + end + + # sorter.expander('aeligature', 'ae') + # sorter.expander('ijligature', 'y') + + def expander(from,to=nil) + to = converted(to) # not from !!! + @max = [@max,to.length+1].max if to + @exp[from] = to || from || '' + end + + def division(from,to=nil) + from, to = converted(from), converted(to) + @max = [@max,to.length+1].max if to + @div[from] = to || from || '' + end + + # shortcut("\\ab\\cd\\e\\f", 'iacute') + # shortcut("\\\'\\i", 'iacute') + # shortcut("\\\'i", 'iacute') + # shortcut("\\\"e", 'ediaeresis') + # shortcut("\\\'o", 'oacute') + + def hextoutf(str) + str.gsub(/^(0x[A-F\d]+)$/) do + [$1.hex()].pack("U") + end + end + + def shortcut(from,to) + from = hextoutf(from) + replacer(from,to) + expander(to) + end + + def prepare + if @rep.size > 0 then + @rexa = /(#{@rep.keys.join('|')})/ # o + else + @rexa = nil + end + if @map.size > 0 then + # watch out, order of match matters + if @@downcase then + @rexb = /(\\[a-zA-Z]+|#{@map.keys.join('|')}|.)\s*/i # o + else + @rexb = /(\\[a-zA-Z]+|#{@map.keys.join('|')}|.)\s*/ # o + end + else + if @@downcase then + @rexb = /(\\[a-zA-Z]+|.)\s*/io + else + @rexb = /(\\[a-zA-Z]+|.)\s*/o + end + end + if false then + @exp.keys.each do |e| + @exp[e].downcase! + end + end + end + + def replace(str) + if @rexa then + str.gsub(@rexa) do + @rep[$1.escaped] + end + else + str + end + end + + def normalize(str) + # replace(str).gsub(/ +/,' ') + replace(str).gsub(/\s\s+/," \\space") + end + + def tokenize(str) + if str then + str.gsub(/\\strchr\{(.*?)\}/o) do "\\#{$1}" end + else + "" + end + end + + def remap(str) + s = str.dup + if true then # numbers are treated special + s.gsub!(/(\d+)/o) do + $1.rjust(10,'a') # rest is b .. k + end + end + if @rexa then + s.gsub!(@rexa) do + @rep[$1.escaped] + end + end + if @rexb then + s.gsub!(@rexb) do + token = $1.sub(/\\/o, '') + if @@downcase then + token.downcase! + end + if @exp.key?(token) then + @exp[token].ljust(@max,' ') + elsif @map.key?(token) then + @map[token].ljust(@max,' ') + else + '' + end + end + end + s + end + + def preset(shortcuts=[],expansions=[],reductions=[],divisions=[],language='') + 'a'.upto('z') do |c| expander(c) ; division(c) end + 'A'.upto('Z') do |c| expander(c) ; division(c) end + expander('1','b') ; expander('2','c') ; expander('3','e') ; expander('4','f') + expander('5','g') ; expander('6','h') ; expander('7','i') ; expander('8','i') + expander('9','j') ; expander('0','a') ; expander('-','-') ; + shortcuts.each do |s| shortcut(s[1],s[2]) if s[0] == '' || s[0] == language end + expansions.each do |e| expander(e[1],e[2]) if e[0] == '' || e[0] == language end + reductions.each do |r| reducer(r[1],r[2]) if r[0] == '' || r[0] == language end + divisions.each do |d| division(d[1],d[2]) if d[0] == '' || d[0] == language end + end + + def simplify(str) + s = str.dup + # ^^ + # s.gsub!(/\^\^([a-f0-9][a-f0-9])/o, $1.hex.chr) + # \- || + s.gsub!(/(\\\-|\|\|)/o) do '-' end + # {} + s.gsub!(/\{\}/o) do '' end + # <*..> (internal xml entity) + s.gsub!(/<\*(.*?)>/o) do $1 end + # entities + s.gsub!(/\\getXMLentity\s*\{(.*?)\}/o) do $1 end + # elements + s.gsub!(/\<.*?>/o) do '' end + # what to do with xml and utf-8 + # \"e etc + # unknown \cs + s.gsub!(/\\[a-zA-Z][a-zA-Z]+\s*\{(.*?)\}/o) do $1 end + return s + end + + def getdivision(str) + @div[str] || str + end + + def division?(str) + @div.key?(str) + end + + private + + def converted(str) + if str then + # puts str + str.gsub(/([\+\-]*\d+)/o) do + n = $1.to_i + if n > 0 then + 'z'*n + elsif n < 0 then + '-'*(-n) # '-' precedes 'a' + else + '' + end + end + else + nil + end + end + + end + + class Plugin + + module MyFiles + + @@files, @@temps = Hash.new, Hash.new + + def MyFiles::reset(logger) + @@files, @@temps = Hash.new, Hash.new + end + + def MyFiles::reader(logger,data) + case data[0] + when 'b', 'e' then + @@files[data[1]] = (@@files[data[1]] ||0) + 1 + when 't' then # temporary file + @@temps[data[1]] = (@@temps[data[1]] ||0) + 1 + end + end + + def MyFiles::writer(logger,handle) + handle << logger.banner("loaded files: #{@@files.size}") + @@files.keys.sort.each do |k| + handle << "% > #{k} #{@@files[k]/2}\n" + end + handle << logger.banner("temporary files: #{@@temps.size}") + @@temps.keys.sort.each do |k| + handle << "% > #{k} #{@@temps[k]}\n" + end + end + + def MyFiles::processor(logger) + @@files.keys.sort.each do |k| + unless (@@files[k] % 2) == 0 then + logger.report("check loading of file '#{k}', begin/end problem") + end + end + @@temps.keys.sort.each do |k| + # logger.report("temporary file '#{k}' can be deleted") + end + end + + def MyFiles::finalizer(logger) + end + + end + + end + + class Plugin + + module MyCommands + + @@commands = [] + + def MyCommands::reset(logger) + @@commands = [] + end + + def MyCommands::reader(logger,data) + @@commands.push(data.shift+data.collect do |d| "\{#{d}\}" end.join) + end + + def MyCommands::writer(logger,handle) + handle << logger.banner("commands: #{@@commands.size}") + @@commands.each do |c| + handle << "#{c}%\n" + end + end + + def MyCommands::processor(logger) + end + + def MyCommands::finalizer(logger) + end + + end + + end + + class Plugin + + module MyExtras + + @@programs = [] + + def MyExtras::reset(logger) + @@programs = [] + end + + def MyExtras::reader(logger,data) + case data[0] + when 'p' then + @@programs.push(data[1]) if data[0] + end + end + + def MyExtras::writer(logger,handle) + handle << logger.banner("programs: #{@@programs.size}") + @@programs.each_with_index do |cmd, p| + handle << "% #{p+1} (#{cmd})\n" + end + end + + def MyExtras::processor(logger) + @@programs.each do |p| + # cmd = @@programs[p.to_i] + # logger.report("running #{cmd}") + # system(cmd) + end + end + + def MyExtras::finalizer(logger) + unless (ENV["CTX.TEXUTIL.EXTRAS"] =~ /^(no|off|false|0)$/io) || (ENV["CTX_TEXUTIL_EXTRAS"] =~ /^(no|off|false|0)$/io) then + @@programs.each do |cmd| + logger.report("running #{cmd}") + system(cmd) + end + end + end + + end + + end + + class Plugin + + module MySynonyms + + class Synonym + + @@debug = false + + def initialize(t, c, k, d) + @type, @command, @key, @sortkey, @data = t, c, k, c, d + end + + attr_reader :type, :command, :key, :data + attr_reader :sortkey + attr_writer :sortkey + + # def build(sorter) + # if @key then + # @sortkey = sorter.normalize(sorter.tokenize(@sortkey)) + # @sortkey = sorter.remap(sorter.simplify(@key.downcase)) # ?? + # if @sortkey.empty? then + # @sortkey = sorter.remap(@command.downcase) + # end + # else + # @key = "" + # @sortkey = "" + # end + # end + + def build(sorter) + if @sortkey and not @sortkey.empty? then + @sortkey = sorter.normalize(sorter.tokenize(@sortkey)) + @sortkey = sorter.remap(sorter.simplify(@sortkey.downcase)) # ?? + end + if not @sortkey or @sortkey.empty? then + @sortkey = sorter.normalize(sorter.tokenize(@key)) + @sortkey = sorter.remap(sorter.simplify(@sortkey.downcase)) # ?? + end + if not @sortkey or @sortkey.empty? then + @sortkey = @key.dup + end + end + + def <=> (other) + @sortkey <=> other.sortkey + end + + def Synonym.flush(list,handle) + if @@debug then + list.each do |entry| + handle << "% [#{entry.sortkey}]\n" + end + end + list.each do |entry| + handle << "\\synonymentry{#{entry.type}}{#{entry.command}}{#{entry.key}}{#{entry.data}}%\n" + end + end + + end + + @@synonyms = Hash.new + @@sorter = Hash.new + @@languages = Hash.new + + def MySynonyms::reset(logger) + @@synonyms = Hash.new + @@sorter = Hash.new + @@languages = Hash.new + end + + def MySynonyms::reader(logger,data) + case data[0] + when 'e' then + @@synonyms[data[1]] = Array.new unless @@synonyms.key?(data[1]) + @@synonyms[data[1]].push(Synonym.new(data[1],data[2],data[3],data[4])) + when 'l' then + @@languages[data[1]] = data[2] || '' + end + end + + def MySynonyms::writer(logger,handle) + if @@synonyms.size > 0 then + @@synonyms.keys.sort.each do |s| + handle << logger.banner("synonyms: #{s} #{@@synonyms[s].size}") + Synonym.flush(@@synonyms[s],handle) + end + end + end + + def MySynonyms::processor(logger) + @@synonyms.keys.each do |s| + @@sorter[s] = Sorter.new + @@sorter[s].preset( + eval("MyKeys").shortcuts, + eval("MyKeys").expansions, + eval("MyKeys").reductions, + eval("MyKeys").divisions, + @@languages[s] || '') + @@sorter[s].prepare + @@synonyms[s].each_index do |i| + @@synonyms[s][i].build(@@sorter[s]) + end + @@synonyms[s] = @@synonyms[s].sort + end + end + + def MySynonyms::finalizer(logger) + end + + end + + end + + class Plugin + + module MyRegisters + + class Register + + @@specialsymbol = "\000" + @@specialbanner = "" # \\relax" + + @@debug = false + + @@howto = /^(.*?)\:\:(.*)$/o + @@split = ' && ' + + def initialize(state, t, l, k, e, s, p, r) + @state, @type, @location, @key, @entry, @seetoo, @page, @realpage = state, t, l, k, e, s, p, r + if @key =~ @@howto then @pagehowto, @key = $1, $2 else @pagehowto = '' end + if @entry =~ @@howto then @texthowto, @entry = $1, $2 else @texthowto = '' end + @key = @entry.dup if @key.empty? + @sortkey = @key.dup + @nofentries, @nofpages = 0, 0 + @normalizeentry = false + end + + attr_reader :state, :type, :location, :key, :entry, :seetoo, :page, :realpage, :texthowto, :pagehowto + attr_reader :sortkey + attr_writer :sortkey + + def build(sorter) + # @entry, @key = sorter.normalize(@entry), sorter.normalize(sorter.tokenize(@key)) + @entry = sorter.normalize(sorter.tokenize(@entry)) if @normalizeentry + @key = sorter.normalize(sorter.tokenize(@key)) + if false then + @entry, @key = [@entry, @key].collect do |target| + # +a+b+c &a&b&c a+b+c a&b&c + case target[0,1] + when '&' then target = target.sub(/^./o,'').gsub(/([^\\])\&/o) do "#{$1}#{@@split}" end + when '+' then target = target.sub(/^./o,'').gsub(/([^\\])\+/o) do "#{$1}#{@@split}" end + else target = target .gsub(/([^\\])[\&\+]/o) do "#{$1}#{@@split}" end + end + # {a}{b}{c} + # if target =~ /^\{(.*)\}$/o then + # $1.split(/\} \{/o).join(@@split) # space between } { is mandate + # else + target + # end + end + else + # @entry, @key = cleanupsplit(@entry), cleanupsplit(@key) + @entry, @key = cleanupsplit(@entry), xcleanupsplit(@key) + end + @sortkey = sorter.simplify(@key) + # special = @sortkey =~ /^([^a-zA-Z\\])/o + special = @sortkey =~ /^([\`\~\!\@\#\$\%\^\&\*\(\)\_\-\+\=\{\}\[\]\:\;\"\'\|\<\,\>\.\?\/\d])/o + @sortkey = @sortkey.split(@@split).collect do |c| sorter.remap(c) end.join(@@split) + if special then + @sortkey = "#{@@specialsymbol}#{@sortkey}" + end + if @realpage == 0 then + @realpage = 999999 + end + @sortkey = [ + @sortkey.downcase, + @sortkey, + @entry, + @texthowto.ljust(10,' '), + # @state, # no, messes up things + (@realpage.to_s || '').rjust(6,' ').gsub(/0/,' '), + # (@realpage ||'').rjust(6,' '), + @pagehowto + ].join(@@split) + end + + def cleanupsplit(target) + # +a+b+c &a&b&c a+b+c a&b&c + case target[0,1] + when '&' then target.sub(/^./o,'').gsub(/([^\\])\&/o) do "#{$1}#{@@split}" end + when '+' then target.sub(/^./o,'').gsub(/([^\\])\+/o) do "#{$1}#{@@split}" end + else target .gsub(/([^\\])[\&\+]/o) do "#{$1}#{@@split}" end + end + end + + def xcleanupsplit(target) # +a+b+c &a&b&c a+b+c a&b&c + t = Array.new + case target[0,1] + when '&' then + t = target.sub(/^./o,'').split(/([^\\])\&/o) + when '+' then + t = target.sub(/^./o,'').split(/([^\\])\+/o) + else + # t = target.split(/([^\\])[\&\+]/o) + # t = target.split(/[\&\+]/o) + t = target.split(/(?!\\)[\&\+]/o) # lookahead + end + if not t[1] then t[1] = " " end # we need some entry else we get subentries first + if not t[2] then t[2] = " " end # we need some entry else we get subentries first + if not t[3] then t[3] = " " end # we need some entry else we get subentries first + return t.join(@@split) + end + def <=> (other) + @sortkey <=> other.sortkey + end + + # more module like + + @@savedhowto, @@savedfrom, @@savedto, @@savedentry = '', '', '', '', '' + @@collapse = false + + def Register.flushsavedline(handle) + if @@collapse && ! @@savedfrom.empty? then + if ! @@savedto.empty? then + handle << "\\registerfrom#{@@savedfrom}%" + handle << "\\registerto#{@@savedto}%" + else + handle << "\\registerpage#{@@savedfrom}%" + end + end + @@savedhowto, @@savedfrom, @@savedto, @@savedentry = '', '', '', '' + end + + def Register.flush(list,handle,sorter) + # a bit messy, quite old mechanism, maybe some day ... + # alphaclass can go, now flushed per class + if list.size > 0 then + @nofentries, @nofpages = 0, 0 + current, previous, howto = Array.new, Array.new, Array.new + lastpage, lastrealpage = '', '' + alphaclass, alpha = '', '' + @@savedhowto, @@savedfrom, @@savedto, @@savedentry = '', '', '', '' + if @@debug then + list.each do |entry| + handle << "% [#{entry.sortkey.gsub(/#{@@split}/o,'] [')}]\n" + end + end + list.each do |entry| +# puts(entry.sortkey.gsub(/\s+/,"")) + if entry.sortkey =~ /^(\S+)/o then + if sorter.division?($1) then + testalpha = sorter.getdivision($1) + else + testalpha = entry.sortkey[0,1].downcase + end + else + testalpha = entry.sortkey[0,1].downcase + end + if (testalpha != alpha.downcase) || (alphaclass != entry.class) then + alpha = testalpha + alphaclass = entry.class + if alpha != ' ' then + flushsavedline(handle) + if alpha =~ /^[a-zA-Z]$/o then + character = alpha.dup + elsif alpha == @@specialsymbol then + character = @@specialbanner + elsif alpha.length > 1 then + # character = "\\getvalue\{#{alpha}\}" + character = "\\#{alpha}" + else + character = "\\unknown" + end + handle << "\\registerentry{#{entry.type}}{#{character}}%\n" + end + end + current = [entry.entry.split(@@split),'','','',''].flatten + howto = current.collect do |e| + e + '::' + entry.texthowto + end + if howto[0] == previous[0] then + current[0] = '' + else + previous[0] = howto[0].dup + previous[1] = '' + previous[2] = '' + previous[3] = '' + end + if howto[1] == previous[1] then + current[1] = '' + else + previous[1] = howto[1].dup + previous[2] = '' + previous[3] = '' + end + if howto[2] == previous[2] then + current[2] = '' + else + previous[2] = howto[2].dup + previous[3] = '' + end + if howto[3] == previous[3] then + current[3] = '' + else + previous[3] = howto[3].dup + end + copied = false + unless current[0].empty? then + Register.flushsavedline(handle) + handle << "\\registerentrya{#{entry.type}}{#{current[0]}}%\n" + copied = true + end + unless current[1].empty? then + Register.flushsavedline(handle) + handle << "\\registerentryb{#{entry.type}}{#{current[1]}}%\n" + copied = true + end + unless current[2].empty? then + Register.flushsavedline(handle) + handle << "\\registerentryc{#{entry.type}}{#{current[2]}}%\n" + copied = true + end + unless current[3].empty? then + Register.flushsavedline(handle) + handle << "\\registerentryd{#{entry.type}}{#{current[3]}}%\n" + copied = true + end + @nofentries += 1 if copied + # if entry.realpage.to_i == 0 then + if entry.realpage.to_i == 999999 then + Register.flushsavedline(handle) + handle << "\\registersee{#{entry.type}}{#{entry.pagehowto},#{entry.texthowto}}{#{entry.seetoo}}{#{entry.page}}%\n" ; + lastpage, lastrealpage = entry.page, entry.realpage + copied = false # no page ! + elsif @@savedhowto != entry.pagehowto and ! entry.pagehowto.empty? then + @@savedhowto = entry.pagehowto + end + # beware, we keep multiple page entries per realpage because of possible prefix usage + if copied || ! ((lastpage == entry.page) && (lastrealpage == entry.realpage)) then + nextentry = "{#{entry.type}}{#{previous[0]}}{#{previous[1]}}{#{previous[2]}}{#{previous[3]}}{#{entry.pagehowto},#{entry.texthowto}}" + savedline = "{#{entry.type}}{#{@@savedhowto},#{entry.texthowto}}{#{entry.location}}{#{entry.page}}{#{entry.realpage}}" + if entry.state == 1 then # from + Register.flushsavedline(handle) + handle << "\\registerfrom#{savedline}%\n" + elsif entry.state == 3 then # to + Register.flushsavedline(handle) + handle << "\\registerto#{savedline}%\n" + @@savedhowto = '' # test + elsif @@collapse then + if savedentry != nextentry then + savedFrom = savedline + else + savedTo, savedentry = savedline, nextentry + end + else + handle << "\\registerpage#{savedline}%\n" + @@savedhowto = '' # test + end + @nofpages += 1 + lastpage, lastrealpage = entry.page, entry.realpage + end + end + Register.flushsavedline(handle) + end + end + + end + + @@registers = Hash.new + @@sorter = Hash.new + @@languages = Hash.new + + def MyRegisters::reset(logger) + @@registers = Hash.new + @@sorter = Hash.new + @@languages = Hash.new + end + + def MyRegisters::reader(logger,data) + case data[0] + when 'f' then + @@registers[data[1]] = Array.new unless @@registers.key?(data[1]) + @@registers[data[1]].push(Register.new(1,data[1],data[2],data[3],data[4],nil,data[5],data[6])) + when 'e' then + @@registers[data[1]] = Array.new unless @@registers.key?(data[1]) + @@registers[data[1]].push(Register.new(2,data[1],data[2],data[3],data[4],nil,data[5],data[6])) + when 't' then + @@registers[data[1]] = Array.new unless @@registers.key?(data[1]) + @@registers[data[1]].push(Register.new(3,data[1],data[2],data[3],data[4],nil,data[5],data[6])) + when 's' then + @@registers[data[1]] = Array.new unless @@registers.key?(data[1]) + # was this but wrong sort order (4,data[1],data[2],data[3],data[4],data[5],data[6],nil)) + @@registers[data[1]].push(Register.new(4,data[1],data[2],data[3],data[4],data[5],data[6],0)) + when 'l' then + @@languages[data[1]] = data[2] || '' + end + end + + def MyRegisters::writer(logger,handle) + if @@registers.size > 0 then + @@registers.keys.sort.each do |s| + handle << logger.banner("registers: #{s} #{@@registers[s].size}") + Register.flush(@@registers[s],handle,@@sorter[s]) + # report("register #{@@registers[s].class}: #{@@registers[s].@nofentries} entries and #{@@registers[s].@nofpages} pages") + end + end + end + + def MyRegisters::processor(logger) + @@registers.keys.each do |s| + @@sorter[s] = Sorter.new + @@sorter[s].preset( + eval("MyKeys").shortcuts, + eval("MyKeys").expansions, + eval("MyKeys").reductions, + eval("MyKeys").divisions, + @@languages[s] || '') + @@sorter[s].prepare + @@registers[s].each_index do |i| + @@registers[s][i].build(@@sorter[s]) + end + # @@registers[s].uniq! + @@registers[s] = @@registers[s].sort + end + end + + def MyRegisters::finalizer(logger) + end + + end + + end + + class Plugin + + module MyPlugins + + @@plugins = nil + + def MyPlugins::reset(logger) + @@plugins = nil + end + + def MyPlugins::reader(logger,data) + @@plugins = Plugin.new(logger) unless @@plugins + case data[0] + when 'r' then + logger.report("registering plugin #{data[1]}") + @@plugins.register(data[1],data[2]) + when 'd' then + begin + @@plugins.reader(data[1],data[2,data.length-1]) + rescue + @@plugins.reader(data[1],['error']) + end + end + end + + def MyPlugins::writer(logger,handle) + @@plugins.writers(handle) if @@plugins + end + + def MyPlugins::processor(logger) + @@plugins.processors if @@plugins + end + + def MyPlugins::finalizer(logger) + @@plugins.finalizers if @@plugins + end + + end + + end + + class Plugin + + module MyKeys + + @@shortcuts = Array.new + @@expansions = Array.new + @@reductions = Array.new + @@divisions = Array.new + + def MyKeys::shortcuts + @@shortcuts + end + def MyKeys::expansions + @@expansions + end + def MyKeys::reductions + @@reductions + end + def MyKeys::divisions + @@divisions + end + + def MyKeys::reset(logger) + @@shortcuts = Array.new + @@expansions = Array.new + @@reductions = Array.new + end + + def MyKeys::reader(logger,data) + key = data.shift + # grp = data.shift # language code, todo + case key + when 's' then @@shortcuts.push(data) + when 'e' then @@expansions.push(data) + when 'r' then @@reductions.push(data) + when 'd' then @@divisions.push(data) + end + end + + def MyKeys::writer(logger,handle) + end + + def MyKeys::processor(logger) + logger.report("shortcuts : #{@@shortcuts.size}") # logger.report(@@shortcuts.inspect) + logger.report("expansions: #{@@expansions.size}") # logger.report(@@expansions.inspect) + logger.report("reductions: #{@@reductions.size}") # logger.report(@@reductions.inspect) + logger.report("divisions : #{@@divisions.size}") # logger.report(@@divisions.inspect) + end + + def MyKeys::finalizer(logger) + end + + end + + end + + class Converter + + def initialize(logger=nil) + if @logger = logger then + def report(str) + @logger.report(str) + end + def banner(str) + @logger.banner(str) + end + else + @logger = self + def report(str) + puts(str) + end + def banner(str) + puts(str) + end + end + @filename = 'texutil' + @fatalerror = false + @plugins = Plugin.new(@logger) + ['MyFiles', 'MyCommands', 'MySynonyms', 'MyRegisters', 'MyExtras', 'MyPlugins', 'MyKeys'].each do |p| + @plugins.register(p) + end + end + + def loaded(filename) + begin + tuifile = File.suffixed(filename,'tui') + if FileTest.file?(tuifile) then + report("parsing file #{tuifile}") + if f = File.open(tuifile,'rb') then + f.each do |line| + case line.chomp + when /^f (.*)$/o then @plugins.reader('MyFiles', $1.splitdata) + when /^c (.*)$/o then @plugins.reader('MyCommands', [$1]) + when /^e (.*)$/o then @plugins.reader('MyExtras', $1.splitdata) + when /^s (.*)$/o then @plugins.reader('MySynonyms', $1.splitdata) + when /^r (.*)$/o then @plugins.reader('MyRegisters',$1.splitdata) + when /^p (.*)$/o then @plugins.reader('MyPlugins', $1.splitdata) + when /^x (.*)$/o then @plugins.reader('MyKeys', $1.splitdata) + when /^r (.*)$/o then # nothing, not handled here + else + # report("unknown entry #{line[0,1]} in line #{line.chomp}") + end + end + f.close + end + else + report("unable to locate #{tuifile}") + end + rescue + report("fatal error in parsing #{tuifile}") + @filename = 'texutil' + else + @filename = filename + end + end + + def processed + @plugins.processors + return true # for the moment + end + + def saved(filename=@filename) + if @fatalerror then + report("fatal error, no tuo file saved") + return false + else + begin + if f = File.open(File.suffixed(filename,'tuo'),'w') then + @plugins.writers(f) + f << "\\endinput\n" + f.close + end + rescue + report("fatal error when saving file (#{$!})") + return false + else + report("tuo file saved") + return true + end + end + end + + def finalized + @plugins.finalizers + @plugins.resets + return true # for the moment + end + + def reset + @plugins.resets + end + + end + +end diff --git a/scripts/context/ruby/base/tool.rb b/scripts/context/ruby/base/tool.rb new file mode 100644 index 000000000..abf0d5ed0 --- /dev/null +++ b/scripts/context/ruby/base/tool.rb @@ -0,0 +1,291 @@ +# module : base/tool +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +require 'timeout' +require 'socket' +require 'rbconfig' + +module Tool + + $constructedtempdir = '' + + def Tool.constructtempdir(create,mainpath='',fallback='') + begin + mainpath += '/' unless mainpath.empty? + timeout(5) do + begin + t = Time.now + u = t.usec.to_s % [1..2] [0..3] + pth = t.strftime("#{mainpath}%Y%m%d-%H%M%S-#{u}-#{Process.pid}") + # + # problems with 1.9 + # + # if pth == $constructedtempdir + # # sleep(0.01) + # retry + # end + pth == $constructedtempdir + # + Dir.mkdir(pth) if create + $constructedtempdir = pth + return pth + rescue + # sleep(0.01) + retry + end + end + rescue TimeoutError + # ok + rescue + # ok + end + unless fallback.empty? + begin + pth = "#{mainpath}#{fallback}" + mkdir(pth) if create + $constructedtempdir = path + return pth + rescue + return '.' + end + else + return '.' + end + + end + + def Tool.findtempdir(*vars) + constructtempdir(false,*vars) + end + + def Tool.maketempdir(*vars) + constructtempdir(true,*vars) + end + + # print maketempdir + "\n" + # print maketempdir + "\n" + # print maketempdir + "\n" + # print maketempdir + "\n" + # print maketempdir + "\n" + + + def Tool.ruby_platform + case RUBY_PLATFORM + when /(mswin|bccwin|mingw|cygwin)/i then 'mswin' + when /(linux)/i then 'linux' + when /(netbsd|unix)/i then 'unix' + when /(darwin|rhapsody|nextstep)/i then 'macosx' + else 'unix' + end + end + + $defaultlineseparator = $/ # $RS in require 'English' + + def Tool.file_platform(filename) + + begin + if f = open(filename,'rb') then + str = f.read(4000) + str.gsub!(/(.*?)\%\!PS/mo, "%!PS") # don't look into preamble crap + f.close + nn = str.count("\n") + nr = str.count("\r") + if nn>nr then + return 2 + elsif nn0 then + return buffer.slice(0..length-1) + else + # when the path or file does not exist, nothing is returned + # so we try to handle the path separately from the basename + basename = File.basename(filename) + pathname = File.dirname(filename) + length = filemethod.call(pathname,buffer,260) + if length>0 then + return buffer.slice(0..length-1) + '/' + basename + else + return filename + end + end + else + # no danger + return filename + end + end + + def Tool.shortpathname(filename) + dowith_pathname(filename,GetShortPathName) + end + + def Tool.longpathname(filename) + dowith_pathname(filename,GetLongPathName) + end + + else + + def Tool.shortpathname(filename) + filename + end + + def Tool.longpathname(filename) + filename + end + + end + + # print shortpathname("C:/Program Files/ABBYY FineReader 6.0/matrix.str")+ "!\n" + # print shortpathname("C:/Program Files/ABBYY FineReader 6.0/matrix.strx")+ "!\n" + + def Tool.checksuffix(old) + + return old unless FileTest.file?(old) + + new = old + + unless new =~ /\./io # no suffix + f = open(filename,'rb') + if str = f.gets + case str + when /^\%\!PS/io + # logging.report(filename, 'analyzed as EPS') + new = new + '.eps' + when /^\%PDF/io + # logging.report(filename, 'analyzed as PDF') + new = new + '.pdf' + else + # logging.report(filename, 'fallback as TIF') + new = new + '.tif' + end + end + f.close + end + + new.sub!(/\.jpeg$/io) do + '.jpg' + end + new.sub!(/\.tiff$/io) do + '.tif' + end + new.sub!(/\.ai$/io) do + '.eps' + end + new.sub!(/\.ai([a-z0-9]*)$/io) do + '-' + $1 + '.eps' + end + new + + end + + def Tool.cleanfilename(old,logging=nil) + + return old if not FileTest.file?(old) + + new = checksuffix(simplefilename(old)) + unless new == old + begin # bugged, should only be name, not path + File.rename(old,new) + logging.report("renaming fuzzy name #{old} to #{new}") unless logging + return old + rescue + logging.report("unable to rename fuzzy name #{old} to #{new}") unless logging + end + end + return new + + end + + def Tool.servername + host = Socket::gethostname + begin + Socket::gethostbyname(host)[0] + rescue + host + end + end + + # print file_platform(ARGV[0]) + +end diff --git a/scripts/context/ruby/base/variables.rb b/scripts/context/ruby/base/variables.rb new file mode 100644 index 000000000..403b57716 --- /dev/null +++ b/scripts/context/ruby/base/variables.rb @@ -0,0 +1,132 @@ +# module : base/variables +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# ['base/tool','tool'].each do |r| begin require r ; rescue Exception ; else break ; end ; end + +require 'base/tool' + +class Hash + + def nothing?(id) + ! self[id] || self[id].empty? + end + + def subset(pattern) + h = Hash.new + p = pattern.gsub(/([\.\:\-])/) do "\\#{$1}" end + r = /^#{p}/ + self.keys.each do |k| + h[k] = self[k].dup if k =~ r + end + return h + end + +end + +class ExtendedHash < Hash + + @@re_var_a = /\%(.*?)\%/ + @@re_var_b = /\$\((.*?)\)/ + + def set(key,value='',resolve=true) + if value then + self[key] = if resolve then resolved(value.to_s) else value.to_s end + else + self[key] = '' + end + end + + def replace(key,value='') + self[key] = value if self?(key) + end + + def get(key,default='') + if self.key?(key) then self[key] else default end + end + + def true?(key) + self[key] =~ /^(yes|on|true|enable|enabled|y|start)$/io rescue false + end + + def resolved(str) + begin + str.to_s.gsub(@@re_var_a) do + self[$1] || '' + end.gsub(@@re_var_b) do + self[$1] || '' + end + rescue + str.to_s rescue '' + end + end + + def check(key,default='') + if self.key?(key) then + if self[key].empty? then self[key] = (default || '') end + else + self[key] = (default || '') + end + end + + def checked(key,default='') + if self.key?(key) then + if self[key].empty? then default else self[key] end + else + default + end + end + + def empty?(key) + self[key].empty? + end + + # def downcase(key) + # self[key].downcase! + # end + +end + +# the next one is obsolete so we need to replace things + +module Variables + + def setvariable(key,value='') + @variables[key] = value + end + + def replacevariable(key,value='') + @variables[key] = value if @variables.key?(key) + end + + def getvariable(key,default='') + if @variables.key?(key) then @variables[key] else default end + end + + def truevariable(key) + @variables[key] =~ /^(yes|on|true)$/io rescue false + end + + def checkedvariable(str,default='') + if @variables.key?(key) then + if @variables[key].empty? then default else @variables[key] end + else + default + end + end + + def report(*str) + @logger.report(*str) + end + + def debug(*str) + @logger.debug(str) + end + +end -- cgit v1.2.3