summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorHans Hagen <pragma@wxs.nl>2006-05-10 23:26:00 +0200
committerHans Hagen <pragma@wxs.nl>2006-05-10 23:26:00 +0200
commitb2b303273a3e05aaea01777309356ae8aeb7a8bf (patch)
tree517313f22051964dfd783868825958d7ce043fac /scripts
parent88eab06af697b8d0fd8ef6d7762eae4901b191d2 (diff)
downloadcontext-b2b303273a3e05aaea01777309356ae8aeb7a8bf.tar.gz
stable 2006.05.10 23:26
Diffstat (limited to 'scripts')
-rw-r--r--scripts/context/perl/mptopdf.pl8
-rw-r--r--scripts/context/ruby/base/tex.rb25
-rw-r--r--scripts/context/ruby/base/texutil.rb37
-rw-r--r--scripts/context/ruby/ctxtools.rb73
-rw-r--r--scripts/context/ruby/rlxtools.rb4
-rw-r--r--scripts/context/ruby/rscortool.rb63
-rw-r--r--scripts/context/ruby/rsfiltool.rb340
-rw-r--r--scripts/context/ruby/rslibtool.rb114
-rw-r--r--scripts/context/ruby/runtools.rb14
-rw-r--r--scripts/context/ruby/texexec.rb36
-rw-r--r--scripts/context/ruby/texmfstart.rb108
-rw-r--r--scripts/context/ruby/www/admin.rb215
-rw-r--r--scripts/context/ruby/www/common.rb80
-rw-r--r--scripts/context/ruby/www/dir.rb155
-rw-r--r--scripts/context/ruby/www/exa.rb386
-rw-r--r--scripts/context/ruby/www/lib.rb1391
-rw-r--r--scripts/context/ruby/www/login.rb13
-rw-r--r--scripts/context/ruby/wwwclient.rb677
-rw-r--r--scripts/context/ruby/wwwserver.rb292
-rw-r--r--scripts/context/ruby/wwwwatch.rb464
-rwxr-xr-xscripts/context/stubs/mswin/texmfstart.bat2
-rwxr-xr-xscripts/context/stubs/unix/ctxtools2
-rwxr-xr-xscripts/context/stubs/unix/ctxtools.bat2
-rwxr-xr-xscripts/context/stubs/unix/exatools2
-rwxr-xr-xscripts/context/stubs/unix/exatools.bat2
-rwxr-xr-xscripts/context/stubs/unix/makempy2
-rwxr-xr-xscripts/context/stubs/unix/makempy.bat2
-rwxr-xr-xscripts/context/stubs/unix/mpstools2
-rwxr-xr-xscripts/context/stubs/unix/mpstools.bat2
-rwxr-xr-xscripts/context/stubs/unix/mptopdf2
-rwxr-xr-xscripts/context/stubs/unix/mptopdf.bat2
-rwxr-xr-xscripts/context/stubs/unix/pdftools2
-rwxr-xr-xscripts/context/stubs/unix/pdftools.bat2
-rwxr-xr-xscripts/context/stubs/unix/pstopdf2
-rwxr-xr-xscripts/context/stubs/unix/pstopdf.bat2
-rwxr-xr-xscripts/context/stubs/unix/runtools2
-rwxr-xr-xscripts/context/stubs/unix/runtools.bat2
-rwxr-xr-xscripts/context/stubs/unix/texexec2
-rwxr-xr-xscripts/context/stubs/unix/texexec.bat2
-rwxr-xr-xscripts/context/stubs/unix/texfont2
-rwxr-xr-xscripts/context/stubs/unix/texfont.bat2
-rwxr-xr-xscripts/context/stubs/unix/texmfstart.bat2
-rwxr-xr-xscripts/context/stubs/unix/textools2
-rwxr-xr-xscripts/context/stubs/unix/textools.bat2
-rwxr-xr-xscripts/context/stubs/unix/texutil2
-rwxr-xr-xscripts/context/stubs/unix/texutil.bat2
-rwxr-xr-xscripts/context/stubs/unix/tmftools2
-rwxr-xr-xscripts/context/stubs/unix/tmftools.bat2
-rwxr-xr-xscripts/context/stubs/unix/xmltools2
-rwxr-xr-xscripts/context/stubs/unix/xmltools.bat2
50 files changed, 4430 insertions, 125 deletions
diff --git a/scripts/context/perl/mptopdf.pl b/scripts/context/perl/mptopdf.pl
index b5d4bc15d..f85c36891 100644
--- a/scripts/context/perl/mptopdf.pl
+++ b/scripts/context/perl/mptopdf.pl
@@ -88,17 +88,17 @@ if (($pattern eq '')||($Help)) {
$rest .= " $mplatexswitch" ;
}
if ($MetaFun) {
- $mpbin = 'mpost --progname=mpost --mem=metafun' ;
+ $mpbin = "mpost --progname=mpost --mem=metafun" ;
} else {
- $mpbin = 'mpost --mem=mpost' ;
+ $mpbin = "mpost --mem=mpost" ;
}
} else {
if ($Latex) {
$rest .= " $texlatexswitch" ;
}
- $mpbin = 'texexec --mptex $PassOn' ;
+ $mpbin = "texexec --mptex $PassOn" ;
}
- print "\n$program : running '$command'" ;
+ print "\n$program : running '$mpbin'\n" ;
my $error = system ("$mpbin $rest $pattern") ;
if ($error) {
print "\n$program : error while processing mp file\n" ;
diff --git a/scripts/context/ruby/base/tex.rb b/scripts/context/ruby/base/tex.rb
index 165d370f9..1e1b67f0f 100644
--- a/scripts/context/ruby/base/tex.rb
+++ b/scripts/context/ruby/base/tex.rb
@@ -50,6 +50,10 @@ class Array
end
end
+ def join_path
+ self.join(File::PATH_SEPARATOR)
+ end
+
end
class TEX
@@ -164,16 +168,22 @@ class TEX
@@extrastringvars = []
def booleanvars
- [@@booleanvars,@@extrabooleanvars].flatten
+ [@@booleanvars,@@extrabooleanvars].flatten.uniq
end
def stringvars
- [@@stringvars,@@extrastringvars].flatten
+ [@@stringvars,@@extrastringvars].flatten.uniq
end
def standardvars
- @@standardvars
+ [@@standardvars].flatten.uniq
end
def knownvars
- @@knownvars
+ [@@knownvars].flatten.uniq
+ end
+ def allbooleanvars
+ [@@booleanvars,@@extrabooleanvars].flatten.uniq
+ end
+ def allstringvars
+ [@@stringvars,@@extrastringvars,@@standardvars,@@knownvars].flatten.uniq
end
def setextrastringvars(vars)
@@ -517,7 +527,8 @@ class TEX
end
mpsformats.each do |mpsformat|
report("generating mps format #{mpsformat}")
- command = [quoted(mpsengine),prognameflag(progname),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)].join(' ')
+ # command = [quoted(mpsengine),prognameflag(progname),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)].join(' ')
+ command = [quoted(mpsengine),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)].join(' ')
report(command) if getvariable('verbose')
system(command)
end
@@ -1185,7 +1196,7 @@ class TEX
if mpsengine && mpsformat && progname then
ENV["MPXCOMMAND"] = "0" unless mpx
# command = [quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),tcxflag,runoptions(mpsengine),filename,mpsprocextras(mpsformat)].join(' ')
- command = [quoted(mpsengine),"-progname=mpost",formatflag(mpsengine,mpsformat),tcxflag,runoptions(mpsengine),mpname,mpsprocextras(mpsformat)].join(' ')
+ command = [quoted(mpsengine),formatflag(mpsengine,mpsformat),tcxflag,runoptions(mpsengine),mpname,mpsprocextras(mpsformat)].join(' ')
report(command) if getvariable('verbose')
system(command)
true
@@ -1285,7 +1296,7 @@ class TEX
report("fixing backend map path for #{backend}") if getvariable('verbose')
ENV['backend'] = backend ;
ENV['progname'] = backend unless validtexengine(backend)
- ENV['TEXFONTMAPS'] = ".;\$TEXMF/fonts/map/{#{backend},pdftex,dvips,}//"
+ ENV['TEXFONTMAPS'] = ['.',"\$TEXMF/fonts/map/{#{backend},pdftex,dvips,}//"].join_path
else
report("unable to fix backend map path") if getvariable('verbose')
end
diff --git a/scripts/context/ruby/base/texutil.rb b/scripts/context/ruby/base/texutil.rb
index 0529f0ca1..89f5e5385 100644
--- a/scripts/context/ruby/base/texutil.rb
+++ b/scripts/context/ruby/base/texutil.rb
@@ -51,12 +51,21 @@ class TeXUtil
@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
- @logger.report("fatal error in resetting plugin")
+ report("resetting")
end
else
@logger.report("no plugin #{name}")
@@ -89,7 +98,7 @@ class TeXUtil
begin
eval("#{name}").reader(@logger,data.flatten)
rescue Exception
- @logger.report("fatal error in plugin reader #{name} (#{$!})")
+ report("reading")
end
else
@logger.report("no plugin #{name}")
@@ -107,7 +116,7 @@ class TeXUtil
begin
eval("#{p}").writer(@logger,handle)
rescue Exception
- @logger.report("fatal error in plugin writer #{p} (#{$!})")
+ report("writing")
end
end
end
@@ -117,7 +126,7 @@ class TeXUtil
begin
eval("#{p}").processor(@logger)
rescue Exception
- @logger.report("fatal error in plugin processor #{p} (#{$!})")
+ report("processing")
end
end
end
@@ -127,7 +136,7 @@ class TeXUtil
begin
eval("#{p}").finalizer(@logger)
rescue Exception
- @logger.report("fatal error in plugin finalizer #{p} (#{$!})")
+ report("finalizing")
end
end
end
@@ -211,8 +220,12 @@ class TeXUtil
end
def replace(str)
- str.gsub(@rexa) do
- @rep[$1.escaped]
+ if @rexa then
+ str.gsub(@rexa) do
+ @rep[$1.escaped]
+ end
+ else
+ str
end
end
@@ -226,11 +239,11 @@ class TeXUtil
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 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]
diff --git a/scripts/context/ruby/ctxtools.rb b/scripts/context/ruby/ctxtools.rb
index 2092b346c..2104eebf7 100644
--- a/scripts/context/ruby/ctxtools.rb
+++ b/scripts/context/ruby/ctxtools.rb
@@ -181,7 +181,7 @@ class Commands
if @commandline.option("pipe") then
print version
else
- report("context version: #{version}")
+ report("context version: #{version} (#{filename})")
end
end
@@ -789,10 +789,10 @@ class String
def markbraces
level = 0
self.gsub(/([\{\}])/o) do |chr|
- if chr == '{'
+ if chr == '{' then
level = level + 1
chr = "((+#{level}))"
- elsif chr == '}'
+ elsif chr == '}' then
chr = "((-#{level}))"
level = level - 1
end
@@ -803,13 +803,13 @@ class String
def unmarkbraces
self.gsub(/\(\(\+\d+?\)\)/o) do
"{"
- end.gsub(/\(\(\-\d+?\)\)/o) do
+ end .gsub(/\(\(\-\d+?\)\)/o) do
"}"
end
end
def getargument(pattern)
- if self =~ /(#{pattern})\s*\(\(\+(\d+)\)\)(.*?)\(\(\-\2\)\)/ then # no /o
+ if self =~ /(#{pattern})\s*\(\(\+(\d+)\)\)(.*?)\(\(\-\2\)\)/m then # no /o
return $3
else
return ""
@@ -843,6 +843,7 @@ class Language
@language = language
@filenames = filenames
@remapping = Array.new
+ @demapping = Array.new
@unicode = Hash.new
@encoding = encoding
@data = ''
@@ -867,6 +868,9 @@ class Language
def remap(from, to)
@remapping.push([from,to])
end
+ def demap(from, to)
+ @demapping.push([from,to])
+ end
def load(filenames=@filenames)
begin
@@ -906,6 +910,11 @@ class Language
@data = @data.withargument(what) do |content|
report("converting #{what}")
report("")
+ @demapping.each_index do |i|
+ content.gsub!(@demapping[i][0], @demapping[i][1])
+ end
+ content.gsub!(/\\delete\{.*?\}/o) do '' end
+ content.gsub!(/\\keep\{(.*?)\}/o) do $1 end
done = false
@remapping.each_index do |i|
from, to, m = @remapping[i][0], @remapping[i][1], 0
@@ -1242,24 +1251,24 @@ class Language
remap(/Y/, "[ostroke]")
remap(/Z/, "[aring]")
when 'hu' then
-
+ # nothing
when 'ca' then
- remap(/\\c\{.*?\}/, "")
+ demap(/\\c\{/, "\\delete{")
when 'de', 'deo' then
- remap(/\\c\{.*?\}/, "")
- remap(/\\n\{\}/, "")
+ demap(/\\c\{/, "\\delete{")
+ demap(/\\n\{/, "\\keep{")
remap(/\\3/, "[ssharp]")
remap(/\\9/, "[ssharp]")
remap(/\"a/, "[adiaeresis]")
remap(/\"o/, "[odiaeresis]")
remap(/\"u/, "[udiaeresis]")
when 'fr' then
- remap(/\\n\{\}/, "")
+ demap(/\\n\{/, "\\keep{")
remap(/\\ae/, "[adiaeresis]")
remap(/\\oe/, "[odiaeresis]")
when 'la' then
# \lccode`'=`' somewhere else, todo
- remap(/\\c\{.*?\}/, "")
+ demap(/\\c\{/, "\\delete{")
remap(/\\a\s*/, "[aeligature]")
remap(/\\o\s*/, "[oeligature]")
when 'agr' then
@@ -2202,6 +2211,47 @@ end
class Commands
+ @@re_utf_bom = /^\357\273\277/o # just utf-8
+
+ def disarmutfbom
+
+ if @commandline.arguments.empty? then
+ report("provide filename")
+ else
+ @commandline.arguments.each do |filename|
+ report("checking '#{filename}'")
+ if FileTest.file?(filename) then
+ begin
+ data = IO.read(filename)
+ if data.sub!(@@re_utf_bom,'') then
+ if @commandline.option('force') then
+ if f = File.open(filename,'wb') then
+ f << data
+ f.close
+ report("bom found and removed")
+ else
+ report("bom found and removed, but saving file fails")
+ end
+ else
+ report("bom found, use '--force' to remove it")
+ end
+ else
+ report("no bom found")
+ end
+ rescue
+ report("bom found, but removing it fails")
+ end
+ else
+ report("provide valid filename")
+ end
+ end
+ end
+ end
+
+end
+
+class Commands
+
include CommandBase
def updatecontext
@@ -2303,6 +2353,7 @@ commandline.registeraction('brandfiles' , 'add context copyright notice [
commandline.registeraction('platformize' , 'replace line-endings [--recurse --force] [pattern]')
commandline.registeraction('dependencies' , 'analyze depedencies witin context [--compact] [rootfile]')
commandline.registeraction('updatecontext' , 'download latest version and remake formats')
+commandline.registeraction('disarmutfbom' , 'remove utf bom [==force]')
commandline.registervalue('type','')
diff --git a/scripts/context/ruby/rlxtools.rb b/scripts/context/ruby/rlxtools.rb
index 7962474eb..addfe9894 100644
--- a/scripts/context/ruby/rlxtools.rb
+++ b/scripts/context/ruby/rlxtools.rb
@@ -97,13 +97,13 @@ class Commands
report("processing record #{nofrecords} (#{variables['file'] || 'noname'}: #{variables.size} entries)")
if conversion = variables['conversion'] then
report("testing for conversion #{conversion}")
- if suffix = variables['suffix'] then
+ if suffix.downcase = variables['suffix'].downcase then
if file = variables['file'] then
report("conversion #{conversion} for suffix #{suffix} for file #{file}")
else
report("conversion #{conversion} for suffix #{suffix}")
end
- pattern = "@name='#{conversion}' and @suffix='#{suffix}'"
+ pattern = "@name='#{conversion}' and @suffix='#{suffix.downcase}'"
if steps = REXML::XPath.first(proc.root,"/rl:manipulators/rl:manipulator[#{pattern}]") then
localsteps = steps.deep_clone
['rl:old','rl:new'].each do |tag|
diff --git a/scripts/context/ruby/rscortool.rb b/scripts/context/ruby/rscortool.rb
new file mode 100644
index 000000000..c656fed85
--- /dev/null
+++ b/scripts/context/ruby/rscortool.rb
@@ -0,0 +1,63 @@
+# program : rscortool
+# copyright : PRAGMA Publishing On Demand
+# version : 1.00 - 2002
+# author : Hans Hagen
+#
+# project : eXaMpLe
+# concept : Hans Hagen
+# info : j.hagen@xs4all.nl
+# www : www.pragma-pod.com / www.pragma-ade.com
+
+require 'rexml/document.rb'
+
+class Array
+
+ def downcase
+ self.collect { |l| l.to_s.downcase }
+ end
+
+end
+
+class SortedXML
+
+ def initialize (filename)
+ return nil if not filename or filename.empty? or not test(?e,filename)
+ @data = REXML::Document.new(File.new(filename),
+ {:ignore_whitespace_nodes => :all,
+ :compress_whitespace => :all})
+ end
+
+ def save (filename)
+ # filename += '.xml' unless filename.match(/\..*?$/)
+ filename += '.xml' unless filename =~ /\..*?$/
+ if not filename.empty? and f = open(filename,'w')
+ @data.write(f,0)
+ f.close
+ end
+ end
+
+ def sort
+ keys = REXML::XPath.match(@data.root,"/contacts/contact/@label")
+ return unless keys
+ keys = keys.downcase
+ records = @data.elements.to_a("/contacts/contact")
+ @data.elements.delete_all("/contacts/contact")
+ keys = keys.collect do |l| # prepare numbers
+ l.gsub(/(\d+)/) do |d| sprintf('%05d', d) end
+ end
+ keys.sort.each do |s|
+ @data.root.add_element(records[keys.index(s)])
+ end
+ end
+
+end
+
+def sortfile (filename)
+ c = SortedXML.new(filename)
+ c.sort
+ c.save('test.xml')
+end
+
+exit if ARGV[0] == nil or ARGV[0].empty?
+
+sortfile(ARGV[0])
diff --git a/scripts/context/ruby/rsfiltool.rb b/scripts/context/ruby/rsfiltool.rb
new file mode 100644
index 000000000..f3abfdfc7
--- /dev/null
+++ b/scripts/context/ruby/rsfiltool.rb
@@ -0,0 +1,340 @@
+# program : rsfiltool
+# copyright : PRAGMA Publishing On Demand
+# version : 1.01 - 2002
+# author : Hans Hagen
+#
+# project : eXaMpLe
+# concept : Hans Hagen
+# info : j.hagen@xs4all.nl
+# www : www.pragma-pod.com / www.pragma-ade.com
+
+unless defined? ownpath
+ ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'')
+ $: << ownpath
+end
+
+# --name=a,b,c.xml wordt names [a.xml, b.xml, c.xml]
+# --path=x/y/z/a,b,c.xml wordt [x/y/z/a.xml, x/y/z/b.xml, x/y/z/c.xml]
+
+# todo : split session stuff from xmpl/base into an xmpl/session module and "include xmpl/session" into base and here and ...
+
+require 'ftools'
+require 'xmpl/base'
+require 'xmpl/switch'
+require 'xmpl/request'
+
+session = Example.new('rsfiltool', '1.01', 'PRAGMA POD')
+
+filterprefix = 'rsfil-'
+
+commandline = CommandLine.new
+
+commandline.registerflag('submit')
+commandline.registerflag('fetch')
+commandline.registerflag('report')
+#commandline.registerflag('split')
+commandline.registerflag('stamp')
+commandline.registerflag('silent')
+commandline.registerflag('request')
+commandline.registerflag('nobackup')
+
+commandline.registervalue('filter')
+
+commandline.registervalue('root')
+commandline.registervalue('path')
+commandline.registervalue('name')
+
+commandline.expand
+
+session.set('log.silent',true) if commandline.option('silent')
+
+session.inherit(commandline)
+
+session.identify
+
+# session.exit unless session.loadenvironment
+
+def prepare (session)
+
+ # Normally the system provides the file, but a user can provide the rest; in
+ # order to prevent problems with keying in names, we force lowercase names.
+
+ session.set('option.file',session.get('argument.first')) if session.get('option.file').empty?
+
+ root = session.get('option.root').downcase
+ path = session.get('option.path').downcase
+ name = session.get('option.name').downcase
+ file = session.get('option.file').downcase
+
+ session.error('provide file') if file.empty?
+ session.error('provide root') if root.empty?
+
+ filter = session.get('option.filter').downcase
+ trash = session.get('option.trash').downcase
+
+ trash = '' unless FileTest.directory?(trash)
+
+ if not filter.empty? then
+ begin
+ require filter
+ rescue Exception
+ begin
+ require filterprefix + filter
+ rescue Exception
+ session.error('invalid filter')
+ end
+ end
+ begin
+ if RSFIL::valid?(file) then
+ split = RSFIL::split(file,name)
+ path = if split[0].downcase then split[0] else '' end
+ file = if split[1].downcase then split[1] else '' end
+ name = if split[2].downcase then split[2] else '' end
+ session.report('split result',split.inspect)
+ session.error('unable to split off path') if path.empty?
+ session.error('unable to split off file') if file.empty?
+ session.error('unable to split off name') if name.empty?
+ session.set('option.path',path) if path
+ session.set('option.file',file) if file
+ session.set('option.name',name) if name
+ else
+ session.error('invalid filename', file)
+ unless trash.empty? then
+ File.copy(file,trash + '/' + file)
+ end
+ end
+ rescue
+ session.error('unable to split',file,'with filter',filter)
+ end
+ end
+
+ session.error('provide path') if path.empty?
+
+ session.error('invalid root') unless test(?d,root)
+
+ exit if session.error?
+
+ session.set('fb.filename',file)
+
+ path.gsub!(/\\/o, '/')
+ path.gsub!(/\s/o, '')
+
+ path = root + '/' + path
+
+ # multiple paths
+
+ if path =~ /^(.*)\/(.*?)$/o then
+ prepath = $1
+ postpath = $2
+ paths = postpath.split(/\,/)
+ paths.collect! do |p|
+ prepath + '/' + p
+ end
+ else
+ paths = Array.new
+ paths.push(path)
+ end
+
+ paths.collect! do |p|
+ p.gsub(/[^a-zA-Z0-9\s\-\_\/\.\:]/o, '-')
+ end
+
+ file.gsub!(/\\/o, '/')
+ file.gsub!(/[^a-zA-Z0-9\s\-\_\/\.\:]/o, '-')
+
+# if session.get('option.split')
+# if file =~ /(.*)\.(.*?)$/o
+# path = path + '/' + $1
+# else
+# session.error('nothing to split in filename')
+# end
+# end
+
+ paths.each do |p|
+ begin
+ session.report('creating path', p)
+ File.makedirs(p)
+ rescue
+ session.error('unable to create path', p)
+ end
+ end
+
+ name.gsub!(/\s+/,'')
+
+ # can be a,b,c.exa.saved => a.exa.saved,b.exa.saved,c.exa.saved
+
+ if name =~ /(.*?)\.(.*)$/
+ name = $1
+ suffix = $2
+ names = name.split(/\,/)
+ names.collect! do |n|
+ n + '.' + suffix
+ end
+ name = names.join(',')
+ else
+ names = name.split(/\,/)
+ end
+
+ session.set('fb.path',path)
+ session.set('fb.paths',paths)
+ session.set('fb.name',name)
+ session.set('fb.names',names)
+
+end
+
+def thefullname(path,file,name='')
+
+ filename = file.gsub(/.*?\//, '')
+
+ if name.empty?
+ path + '/' + filename
+ else
+ unless name =~ /\..+$/o # unless name.match(/\..+$/o)
+ if filename =~ /(\..+)$/o # if file.match(/(\..+)$/o)
+ name = name + $1
+ end
+ end
+ path + '/' + name
+ end
+
+end
+
+def submitfile (session)
+
+ filename = session.get('fb.filename')
+ paths = session.get('fb.paths')
+ names = session.get('fb.names')
+
+ paths.each do |path|
+ session.report('submitting path',path)
+ names.each do |name|
+ session.report('submitting file',filename,'to',name)
+ submit(session,path,filename,name)
+ end
+ end
+
+end
+
+def submitlist (session)
+
+ requestname = session.get('fb.filename')
+ paths = session.get('fb.paths')
+
+ if test(?e,requestname)
+ session.report('loading request file', requestname)
+ if request = ExaRequest.new(requestname)
+ filelist = request.files
+ if filelist && (filelist.size > 0)
+ filelist.each do |filename|
+ paths.each do |path|
+ session.report('submitting file from list', filename)
+ submit(session,path,filename,request.naturalname(filename))
+ end
+ end
+ else
+ session.warning('no filelist in', requestname)
+ end
+ else
+ session.warning('unable to load', requestname)
+ end
+ else
+ session.warning('no file', requestname)
+ end
+
+end
+
+def submit (session, path, filename, newname)
+
+ fullname = thefullname(path,newname)
+
+ unless test(?e,filename)
+ session.warning('no file to submit', filename)
+ return
+ end
+
+ begin
+ File.copy(fullname,fullname+'.old') if ! session.get('nobackup') && test(?e,fullname)
+ if test(?e,filename)
+ File.copy(filename,fullname)
+ session.report('submit', filename, 'in', fullname)
+ if session.get('option.stamp')
+ f = open(fullname+'.tim','w')
+ f.puts(Time.now.gmtime.strftime("%a %b %d %H:%M:%S %Y"))
+ f.close
+ end
+ else
+ session.error('unable to locate', filename)
+ end
+ rescue
+ session.error('unable to move', filename, 'to', fullname)
+ end
+
+end
+
+def fetch (session)
+
+ filename = session.get('fb.filename')
+ paths = session.get('fb.paths')
+ name = session.get('fb.name')
+
+ begin
+ File.copy(filename,filename+'.old') if ! session.get('nobackup') && test(?e,filename)
+ paths.each do |path|
+ # fullname = thefullname(path,request.naturalname(filename))
+ # fullname = thefullname(path,filename)
+ fullname = thefullname(path,name)
+ if test(?e,fullname)
+ File.copy(fullname,filename)
+ session.report('fetch', filename, 'from', fullname)
+ return
+ else
+ session.report('file',fullname, 'is not present')
+ end
+ end
+ rescue
+ session.error('unable to fetch file from path')
+ end
+ session.error('no file',filename, 'fetched') unless test(?e,filename)
+
+end
+
+def report (session)
+
+ filename = session.get('fb.filename')
+ paths = session.get('fb.paths')
+
+ paths.each do |path|
+ fullname = thefullname(path,request.naturalname(filename))
+ if test(?e,fullname)
+ begin
+ session.report('file', fullname)
+ session.report('size', test(?s,fullname))
+ if test(?e,fullname+'.tim')
+ str = IO.readlines(fullname+'.tim')
+ # str = IO.read(fullname+'.tim')
+ session.report('time', str)
+ end
+ rescue
+ session.error('unable to report about', fullname)
+ end
+ end
+ end
+
+end
+
+if session.get('option.submit')
+ prepare(session)
+ if session.get('option.request')
+ submitlist(session)
+ else
+ submitfile(session)
+ end
+elsif session.get('option.fetch')
+ prepare(session)
+ fetch(session)
+elsif session.get('option.report')
+ prepare(session)
+ report(session)
+else
+ session.report('provide action')
+end
diff --git a/scripts/context/ruby/rslibtool.rb b/scripts/context/ruby/rslibtool.rb
new file mode 100644
index 000000000..7ae5efb64
--- /dev/null
+++ b/scripts/context/ruby/rslibtool.rb
@@ -0,0 +1,114 @@
+# program : rslibtool
+# copyright : PRAGMA Publishing On Demand
+# version : 1.00 - 2002
+# author : Hans Hagen
+#
+# project : eXaMpLe
+# concept : Hans Hagen
+# info : j.hagen@xs4all.nl
+# www : www.pragma-pod.com / www.pragma-ade.com
+
+# --add --base=filename --path=directory pattern
+# --remove --base=filename --path=directory label
+# --sort --base=filename --path=directory
+# --purge --base=filename --path=directory
+# --dummy --base=filename
+# --namespace
+
+# rewrite
+
+unless defined? ownpath
+ ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'')
+ $: << ownpath
+end
+
+require 'rslb/base'
+require 'xmpl/base'
+require 'xmpl/switch'
+
+session = Example.new('rslbtool', '1.0', 'PRAGMA POD')
+
+session.identify
+
+commandline = CommandLine.new
+
+commandline.registerflag('add')
+commandline.registerflag('remove')
+commandline.registerflag('delete')
+commandline.registerflag('sort')
+commandline.registerflag('purge')
+commandline.registerflag('dummy')
+commandline.registerflag('process')
+commandline.registerflag('namespace')
+
+commandline.registervalue('prefix')
+commandline.registervalue('base')
+commandline.registervalue('path')
+commandline.registervalue('result')
+commandline.registervalue('texexec')
+commandline.registervalue('zipalso')
+
+commandline.expand
+
+session.inherit(commandline)
+
+base = session.get('option.base')
+path = session.get('option.path')
+
+base = 'rslbtool.xml' if base.empty?
+
+# when path is given, assume that arg list is list of
+# suffixes, else assume it is a list of globbed filespec
+
+if path.empty?
+ base += '.xml' unless base =~ /\..+$/
+ list = commandline.arguments
+else
+ Dir.chdir(File.dirname(path))
+ list = Dir.glob("*.{#{commandline.arguments.join(',')}}")
+end
+
+begin
+ reslib = Resource.new(base,session.get('option.namespace'))
+ reslib.load(base)
+rescue
+ session.error('problems with loading base')
+ exit
+end
+
+unless session.get('option.texexec').empty?
+ reslib.set_texexec(session.get('option.texexec'))
+end
+
+if session.get('option.add')
+
+ session.report('adding records', list)
+ reslib.add_figures(list,session.get('option.prefix'))
+
+elsif session.get('option.remove') or session.get('option.delete')
+
+ session.report('removing records')
+ reslib.delete_figures(list)
+
+elsif session.get('option.sort')
+
+ session.report('sorting records')
+ reslib.sort_figures()
+
+elsif session.get('option.purge')
+
+ session.report('purging records')
+ reslib.purge_figures()
+
+elsif session.get('option.dummy')
+
+ session.report('creating dummy records')
+ reslib.create_dummies(session.get('option.process'),session.get('option.result'),session.get('option.zipalso'))
+
+else
+
+ session.warning('provide action')
+
+end
+
+reslib.save(base)
diff --git a/scripts/context/ruby/runtools.rb b/scripts/context/ruby/runtools.rb
index 7cb80f4ff..9c504845a 100644
--- a/scripts/context/ruby/runtools.rb
+++ b/scripts/context/ruby/runtools.rb
@@ -213,7 +213,7 @@ class Job
end
end
- def copy_dir(from,to,pattern='*',exclude=[])
+ def copy_dir(from,to,pattern='*',exclude=[]) # recursive
pattern = '*' if ! pattern or pattern.empty?
if from and to and File.expand_path(from) != File.expand_path(to) then
ex = [exclude].flatten
@@ -225,6 +225,18 @@ class Job
end
end
+ def copy_path(from,to,pattern='*',exclude=[]) # non-recursive
+ pattern = '*' if ! pattern or pattern.empty?
+ if from and to and File.expand_path(from) != File.expand_path(to) then
+ ex = [exclude].flatten
+ Dir.glob("#{from}/#{pattern}").each do |file|
+ unless ex.include?(File.extname(file)) then
+ _do_copy_(file,File.join(to,file.sub(/^#{from}/, '')))
+ end
+ end
+ end
+ end
+
def _do_copy_(file,tofile)
if FileTest.file?(file) and File.expand_path(file) != File.expand_path(tofile) then
begin
diff --git a/scripts/context/ruby/texexec.rb b/scripts/context/ruby/texexec.rb
index e4de87b10..2751cef63 100644
--- a/scripts/context/ruby/texexec.rb
+++ b/scripts/context/ruby/texexec.rb
@@ -603,6 +603,42 @@ if job = TEX.new(logger) then
end
+class Commands
+
+ alias saved_help help
+
+ def wrap_help(title, vars)
+ report("")
+ report(title)
+ report("")
+ r, n = '', 0
+ vars.sort.each do |s|
+ if n == 5 then
+ report(r)
+ r, n = '', 1
+ else
+ n += 1
+ end
+ r << ' ' + s
+ end
+ report(r) unless r.empty?
+ end
+
+ def help
+ saved_help
+ if @commandline.option('all') then
+ if job = TEX.new(logger) then
+ wrap_help("boolean switches:", job.allbooleanvars)
+ wrap_help("string switches:", job.allstringvars)
+ end
+ else
+ report('')
+ report('--help --all shows all switches')
+ end
+ end
+
+end
+
# todo: register flags -> first one true
commandline.registerflag('pdf')
diff --git a/scripts/context/ruby/texmfstart.rb b/scripts/context/ruby/texmfstart.rb
index bae921098..569da636c 100644
--- a/scripts/context/ruby/texmfstart.rb
+++ b/scripts/context/ruby/texmfstart.rb
@@ -58,7 +58,9 @@ end
if $kpseerror then
$kpsereport << "unable to locate #{$kpsemodules.join('|')} on library paths:\n\n"
$kpsereport << " " + $:.join("\n ") + "\n\n"
- $kpsereport << "an option is to copy\n\n"
+ $kpsereport << "an option is to point the RUBYLIB variable to\n\n"
+ $kpsereport << " <texmf-local or texmf>/scripts/context/ruby\n\n"
+ $kpsereport << "or to copy\n\n"
$kpsereport << " <texmf-local or texmf>/scripts/context/ruby/base/kpse*\n\n"
$kpsereport << "(including the kpse subpath) to e.g.\n\n"
$kpsereport << " #{$ownpath}/../lib/texmfstart/\n\n"
@@ -74,7 +76,7 @@ if $mswindows then
require "Win32API"
end
-exit if defined?(REQUIRE2LIB)
+# exit if defined?(REQUIRE2LIB)
$stdout.sync = true
$stderr.sync = true
@@ -135,7 +137,7 @@ $makelist = [
'exatools',
'runtools',
#
- 'texmfstart'
+ # no, 'texmfstart'
]
# if ENV['TEXMFSTART_MODE'] = 'experimental' then
@@ -785,75 +787,69 @@ def edit(filename)
end
def make(filename,windows=false,linux=false,remove=false)
- basename = filename.dup
- basename.sub!(/\.[^.]+?$/, '')
- basename.sub!(/^.*[\\\/]/, '')
+ basename = File.basename(filename).gsub(/\.[^.]+?$/, '')
if $stubpath == 'auto' then
basename = File.dirname($0) + '/' + basename
else
basename = $stubpath + '/' + basename unless $stubpath.empty?
end
- if basename == filename then
- report("nothing made (#{filename})")
+ if filename == 'texmfstart' then
+ program = 'ruby'
+ command = 'kpsewhich --format=texmfscripts --progname=context texmfstart.rb'
+ filename = `#{command}`.chomp.gsub(/\\/, '/')
+ if filename.empty? then
+ report("failure: #{command}")
+ return
+ elsif not remove then
+ if windows then
+ ['bat','exe'].each do |suffix|
+ if FileTest.file?("#{basename}.#{suffix}") then
+ report("windows stub '#{basename}.#{suffix}' skipped (already present)")
+ return
+ end
+ end
+ elsif linux && FileTest.file?(basename) then
+ report("unix stub '#{basename}' skipped (already present)")
+ return
+ end
+ end
else
program = nil
if filename =~ /[\\\/]/ && filename =~ /\.(#{$scriptlist})$/ then
program = $applications[$1]
end
filename = "\"#{filename}\"" if filename =~ /\s/
- if filename == 'texmfstart' then
- program = 'ruby'
- command = 'kpsewhich --format=texmfscripts --progname=context texmfstart.rb'
- filename = `#{command}`.chomp
- if filename.empty? then
- report("failure: #{command}")
- return
- elsif not remove then
- if windows then
- ['bat','exe'].each do |suffix|
- if FileTest.file?("#{basename}.#{suffix}") then
- report("windows stub '#{basename}.#{suffix}' skipped (already present)")
- return
- end
- end
- elsif linux && FileTest.file?(basename) then
- report("unix stub '#{basename}' skipped (already present)")
- return
- end
+ program = 'texmfstart' if $indirect || ! program || program.empty?
+ end
+ begin
+ callname = $predefined[filename.sub(/\.*?$/,'')] || filename
+ if remove then
+ if windows && (File.delete(basename+'.bat') rescue false) then
+ report("windows stub '#{basename}.bat' removed (calls #{callname})")
+ elsif linux && (File.delete(basename) rescue false) then
+ report("unix stub '#{basename}' removed (calls #{callname})")
end
else
- program = 'texmfstart' if $indirect || ! program || program.empty?
- end
- begin
- callname = $predefined[filename.sub(/\.*?$/,'')] || filename
- if remove then
- if windows && (File.delete(basename+'.bat') rescue false) then
- report("windows stub '#{basename}.bat' removed (calls #{callname})")
- elsif linux && (File.delete(basename) rescue false) then
- report("unix stub '#{basename}' removed (calls #{callname})")
- end
- else
- if windows && f = open(basename+'.bat','w') then
- f.binmode
- f.write("@echo off\015\012")
- f.write("#{program} #{callname} %*\015\012")
- f.close
- report("windows stub '#{basename}.bat' made (calls #{callname})")
- elsif linux && f = open(basename,'w') then
- f.binmode
- f.write("#!/bin/sh\012")
- f.write("#{program} #{callname} $@\012")
- f.close
- report("unix stub '#{basename}' made (calls #{callname})")
- end
+ if windows && f = open(basename+'.bat','w') then
+ f.binmode
+ f.write("@echo off\015\012")
+ f.write("#{program} #{callname} %*\015\012")
+ f.close
+ report("windows stub '#{basename}.bat' made (calls #{callname})")
+ elsif linux && f = open(basename,'w') then
+ f.binmode
+ f.write("#!/bin/sh\012")
+ f.write("#{program} #{callname} $@\012")
+ f.close
+ report("unix stub '#{basename}' made (calls #{callname})")
end
- rescue
- report("failed to make stub '#{basename}' #{$!}")
- else
- return true
end
+ rescue
+ report("failed to make stub '#{basename}' #{$!}")
+ return false
+ else
+ return true
end
- return false
end
def process(&block)
diff --git a/scripts/context/ruby/www/admin.rb b/scripts/context/ruby/www/admin.rb
new file mode 100644
index 000000000..4e85fd830
--- /dev/null
+++ b/scripts/context/ruby/www/admin.rb
@@ -0,0 +1,215 @@
+require 'fileutils'
+
+require 'www/lib'
+require 'www/dir'
+require 'www/common'
+
+class WWW
+
+ include Common
+
+ # klopt nog niet, twee keer task met een verschillend doel
+
+ def handle_exatask
+ # case @session.check('task', request_variable('task'))
+ task, options, option = @session.get('task'), @session.get('option').split(@@re_bar), request_variable('option')
+ option = (options.first || '') if option.empty?
+ case task
+ when 'exaadmin'
+ @session.set('status', 'admin') # admin: status|dir
+ touch_session(@session.get('id'))
+ if options.include?(option) then
+ case option
+ when 'status' then handle_exaadmin_status
+ when 'dir' then handle_exaadmin_dir
+ else handle_exaadmin_status
+ end
+ elsif option.empty? then
+ message('Status', "unknown option")
+ else
+ message('Status', "option '#{option}' not permitted #{options.inspect}")
+ end
+ else
+ message('Status', "unknown task '#{task}")
+ end
+ end
+
+ def handle_exaadmin
+ if id = valid_session() then
+ handle_exatask
+ else
+ message('Status', 'no login')
+ end
+ end
+
+ def handle_exaadmin_dir
+ check_template_file('exalogin','exalogin-template.htm')
+ @interface.set('path:docroot', work_root)
+ @interface.set('dir:uri', 'exaadmin') # forces the dir handler into cgi mode
+ @interface.set('dir:task', 'exaadmin') # forces the dir handler into cgi mode
+ @interface.set('dir:option', 'dir') # forces the dir handler into cgi mode
+ filename = "#{@@session_prefix}#{request_variable('path')}"
+ fullname = File.join(work_root,filename)
+ if request_variable('path').empty? then
+ handle_exaadmin_status
+ elsif FileTest.directory?(fullname) then
+ handle_dir(filename, [], false)
+ elsif File.zero?(fullname) then
+ message('Error', "The file '#{filename}' is empty")
+ elsif File.size?(fullname) > (4 * 1024 * 1024) then
+ if FileTest.file?(File.expand_path(File.join(cache_root,filename))) then
+ str = "<br/><br/>Cached alternative: <a href=\"#{File.join('cache',filename)}\">#{File.basename(filename)}</a>"
+ else
+ str = ''
+ end
+ message('Error', "The file '#{filename}' is too big to serve over cgi." + str)
+ else
+ send_file(fullname)
+ end
+ end
+
+ def handle_exaadmin_status
+ check_template_file('exalogin','exalogin-template.htm')
+ begin
+ n, str, lines, list, start, most, least, cached = 0, '', '', Hash.new, Time.now, 0, 0, false
+ filename = File.join(tmp_path(dirname),'sessions.rbd')
+ begin
+ File.open(filename) do |f|
+ list = Marshal.load(f)
+ end
+ rescue
+ cached, list = false, Hash.new
+ else
+ cached = true
+ end
+ files = Dir.glob("{#{work_roots.join(',')}}/#{@@session_prefix}*.ses")
+ list.keys.each do |l|
+ list.delete(l) unless files.include?(l) # slow
+ end
+ files.each do |f|
+ ctime = File.ctime(f)
+ stime = list[f][0] == ctime rescue 0
+ unless ctime == stime then
+ begin
+ hash = load_session_file(f)
+ rescue
+ else
+ list[f] = [ctime,hash]
+ end
+ end
+ end
+ begin
+ File.open(filename,'w') do |f|
+ f << Marshal.dump(list)
+ end
+ rescue
+ # no save
+ end
+ begin
+ keys = list.keys.sort do |a,b|
+ case list[b][0] <=> list[a][0]
+ when -1 then -1
+ when +1 then +1
+ else
+ a <=> b
+ end
+ end
+ rescue
+ keys = list.keys.sort
+ end
+ totaltime, totaldone = 0.0, 0
+ if keys.length > 0 then
+ keys.each do |entry|
+ s, t, session = entry, list[entry][0], list[entry][1]
+ status = session['status'] || ''
+ runtime = (session['runtime'] || '').to_f rescue 0
+ starttime = (start.to_i-session['starttime'].to_i).to_s rescue ''
+ requesttime = session['endtime'].to_i-session['starttime'].to_i rescue 0
+ requesttime = if requesttime > 0 then requesttime.to_s else '' end
+ if runtime > 0.0 then
+ totaltime += runtime
+ totaldone += 1
+ if least > 0 then
+ if runtime < least then least = runtime end
+ else
+ least = runtime
+ end
+ if most > 0 then
+ if runtime > most then most = runtime end
+ else
+ most = runtime
+ end
+ end
+ if status.empty? then
+ # skip, garbage
+ elsif status =~ /^(|exa)admin/o then
+ # skip, useless
+ else
+ begin
+ lines << "<tr>\n"
+ lines << td("<a href=\"exaadmin?option=dir&path=#{session['id']}.dir\">#{session['id']}</a>")
+ lines << td(status)
+ lines << td(session['timeout'])
+ lines << td(starttime)
+ lines << td(session['runtime'])
+ lines << td(requesttime)
+ lines << td(t.strftime("%H:%M:%S %Y-%m-%d"))
+ lines << td(session['domain'])
+ lines << td(session['project'])
+ lines << td(session['username'])
+ lines << td(File.basename(File.dirname(s)))
+ lines << "</tr>\n"
+ rescue
+ else
+ n += 1
+ end
+ end
+ end
+ if n > 0 then
+ str = "<table cellpadding='0'>\n"
+ str << "<tr>\n"
+ str << th('session identifier')
+ str << th('status')
+ str << th('timeout')
+ str << th('time')
+ str << th('runtime')
+ str << th('total')
+ str << th('modification&nbsp;time')
+ str << th('domain')
+ str << th('project')
+ str << th('username')
+ str << th('process')
+ str << "</tr>\n"
+ str << lines
+ str << "</table>\n"
+ end
+ end
+ rescue
+ message('Status', "#{$!} There is currently no status available.", false, @@admin_refresh, 'exaadmin')
+ else
+ if n > 0 then
+ # r = if n > 100 then 60 else @@admin_refresh.to_i end # scanning takes long
+ r = @@admin_refresh
+ average = "average = #{if totaldone > 0 then sprintf('%.02f',totaltime/totaldone) else '0' end} (#{sprintf('%.02f',least)} .. #{sprintf('%.02f',most)})"
+ sessions = "sessions = #{n}"
+ refresh = "refresh = #{r.to_s} sec"
+ loadtime = "loadtime = #{sprintf('%.04f',Time.now-start)} sec"
+ cached = if cached then "cached" else "not cached" end
+ message("Status | #{sessions} | #{refresh} | #{loadtime} - #{cached} | #{average} |", str, false, r, 'exaadmin')
+ else
+ message('Status', "There are no sessions registered.", false, @@admin_refresh, 'exaadmin')
+ end
+ end
+ end
+
+ private
+
+ def th(str)
+ "<th align='left'>#{str}&nbsp;&nbsp;&nbsp;</th>\n"
+ end
+
+ def td(str)
+ "<td><code>#{str || ''}&nbsp;&nbsp;&nbsp</code></td>\n"
+ end
+
+end
diff --git a/scripts/context/ruby/www/common.rb b/scripts/context/ruby/www/common.rb
new file mode 100644
index 000000000..9c3832294
--- /dev/null
+++ b/scripts/context/ruby/www/common.rb
@@ -0,0 +1,80 @@
+# We cannot chdir in threads because it is something
+# process wide, so we will run into problems with the
+# other threads. The same is true for the global ENV
+# pseudo hash, so we cannot communicate the runpath
+# via an anvironment either. This leaves texmfstart
+# in combination with a path directive and an tmf file.
+
+module Common # can be a mixin
+
+ # we assume that the hash.subset method is defined
+
+ @@re_texmfstart = /^(texmfstart|ruby\s*texmfstart.rb)\s*(.*)$/
+ @@re_texmfpath = /^\-\-path\=/
+
+ def command_string(path,command,log='')
+ runner = "texmfstart --path=#{File.expand_path(path)}"
+ if command =~ @@re_texmfstart then
+ cmd, arg = $1, $2
+ if arg =~ @@re_texmfpath then
+ # there is already an --path (first switch)
+ else
+ command = "#{runner} #{arg}"
+ end
+ else
+ command = "#{runner} bin:#{command}"
+ end
+ if log && ! log.empty? then
+ return "#{command} 2>&1 > #{File.expand_path(File.join(path,log))}"
+ else
+ return command
+ end
+ end
+
+ def set_os_vars
+ begin
+ ENV['TEXOS'] = ENV['TEXOS'] || platform
+ rescue
+ ENV['TEXOS'] = 'texmf-linux'
+ else
+ ENV['TEXOS'] = 'texmf-' + ENV['TEXOS'] unless ENV['TEXOS'] =~ /^texmf\-/
+ ensure
+ ENV['EXA:TEXOS'] = ENV['TEXOS']
+ end
+ end
+
+ def set_environment(hash)
+ set_os_vars
+ paths = ENV['PATH'].split(File::PATH_SEPARATOR)
+ hash.subset('binpath:').keys.each do |key|
+ begin
+ paths << File.expand_path(hash[key])
+ rescue
+ end
+ end
+ ENV['PATH'] = paths.uniq.join(File::PATH_SEPARATOR)
+ hash.subset('path:').keys.each do |path|
+ key, value = "EXA:#{path.upcase}", File.expand_path(hash[path])
+ ENV[key] = value
+ end
+ end
+
+ def save_environment(hash,path,filename='request.tmf')
+ begin
+ File.open(File.join(path,filename),'w') do |f|
+ set_os_vars
+ ['EXA:TEXOS','TEXOS'].each do |key|
+ f.puts("#{key} = #{ENV[key]}")
+ end
+ hash.subset('binpath:').keys.each do |key|
+ f.puts("PATH < #{File.expand_path(@interface.get(key))}")
+ end
+ hash.subset('path:').keys.each do |path|
+ f.puts("EXA:#{path.upcase} = #{File.expand_path(@interface.get(path))}")
+ end
+ end
+ rescue
+ end
+ end
+
+end
diff --git a/scripts/context/ruby/www/dir.rb b/scripts/context/ruby/www/dir.rb
new file mode 100644
index 000000000..09e088d77
--- /dev/null
+++ b/scripts/context/ruby/www/dir.rb
@@ -0,0 +1,155 @@
+require 'www/lib'
+
+# dir handling
+
+class WWW
+
+ # borrowed code from webrick demo, patched
+
+ @@dir_name_width = 25
+
+ def handle_dir(dirpath=@variables.get('path'),hidden=[],showdirs=true)
+ check_template_file('dir','text-template.htm')
+ docroot = @interface.get('path:docroot')
+ dirpath = dirpath || ''
+ hidden = [] unless hidden
+ local_path = dirpath.dup
+ title, str = "Index of #{escaped(dirpath)}", ''
+ begin
+ local_path.gsub!(/[\/\\]+/,'/')
+ local_path.gsub!(/\/$/, '')
+ if local_path !~ /^(\.|\.\.|\/|[a-zA-Z]\:)$/io then # maybe also /...
+ full_path = File.join(docroot,local_path)
+ @interface.set('log:dir', full_path)
+ begin
+ list = Dir::entries(full_path)
+ rescue
+ str << "unable to parse #{local_path}"
+ else
+ if list then
+ list.collect! do |name|
+ if name =~ /^\.+/o then
+ nil # no . and ..
+ else
+ st = (File::stat(File.join(docroot,local_path,name)) rescue nil)
+ if st.nil? then
+ [name, nil, -1, false]
+ elsif st.directory? then
+ if showdirs then [name + "/", st.mtime, -1, true] else nil end
+ elsif hidden.length > 0 then
+ if hidden.include?(name) then nil else [name, st.mtime, st.size, false] end
+ else
+ [name, st.mtime, st.size, false]
+ end
+ end
+ end
+ list.compact!
+ n, m, s = @variables.get('n'), @variables.get('m'), @variables.get('s')
+ if ! n.empty? then
+ idx, d0 = 0, n
+ elsif ! m.empty? then
+ idx, d0 = 1, m
+ elsif ! s.empty? then
+ idx, d0 = 2, s
+ else
+ idx, d0 = 0, 'a'
+ end
+ d1 = if d0 == 'a' then 'd' else 'a' end
+ if d0 == 'a' then
+ list.sort! do |a,b| a[idx] <=> b[idx] end
+ else
+ list.sort! do |a,b| b[idx] <=> a[idx] end
+ end
+ u = dir_uri(@variables.get('path') || '.')
+ str << "<div class='dir-view'>\n<pre>\n"
+ str << "<a href=\"#{u}&n=#{d1}\">name</A>".ljust(49+u.length)
+ str << "<a href=\"#{u}&m=#{d1}\">last modified</A>".ljust(41+u.length)
+ str << "<a href=\"#{u}&s=#{d1}\">size</A>".rjust(31+u.length) << "\n" << "\n"
+ # parent path
+ if showdirs && ! hidden.include?('..') then
+ dname = "parent directory"
+ fname = "#{File.dirname(dirpath)}"
+ time = File::mtime(File.join(docroot,local_path,"/.."))
+ str << dir_entry(fname,dname,time,-1,true)
+ str << "\n"
+ end
+ # directories
+ done = false
+ list.each do |name, time, size, dir|
+ if dir then
+ if name.size > @@dir_name_width then
+ dname = name.sub(/^(.#{@@dir_name_width-2})(.*)/) do $1 + ".." end
+ else
+ dname = name
+ end
+ fname = "#{escaped(dirpath)}/#{escaped(name)}"
+ str << dir_entry(fname,dname,time,size,dir)
+ done = true
+ end
+ end
+ str << "\n" if done
+ # files
+ list.each do |name, time, size, dir|
+ unless dir then
+ if name.size > @@dir_name_width then
+ dname = name.sub(/^(.#{@@dir_name_width-2})(.*)/) do $1 + ".." end
+ else
+ dname = name
+ end
+ fname = "#{escaped(dirpath)}/#{escaped(name)}"
+ str << dir_entry(fname,dname,time,size,dir)
+ end
+ end
+ str << "\n"
+ str << '</pre></div>'
+ else
+ str << 'no info'
+ end
+ end
+ else
+ str << 'no access'
+ end
+ rescue
+ str << "error #{$!}<br/><pre>"
+ str << $@.join("\n")
+ str << "</pre>"
+ end
+ message(title,str)
+ end
+ def dir_uri(f='.')
+ u, t, o = @interface.get('dir:uri'), @interface.get('dir:task'), @interface.get('dir:option') # takes precedence, in case we run under cgi control
+ if u.empty? then
+ u, t, o = @interface.get('process:uri'), '', ''
+ elsif ! t.empty? then
+ t = "task=#{t}&"
+ o = "option=#{o}&"
+ end
+ if u && ! u.empty? then
+ u = u.sub(/\?.*$/,'') # frozen string
+ if f =~ /^\.+$/ then
+ "#{u}?#{t}#{o}path="
+ else
+ "#{u}?#{t}#{o}path=#{f}"
+ end
+ else
+ ''
+ end
+ end
+
+ def dir_entry(fname,dname,time,size,dir=false)
+ if dir then
+ f = fname.sub(/\/+$/,'').sub(/^\/+/,'')
+ s = "<a href=\"#{dir_uri(f)}\">#{dname}</a>"
+ elsif ! @interface.get('dir:uri').empty? then # takes precedence, in case we run under cgi control
+ s = "<a href=\"#{dir_uri(fname.gsub(/\/+/,'/'))}\">#{dname}</a>"
+ else
+ s = "<a href=\"#{fname.gsub(/\/+/,'/')}\">#{dname}</a>"
+ end
+ # s << " " * (30 - dname.size)
+ s << " " * (@@dir_name_width + 5 - dname.size)
+ s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
+ s << (size >= 0 ? size.to_s : "-").rjust(12) << "\n"
+ return s
+ end
+
+end
diff --git a/scripts/context/ruby/www/exa.rb b/scripts/context/ruby/www/exa.rb
new file mode 100644
index 000000000..6cf7d8a9b
--- /dev/null
+++ b/scripts/context/ruby/www/exa.rb
@@ -0,0 +1,386 @@
+require 'fileutils'
+require 'www/lib'
+require 'www/dir'
+require 'www/common'
+require 'www/admin'
+
+class WWW
+
+ include Common
+
+ def handle_exadefault
+ check_template_file('exalogin','exalogin-template.htm')
+ if id = logged_in_session(true) then
+ finish_login
+ else
+ message('Error', 'No default login permitted.')
+ end
+ end
+
+ def handle_exalogin
+ check_template_file('exalogin','exalogin-template.htm')
+ if id = logged_in_session(false) then
+ finish_login
+ else
+ message('Error', 'No default login permitted.')
+ end
+ end
+
+ def finish_login
+ get_gui()
+ filename, path, task = @session.get('gui'), @session.checked('path','.'), @session.get('task')
+ if ! task.empty? then
+ save_session
+ handle_exatask
+ elsif filename and not filename.empty? then
+ save_session
+ fullname = filename.gsub(/\.\./,'')
+ fullname = File.join(path,filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'), filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'), path, filename) unless FileTest.file?(fullname)
+ if FileTest.file?(fullname) then
+ send_file(fullname,true)
+ else
+ message('Interface', 'Invalid interface request, no valid interface file.' )
+ end
+ else
+ message('Interface', 'Invalid interface request, no default interface file.')
+ end
+ end
+
+ def handle_exainterface()
+ check_template_file('text','text-template.htm')
+ if id = valid_session() then
+ filename = @interface.get('process:uri').to_s # kind of dup
+ if ! filename.empty? && filename.sub!(/^.*\//,'') then
+ path = @session.checked('path', '.')
+ fullname = filename.gsub(/\.\./,'')
+ fullname = File.join(path,filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'),filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'),path,filename) unless FileTest.file?(fullname)
+ if FileTest.file?(fullname) then
+ save_session
+ send_file(fullname,true)
+ else
+ get_file(filename)
+ filename, path = @session.get('gui'), @session.checked('path','.')
+ if filename and not filename.empty? then
+ save_session
+ fullname = filename.gsub(/\.\./,'')
+ fullname = File.join(path,filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'),filename) unless FileTest.file?(fullname)
+ fullname = File.join(@interface.get('path:interfaces'),path,filename) unless FileTest.file?(fullname)
+ send_file(fullname,true) if FileTest.file?(fullname)
+ else
+ message('Interface', 'Invalid interface request, no interface file.')
+ end
+ end
+ else
+ message('Interface', 'Invalid interface request, no resource file.')
+ end
+ else
+ message('Interface', 'Invalid interface request, no login.')
+ end
+ end
+
+ def handle_exarequest() # todo: check if request is 'command'
+ check_template_file('exalogin','exalogin-template.htm')
+ if id = client_session() then
+ client = true
+ @interface.set('log:kind', "remote client request: #{id}")
+ elsif id = valid_session() then
+ client = false
+ @interface.set('log:kind', "remote browser request: #{id}")
+ else
+ client, id = false, nil
+ @interface.set('log:kind', 'unknown kind of request')
+ end
+ if id then
+ dir, tmp = dirname, tmp_path(dirname)
+ requestname, replyname = 'request.exa', 'reply.exa'
+ requestfile, replyfile = File.join(tmp,requestname), File.join(tmp,replyname)
+ lockfile = File.join(dirname,lckname)
+ action, filename, command, url, req = '', '', '', '', ''
+ extract_sent_files(tmp)
+ @variables.each do |key, value|
+ case key
+ when 'exa:request' then
+ req = value.dup
+ when 'exa:action' then
+ action = value.dup
+ # when 'exa:command' then
+ # command = value.dup
+ # when 'exa:url' then
+ # url = value.dup
+ when 'exa:filename' then
+ filename = value.dup
+ when 'exa:threshold' then
+ @interface.set('process:threshold', value.dup)
+ when /^fakename/o then
+ @variables.set(key, File.basename(value))
+ when /^filename\-/o then
+ @variables.set(key, filename = File.basename(value))
+ when /^dataname\-/o then
+ @variables.set(key)
+ else # remove varname- prefix from value
+ @variables.set(key, @variables.get(key).sub(/#{key}\-/,''))
+ end
+ end
+ @variables.check('exa:filename', filename)
+ @variables.check('exa:action', action)
+ if @variables.empty?('exa:filename') then
+ @variables.set('exa:filename', @interface.get('log:attachments').split('|').first || '')
+ end
+ req.gsub!(/<exa:data\s*\/>/i, '')
+ dat = "<exa:data>\n"
+ @variables.each do |key, value|
+ if ['password','exa:request'].include?(key) then
+ # skip
+ elsif ! value || value.empty? then
+ dat << "<exa:variable label='#{key}'/>\n"
+ else # todo: escape 'm
+ dat << "<exa:variable label='#{key}'>#{value}</exa:variable>\n"
+ end
+ end
+ dat << "</exa:data>\n"
+ if req.empty? then
+ req << "<?xml version='1.0' ?>\n"
+ req << "<exa:request #{@@namespace}'>\n"
+ req << "<exa:application>\n"
+ req << "<exa:action>'#{action}</exa:action>\n" unless action.empty?
+ # req << "<exa:command>'#{command}</exa:command>\n" unless command.empty?
+ # req << "<exa:url>'#{url}</exa:url>\n" unless url.empty?
+ req << "<exa:application>\n"
+ req << "<exa:comment>constructed request</exa:comment>\n"
+ req << dat
+ req << "</exa:request>\n"
+ else
+ # better use rexml but slower
+ if req =~ /<exa:request[^>]*>.*?\s*<exa:threshold>\s*(.*?)\s*<\/exa:threshold>\s*.*?<\/exa:request>/mois then
+ threshold = $1
+ unless threshold.empty? then
+ @interface.set('process:threshold', threshold)
+ @session.set('threshold', threshold)
+ end
+ end
+ req.sub!(/(<exa:request[^>]*>.*?)\s*<exa:option>\s*\-\-action\=(.*?)\s*<\/exa:option>\s*(.*?<\/exa:request>)/mois) do
+ pre, act, pos = $1, $2, $3
+ action = act.sub(/\.exa$/,'') if action.empty?
+ str = "#{pre}<exa:action>#{action}</exa:action>#{pos}"
+ str.sub(/\s*<exa:command>.*?<\/exa:command>\s*/mois ,'')
+ end
+ req.sub!(/(<exa:request[^>]*>.*?)<exa:action>\s*(.*?)\s*<\/exa:action>(.*?<\/exa:request>)/mois) do
+ pre, act, pos = $1, $2, $3
+ action = act.sub(/\.exa$/,'') if action.empty?
+ str = "#{pre}<exa:action>#{action}</exa:action>#{pos}"
+ str.sub(/\s*<exa:command>.*?<\/exa:command>\s*/mois ,'')
+ end
+ unless req =~ /<exa:data>(.*?)<\/exa:data>/mois then
+ req.sub!(/(<\/exa:request>)/) do dat + $1 end
+ end
+ end
+ req.sub!(/<exa:filename>.*?<\/exa:filename>/mois, '')
+ unless @variables.empty?('exa:filename') then
+ req.sub!(/(<\/exa:application>)/mois) do
+ "<exa:filename>#{@variables.get('exa:filename')}<\/exa:filename>" + $1
+ end
+ end
+ @variables.set('exa:action', action)
+ @interface.set("log:#{requestname}", req)
+ begin
+ File.open(requestfile,'w') do |f|
+ f << req
+ end
+ rescue
+ message('Error', 'There is a problem in handling this request (working path access).')
+ return
+ end
+ File.delete(replyfile) rescue false
+ @interface.set('log:action',action)
+ get_command(action)
+ logdata = ''
+ begin
+ command = @session.get('command')
+ @interface.set('log:command',if command.empty? then '[no command]' else command end)
+ if ! command.empty? then
+ @session.set('starttime', Time.now.to_i.to_s) # can be variables and in save list
+ if @interface.true?('process:background') then
+ # background
+ @session.set('status', 'running: background')
+ @session.set('maxtime', @interface.get('process:timeout'))
+ @session.set('threshold', @interface.get('process:threshold'))
+ save_session
+ timeout(@@watch_delay) do
+ save_environment(@interface,tmp)
+ begin
+ starttime = File.mtime(@session_file)
+ # crap
+ loop do
+ sleep(1)
+ if starttime != File.mtime(@session_file) then
+ break unless FileTest.file?(lockfile)
+ end
+ end
+ rescue TimeoutError
+ if client then
+ send_reply()
+ else
+ message('Status', 'Processing your request takes a while',true,5,'exastatus')
+ end
+ return
+ rescue
+ end
+ end
+ if client then send_reply() else send_result() end
+ else
+ # foreground
+ status = 'running: foreground'
+ @session.set('status', status)
+ @session.set('maxtime', @interface.get('process:timeout'))
+ @session.set('threshold', @interface.get('process:threshold'))
+ save_session
+ timeout(@interface.get('process:timeout').to_i) do
+ begin
+ status = 'running: foreground'
+ set_environment(@interface)
+ save_environment(@interface,tmp)
+ command = command_string(tmp,command)
+ logdata = `#{command}`
+ rescue TimeoutError
+ status = 'running: timeout'
+ logdata = "timeout: #{@interface.get('process:timeout')} seconds"
+ rescue
+ status = 'running: aborted'
+ logdata = 'fatal runtime error'
+ else
+ @session.set('endtime', Time.now.to_i.to_s)
+ status = 'running: finished'
+ end
+ end
+ @session.set('status', status)
+ save_session
+ case @session.get('status')
+ when 'running: finished' then
+ if client then send_reply(logdata) else send_result(logdata) end
+ when 'running: timeout' then
+ message('Error', 'There is a problem in handling this request (timeout).')
+ when 'running: aborted' then
+ message('Error', 'There is a problem in handling this request (aborted).')
+ else
+ message('Error', 'There is a problem in handling this request (unknown).')
+ end
+ end
+ else
+ message('Error', 'There is a problem in handling this request (no runner).')
+ end
+ rescue
+ message('Error', 'There is a problem in handling this request (no run).' + $!)
+ end
+ else
+ message('Error', 'Invalid session.')
+ end
+ end
+
+ def handle_exacommand() # shares code with exarequest
+ check_template_file('exalogin','exalogin-template.htm')
+ if id = client_session() then
+ client = true
+ @interface.set('log:kind', "remote client request: #{id}")
+ elsif id = valid_session() then
+ client = false
+ @interface.set('log:kind', "remote browser request: #{id}")
+ else
+ client, id = false, nil
+ @interface.set('log:kind', 'unknown kind of request')
+ end
+ if id then
+ dir, tmp = dirname, tmp_path(dirname)
+ requestname, replyname = 'request.exa', 'reply.exa'
+ requestfile, replyfile = File.join(tmp,requestname), File.join(tmp,replyname)
+ req, command, url = '', '', ''
+ @variables.each do |key, value|
+ case key
+ when 'exa:request' then
+ req = value.dup
+ when 'exa:command' then
+ command = value.dup
+ when 'exa:threshold' then
+ @interface.set('process:threshold', value.dup)
+ when 'exa:url' then
+ url = value.dup
+ end
+ end
+ unless req.empty? then
+ # better use rexml but slower / reuse these : command = filter_from_request('exa:command')
+ if req =~ /<exa:request[^>]*>.*?\s*<exa:command>\s*(.*?)\s*<\/exa:command>\s*.*?<\/exa:request>/mois then
+ command = $1
+ end
+ if req =~ /<exa:request[^>]*>.*?\s*<exa:url>\s*(.*?)\s*<\/exa:url>\s*.*?<\/exa:request>/mois then
+ url = $1
+ end
+ if req =~ /<exa:request[^>]*>.*?\s*<exa:threshold>\s*(.*?)\s*<\/exa:threshold>\s*.*?<\/exa:request>/mois then
+ threshold = $1
+ unless threshold.empty? then
+ @interface.set('process:threshold', threshold)
+ @session.set('threshold', threshold)
+ end
+ end
+ end
+ @variables.check('exa:command', command)
+ @variables.check('exa:url', url)
+ File.delete(replyfile) rescue false
+ case @variables.get('exa:command')
+ when 'fetch' then
+ if @variables.empty?('exa:url') then
+ message('Error', "Problems with fetching, no file given")
+ else
+ # the action starts here
+ filename = @variables.get('exa:url').to_s # kind of dup
+ unless filename.empty? then
+ get_path(filename) # also registers filename as url
+ path = @session.checked('path', '')
+ fullname = filename.gsub(/\.\./,'')
+ fullname = File.join(path,fullname) unless path.empty?
+ if FileTest.file?(fullname) then
+ if client then
+ send_url(fullname)
+ else
+ send_file(fullname,true)
+ end
+ @session.set('threshold', @interface.get('process:threshold'))
+ @session.set('url',filename)
+ save_session
+ else
+ message('Error', "Problems with fetching, unknown file #{fullname}.")
+ # message('Error', "Problems with fetching, unknown file #{filename}.")
+ end
+ else
+ message('Error', "Problems with fetching, invalid file #{filename}.")
+ end
+ # and ends here
+ end
+ else
+ message('Error', "Invalid command #{command}.")
+ end
+ else
+ message('Error', 'Invalid session.')
+ end
+ end
+
+ def handle_exastatus
+ if request_variable('id').empty? then
+ if id = valid_session() then
+ send_result()
+ else
+ message('Error', 'Invalid session.')
+ end
+ else
+ if id = valid_session() then
+ send_reply()
+ else
+ send_reply('invalid session')
+ end
+ end
+ end
+
+end
diff --git a/scripts/context/ruby/www/lib.rb b/scripts/context/ruby/www/lib.rb
new file mode 100644
index 000000000..b330c2a97
--- /dev/null
+++ b/scripts/context/ruby/www/lib.rb
@@ -0,0 +1,1391 @@
+#!/usr/bin/env ruby
+
+# This is just a simple environment for remote processing of context
+# files. It's not a framework, nor an example of how that should be done.
+# Nowadays there are environments like Rails or Nitro. Maybe some day I'll
+# give one of them a try.
+
+# <META Http-Equiv="Cache-Control" Content="no-cache">
+# <META Http-Equiv="Pragma" Content="no-cache">
+# <META Http-Equiv="Expires" Content="0">
+
+# we make limited use of cgi methods because we also need to handle webrick
+
+# %var% as well as $(var) are supported
+
+# paths need to be expanded before they enter apache, since .. is not
+# handled by default
+
+require 'base/variables'
+
+require 'ftools'
+require 'fileutils'
+require 'tempfile'
+require 'timeout'
+require 'md5'
+require 'digest/md5'
+require 'cgi' # we also need escaping for webrick (could move it here)
+
+# beware, namespaces have to match !
+
+module XML
+
+ def XML::element(tag,attributes=nil)
+ if attributes.class == Hash then
+ if block_given? then
+ XML::element(tag,XML::attributes(attributes)) do yield end
+ else
+ XML::element(tag,XML::attributes(attributes))
+ end
+ else
+ if block_given? then
+ "<#{tag}#{if attributes && ! attributes.empty? then ' ' + attributes end}>#{yield}</#{tag}>"
+ else
+ "<#{tag}#{if attributes && ! attributes.empty? then ' ' + attributes end}/>"
+ end
+ end
+ end
+
+ def XML::attributes(hash)
+ str = ''
+ hash.each do |k,v|
+ str << ' ' unless str.empty?
+ if v =~ /\'/ then
+ str << "#{k}=\"#{v}\""
+ else
+ str << "#{k}=\'#{v}\'"
+ end
+ end
+ return str
+ end
+
+ def XML::create(version='1.0')
+ "<?version='#{version}'?>#{yield || ''}"
+ end
+
+ def XML::line
+ "\n"
+ end
+
+end
+
+# str =
+ # XML::create do
+ # XML::element('test') do
+ # XML::element('test') do
+ # 'text a'
+ # end +
+ # XML::element('test',XML::attributes({'a'=>'b'})) do
+ # XML::element('nested',XML::attributes({'a'=>'b'})) do
+ # 'text b-1'
+ # end +
+ # XML::element('nested',XML::attributes({'a'=>'b'})) do
+ # 'text b-2'
+ # end
+ # end +
+ # XML::element('nested',{'a'=>'b'}) do
+ # 'text c'
+ # end
+ # end
+ # end
+
+class ExtendedHash
+
+ DEFAULT = 'default'
+
+ @@re_default = /^(default|)$/i
+
+ def default?(key)
+ self[key] =~ @@re_default rescue true # unset, empty or 'default'
+ end
+
+ def default(key)
+ self[key] = DEFAULT
+ end
+
+ def match?(key,value)
+ value == '*' || value == self[key]
+ end
+
+
+end
+
+class WWW
+
+ @@session_prefix = ''
+ @@data_file = 'example.cfg'
+ @@session_max_age = 60*60
+ @@watch_delay = 30
+ @@send_threshold = 2*1024*1024
+ @@admin_refresh = 10
+ @@namespace = "http://www.pragma-ade.com/schemas/example.rng"
+
+ @@re_bar = /\s*\|\s*/
+ @@re_lst = /\s*\,\s*/
+ @@re_var_a = /\%(.*?)\%/
+ @@re_var_b = /\$\((.*?)\)/
+
+ attr_reader :variables
+ attr_writer :variables
+
+ @@paths = [
+ 'configurations',
+ 'data',
+ 'distributions',
+ 'documents',
+ 'interfaces',
+ 'logs',
+ 'resources',
+ 'runners',
+ 'scripts',
+ 'templates',
+ 'work']
+
+ @@re_true = /^\s*(YES|ON|TRUE|1)\s*$/io
+ @@re_false = /^\s*(NO|OFF|FALSE|0)\s*$/io
+
+ def initialize(webrick_daemon=nil,webrick_request=nil,webrick_response=nil)
+ @session_id, @session_file = '', ''
+ @cgi, @cgi_cookie = nil, nil
+ @webrick_daemon, @webrick_request, @webrick_response = webrick_daemon, webrick_request, webrick_response
+
+ @interface = ExtendedHash.new
+ @variables = ExtendedHash.new
+ @session = ExtendedHash.new
+
+ @checked = false
+
+ analyze_request()
+ update_interface()
+
+ @interface.set('template:message' , 'text-template.htm')
+ @interface.set('template:status' , 'text-template.htm')
+ @interface.set('template:login' , 'exalogin.htm')
+ @interface.set('process:timeout' , @@session_max_age)
+ @interface.set('process:threshold' , @@send_threshold)
+ @interface.set('process:background', 'yes') # this demands a watchdog being active
+ @interface.set('process:indirect' , 'no') # indirect download, no direct feed
+ @interface.set('process:autologin' , 'yes') # provide default interface when applicable
+ @interface.set('process:exaurl' , '') # this one will be used as replacement in templates
+ @interface.set('trace:run' , 'no')
+ @interface.set('trace:errors' , 'no')
+ @interface.set('process:os' , platform)
+ @interface.set('process:texos' , 'texmf-' + platform)
+
+ @interface.set('trace:run' , 'yes') if (ENV['EXA:TRACE:RUN'] || '') =~ @@re_true
+ @interface.set('trace:errors' , 'yes') if (ENV['EXA:TRACE:ERRORS'] || '') =~ @@re_true
+
+ yield self if block_given?
+ end
+
+ def set(key,value)
+ @interface.set(key,value)
+ end
+ def get(key)
+ @interface.get(key,value)
+ end
+
+ def 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
+
+ def check_cgi
+ # when mod_ruby is used, we need to close
+ # the cgi session explicitly
+ unless @webrick_request then
+ unless @cgi then
+ @cgi = CGI.new('html4')
+ at_exit do
+ begin
+ @cgi.close
+ rescue
+ end
+ end
+ end
+ end
+ end
+
+ def request_variable(key)
+ begin
+ if @webrick_request then
+ [@webrick_request.query[key]].flatten.first.to_s
+ else
+ check_cgi
+ [@cgi.params[key]].flatten.first.to_s
+ end
+ rescue
+ ''
+ end
+ end
+
+ def request_cookie(key)
+ begin
+ if @cgi then
+ if str = @cgi.cookies[key] then
+ return str.first || ''
+ end
+ elsif @webrick_request then
+ @webrick_request.cookies.flatten.each do |cookie|
+ if cookie.name == key then
+ return cookie.value unless cookie.value.empty?
+ end
+ end
+ end
+ rescue
+ end
+ return ''
+ end
+
+ def analyze_request
+ if @webrick_request then
+ @interface.set('path:docroot', @webrick_daemon.config[:DocumentRoot] || './documents')
+ @interface.set('process:uri', @webrick_request.request_uri.to_s)
+ # @interface.set('process.url', [@webrick_request.host,@webrick_request.request_port].join(':'))
+ @cgi = nil
+ @webrick_request.query.each do |key, value|
+ # todo: filename
+ @variables.set(key, [value].flatten.first)
+ end
+ else
+ @interface.set('path:docroot', ENV['DOCUMENT_ROOT'] || './documents')
+ @interface.set('process:uri', ENV['REQUEST_URI'] || '')
+ # @interface.set('process.url', [ENV['SERVER_NAME'],ENV['SERVER:PORT']].join(':'))
+ ARGV[0] = '' # get rid of terminal mode
+ check_cgi
+ # quite fragile, due to changes between 1.6 and 1.8
+ @cgi.params.keys.each do |p|
+ if @cgi[p].respond_to?(:original_filename) then
+ @interface.set('log:method','post')
+ if @cgi[p].original_filename && ! @cgi[p].original_filename.empty? then
+ @variables.set(p, File.basename(@cgi[p].original_filename))
+ else
+ case @cgi.params[p].class
+ when StringIO.class then @variables.set(p, @cgi[p].read)
+ when Array.class then @variables.set(p, @cgi[p].first.to_s)
+ when String.class then @variables.set(p, @cgi[p])
+ when Tempfile.class then @variables.set(p, '[data blob]')
+ end
+ end
+ else
+ @interface.set('log:method','get') unless @interface.get('log:method') == 'post'
+ @variables.set(p, [@cgi.params[p]].flatten.first.to_s)
+ end
+ end
+ end
+ end
+
+ # name in calling script takes precedence
+ # one can set template:whatever as well
+ # todo: in config
+
+ def check_template_file(tag='',filename='exalogin-template.htm')
+ @interface.set('file:template', filename) if @interface.get('file:template').empty?
+ @interface.set('tag:template', tag)
+ @interface.set('file:template', @interface.get('tag:template')) unless @interface.get('tag:template').empty?
+ end
+
+ def update_interface()
+ root = @interface.get('path:docroot')
+ @interface.set('path:docroot', File.expand_path("#{root}"))
+ @@paths.each do |path|
+ @interface.set("path:#{path}", File.expand_path("#{root}/../#{path}"))
+ end
+ @interface.set('file:template', @interface.get('tag:template')) unless @interface.get('tag:template').empty?
+ end
+
+ def indirect?(result)
+ size = FileTest.size?(result) || 0
+ @interface.true?('trace:errors') || @interface.true?('trace:run') || @interface.true?('process:indirect') ||
+ ((! @interface.empty?('process:threshold')) && (size > @interface.get('process:threshold').to_i)) ||
+ ((! @session.empty?('threshold')) && (size > @session.get('threshold').to_i))
+ end
+
+end
+
+# files
+
+class WWW
+
+ def sesname
+ File.basename(@session_file)
+ end
+ def dirname
+ File.basename(@session_file.sub(/ses$/,'dir'))
+ end
+ def lckname
+ File.basename(@session_file.sub(/ses$/,'lck'))
+ end
+
+ def work_root(expand=true)
+ p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
+ if @interface.true?('process:background') then
+ File.join(@interface.get('path:work'),'watch')
+ else
+ File.join(@interface.get('path:work'),'direct')
+ end
+ end
+
+ def work_roots(expand=true)
+ p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
+ [File.join(@interface.get('path:work'),'watch'),File.join(@interface.get('path:work'),'direct')]
+ end
+
+ def cache_root(expand=true)
+ p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
+ File.join(@interface.get('path:work'),'cache')
+ end
+
+ def cleanup_path(dir)
+ FileUtils::rm_r(pth) rescue false
+ end
+
+ def tmp_path(dir)
+ @interface.set('path:templates', File.expand_path(@interface.get('path:templates'))) # to be sure; here ? ? ?
+ pth = File.join(work_root,dir)
+ File.makedirs(pth) rescue false
+ pth
+ end
+
+ def locked?(lck)
+ FileTest.file?(lck)
+ end
+
+end
+
+# sessions
+
+class WWW
+
+ @@session_tags = ['id','domain','project','username','password','gui','path','process','command','filename','action','status', 'starttime','endtime','runtime','task','option','threshold','url'].sort
+ @@session_keep = ['id','domain','project','username','password','process'].sort
+ @@session_reset = @@session_tags - @@session_keep
+
+ def new_session()
+ if @variables.empty?('exa:session') then
+ @session_id = new_session_id
+ else
+ @session_id = @variables.get('exa:session')
+ end
+ if @session_id == 'default' then # ???
+ @session_id = new_session_id
+ end
+ @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
+ register_session
+ return @session_id
+ end
+
+ def reset_session(all=false)
+ (if all then @@session_tags else @@session_reset end).each do |k|
+ @session.set(k)
+ end
+ end
+
+ def valid_session
+ @session_id = request_variable('id')
+ if @session_id.empty? then
+ begin
+ if @cgi then
+ if @session_id = @cgi.cookies['session_id'] then
+ @session_id = @session_id.first || ''
+ else
+ @session_id = ''
+ end
+ elsif @webrick_request then
+ @webrick_request.cookies.flatten.each do |cookie|
+ if cookie.name == 'session_id' then
+ unless cookie.value.empty? then
+ @session_id = cookie.value
+ # break
+ end
+ end
+ end
+ else
+ @session_id = ''
+ end
+ rescue
+ @interface.set('log:session',"[error in request #{$!}]")
+ return false
+ end
+ end
+ if @session_id.empty? then
+ @interface.set('log:session','[no id, check work dir permissions]')
+ return false
+ else
+ @interface.set('log:session',@session_id)
+ load_session
+ if ! @session.empty?('domain') && ! @session.empty?('project') && ! @session.empty?('username') then
+ register_session
+ return @session_id
+ else
+ return false
+ end
+ end
+ end
+
+ def touch_session(id=nil)
+ begin
+ t = Time.now
+ File.utime(t,t,File.join(work_root,"#{@@session_prefix}#{id || @session_id}.ses")) rescue false
+ rescue
+ false
+ end
+ end
+
+ def forced_session
+ @session_id = new_session
+ if @session_id.empty? then
+ @interface.set('log:session','[no id, check work dir permissions]')
+ return false
+ else
+ return check_session
+ end
+ end
+
+ def client_session
+ request, done = @variables.get('exa:request'), false
+ request.sub!(/(^.*<exa:request[^>]*>.*?)\s*<exa:client>\s*(.*)\s*<\/exa:client>\s*(.*?<\/exa:request>.*$)/mio) do
+ pre, client, post = $1, $2, $3
+ client.scan(/<exa:(domain|project|username|password)>(.*?)<\/exa:\1>/mio) do
+ @variables.set($1, $2)
+ end
+ done = true
+ pre + post
+ end
+ if done then
+ return forced_session
+ else
+ return nil
+ end
+ end
+
+ def register_session
+ if @cgi then
+ @cgi_cookie = CGI::Cookie::new(
+ 'name' => 'session_id',
+ 'value' => @session_id,
+ 'expires' => Time.now + @interface.get('process:timeout').to_i
+ )
+ # @cgi_cookie = CGI::Cookie::new('session_id',@session_id)
+ elsif @webrick_response then
+ if cookie = WEBrick::Cookie.new('session_id', @session_id) then
+ cookie.expires = Time.now + @interface.get('process:timeout').to_i
+ cookie.max_age = @interface.get('process:timeout').to_i
+ cookie.comment = 'exa identifier'
+ @webrick_response.cookies.clear
+ @webrick_response.cookies << cookie
+ end
+ end
+ end
+
+ def new_session_id # taken from cgi
+ md5 = Digest::MD5::new
+ now = Time::now
+ md5.update(now.to_s)
+ md5.update(String(now.usec))
+ md5.update(String(rand(0)))
+ md5.update(String($$))
+ md5.update('foobar')
+ @new_session = true
+ md5.hexdigest[0,32] # was 16
+ end
+
+ @@hide_passwords = true
+ HIDDEN = 'hidden'
+
+ def same_passwords(password) # password in cfg file
+ if @@hide_passwords && (@session.get('password') == HIDDEN) && (@session_id == @session.get('id')) then
+ # this condition is only true when a same session id is found and
+ # the password is checked once and set to HIDDEN
+ same = true
+ elsif password =~ /^MD5:/ then
+ # so, one cannot send a known encrypted password since it will be
+ # encrypted twice then
+ same = (password == "MD5:" + MD5.new(@session.get('password')).hexdigest.upcase)
+ else
+ if (@session.default?('domain') && @session.default?('project') && @session.default?('username')) then
+ @session.default('password') # is this safe enough?
+ end
+ same = (password == @session.get('password'))
+ end
+ if @@hide_passwords && same then
+ @session.set('password', HIDDEN)
+ save_session # next time this session is ok anyway
+ end
+ return same
+ end
+
+ @@session_line = /^\s*(?![\#\%])(.*?)\s*\=\s*(.*?)\s*$/o
+ @@session_begin = 'begin exa session'
+ @@session_end = 'end exa session'
+
+ def loaded_session_data(filename)
+ begin
+ if data = IO.readlines(filename) then
+ return data if (data.first =~ /^[\#\%]\s*#{@@session_begin}/o) && (data.last =~ /^[\#\%]\s*#{@@session_end}/o)
+ end
+ rescue
+ end
+ return nil
+ end
+
+ def load_session()
+ begin
+ @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
+ if data = loaded_session_data(@session_file) then
+ data.each do |line|
+ if line =~ @@session_line then
+ @session.set($1, $2 || '')
+ end
+ end
+ else
+ return false
+ end
+ rescue
+ return false
+ else
+ return true
+ end
+ end
+
+ def load_session_file(filename)
+ begin
+ if data = loaded_session_data(filename) then
+ session = Hash.new
+ data.each do |line|
+ if line =~ @@session_line then
+ session[$1] = $2 || ''
+ end
+ end
+ else
+ Hash.new
+ end
+ rescue
+ Hash.new
+ else
+ session
+ end
+ end
+
+ def save_session
+ begin
+ unless @session_id.empty? then
+ @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
+ @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
+ File.open(@session_file,'w') do |f|
+ f << "\# #{@@session_begin}\n"
+ @@session_tags.each do |tag|
+ if @session && @session.key?(tag) then
+ if ! @session.get(tag).empty? then # no one liner, fails
+ f << "#{tag}=#{@session.get(tag)}\n"
+ end
+ elsif @variables.key?(tag) && ! @variables.empty?(key) then
+ f << "#{tag}=#{@variables.get(tag)}\n"
+ end
+ end
+ @session.subset("ENV").keys.each do |tag|
+ f << "#{tag}=#{@session.get(tag)}\n"
+ end
+ f << "\# #{@@session_end}\n"
+ end
+ end
+ rescue
+ return false
+ else
+ return true
+ end
+ end
+
+ def logged_in_session(force_default=false)
+ if force_default || (@variables.default?('domain') && @variables.default?('project') && @variables.default?('username')) then
+ id = default_session
+ else
+ id = check_session
+ end
+ end
+
+ def default_session
+ if @interface.true?('process:autologin') then
+ @variables.default('domain')
+ @variables.default('project')
+ @variables.default('username')
+ @variables.default('password')
+ check_session
+ else
+ @session_id = nil
+ end
+ end
+
+ def check_session
+ @session.set('domain', @variables.get('domain').downcase)
+ @session.set('project', @variables.get('project').downcase)
+ @session.set('username', @variables.get('username').downcase)
+ @session.set('password', @variables.get('password').downcase)
+ new_session
+ @session.set('id', @session_id)
+ save_session
+ return @session_id
+ end
+
+ def delete_session(id=nil)
+ File.delete(work_root,"#{@@session_prefix}#{id || @session_id}.ses") rescue false
+ end
+
+ def cleanup_sessions(max_age=nil)
+ begin
+ now, age = Time.now, (max_age||@interface.get('process:timeout')).to_i
+ Dir.glob("{#{work_root},#{cache_root}/#{@@session_prefix}*").each do |s|
+ begin
+ if (now - File.mtime(s)) > age then
+ if FileTest.directory?(s) then
+ FileUtils::rm_r(s)
+ else
+ File.delete(s)
+ end
+ end
+ rescue
+ # maybe purged in the meantime
+ end
+ end
+ rescue
+ # maybe another process is busy
+ end
+ end
+
+end
+
+# templates
+
+class WWW
+
+ def filled_template(title,text,showtime=false,refresh=0,refreshurl=nil)
+ template = @interface.get("template:#{@interface.get('tag:template')}")
+ template = @interface.get("template:status") if template.empty?
+ fullname = File.join(@interface.get('path:templates'),template)
+ @interface.set('log:templatename',template)
+ @interface.set('log:templatefile',fullname)
+ append_status(text)
+ htmreply = ''
+ if FileTest.file?(fullname) then
+ begin
+ htmreply = IO.read(fullname)
+ rescue
+ htmreply = ''
+ end
+ end
+ if refresh>0 then
+ if refreshurl then
+ metadata = "<meta http-equiv='refresh' content='#{refresh};#{refreshurl}'>"
+ else
+ metadata = "<meta http-equiv='refresh' content='#{refresh}'>"
+ end
+ else
+ metadata = ''
+ end
+ if ! htmreply || htmreply.empty? then
+ # in head: <link rel='stylesheet' href='/exaresource/exastyle.css'>
+ htmreply = <<-EOD
+ <html>
+ #{metadata}
+ <head>
+ <title>#{title}</title>
+ </head>
+ <body>
+ <h2>#{title}</h2>
+ <h4>#{Time.now}</h4>
+ #{text}
+ </body>
+ </html>
+ EOD
+ else
+ if showtime then
+ exa_template = "<h1>#{title}</h1>\n<h2>#{Time.now}</h2>\n#{text}\n"
+ else
+ exa_template = "<h1>#{title}</h1>#{text}\n"
+ end
+ htmreply = replace_template_placeholder(htmreply,exa_template,metadata)
+ end
+ htmreply
+ end
+
+ def message(title,str='',showtime=false,refresh=0,refreshurl=nil)
+ if @cgi then
+ @cgi.out("cookie"=>[@cgi_cookie]) do
+ filled_template(title,str,showtime,refresh,refreshurl)
+ end
+ elsif @webrick_response then
+ @webrick_response['content-type'] = 'text/html'
+ @webrick_response.body = filled_template(title,str,showtime,refresh,refreshurl)
+ else
+ filled_template(title,str,showtime,refresh,refreshurl)
+ end
+ end
+
+ def plaintext(str)
+ if @cgi then
+ @cgi.out('cookie'=>[@cgi_cookie],'content-type'=>'text/plain') do
+ str
+ end
+ elsif @webrick_response then
+ @webrick_response['content-type'] = 'text/plain'
+ @webrick_response.body = str
+ else
+ str
+ end
+ end
+
+ def exareply(status='',url='',size='',comment='')
+ exaurl = @interface.get('process:exaurl')
+ str = "<?xml version='1.0'?>\n\n"
+ str << "<exa:reply xmlns:exa='#{@@namespace}'>\n"
+ str << " <exa:session>#{@session_id}</exa:session>\n" unless @session_id.empty?
+ str << " <exa:status>#{status}</exa:status>\n" unless (status || '').empty?
+ str << " <exa:url>#{exaurl}/#{url}</exa:url>\n" unless (url || '').empty?
+ str << " <exa:size>#{size}</exa:size>\n" unless (size || '').empty?
+ str << " <exa:comment>#{comment}</exa:comment>\n" unless (comment|| '').empty?
+ str << "</exa:reply>\n"
+ return str
+ end
+
+ def append_status(str='')
+ if @interface.true?('trace:errors') then
+ if $! && $@ then
+ str << "<br/><br/><br/><em>Error:</em><br/><pre>#{$!}</pre><pre>"
+ str << $@.join("\n")
+ end
+ str << '<br/><br/><br/>'
+ str << status_data
+ str << '<em>Paths</em><br/>'
+ str << '<pre>'
+ @interface.subset('path:').each do |k,v|
+ if FileTest.directory?(v) then
+ if FileTest.writable?(v) then
+ str << "#{v} exists and is writable\n"
+ else
+ str << "#{v} is not writable\n"
+ end
+ else
+ str << "#{v} does not exist\n"
+ end
+ end
+ str << '</pre>'
+ end
+ str
+ end
+
+ def simpleurl(url)
+ if url then url.sub(/(:80|:443)$/,'') else '' end
+ end
+
+ def replace_exa_placeholders(data)
+ data.gsub(/([\"\'])\@exa\_([a-zA-Z0-9\-\_]+)\1/) do
+ quot, key, value = $1, $2, ''
+ begin
+ value = @variables.get(key)
+ rescue
+ value = ''
+ end
+ quot + value + quot
+ end
+ end
+
+ def replace_url_placeholder(data)
+ data.gsub!(/(http:\/\/|\/+)*\@exa\_main\_url/, @interface.get('process:exaurl'))
+ replace_exa_placeholders(data)
+ end
+
+ def replace_template_placeholder(data,template='',metadata='')
+ data.gsub!(/(http:\/\/|\/+)*\@exa\_main\_url/, @interface.get('process:exaurl'))
+ data.gsub!(/\@exa\_template/, template)
+ data.gsub!(/\@exa\_metadata/, metadata)
+ replace_exa_placeholders(data)
+ end
+
+ def escaped(str)
+ str
+ end
+
+end
+
+# send files
+
+class WWW
+
+ def send_file(filename,parse=false) # this can take a lot of memory, look for alternative (fastcgi ?)
+ begin
+ if filename =~ /\.pdf$/ then
+ mimetype, parse = 'application/pdf', false
+ elsif filename =~ /\.(html|htm)$/ then
+ mimetype, parse = 'text/html', true
+ else
+ mimetype, parse = 'text/plain', false
+ end
+ if FileTest.file?(filename) then
+ if @webrick_response then
+ begin
+ @webrick_response['content-type'] = mimetype
+ @webrick_response['content-length'] = FileTest.size?(filename)
+ if parse then
+ File.open(filename, 'rb') do |f|
+ @webrick_response.body = replace_url_placeholder(f.read)
+ end
+ else
+ @webrick_response.body = File.open(filename, 'rb')
+ end
+ rescue
+ else
+ return
+ end
+ elsif @cgi then
+ begin
+ # the following works ok, but stores the whole file in memory (see @cgi.out)
+ #
+ # File.open(filename, 'rb') do |f|
+ # @cgi.out('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype) do
+ # if parse then replace_url_placeholder(f.read) else f.read end
+ # end
+ # end
+ if parse then
+ File.open(filename, 'rb') do |f|
+ @cgi.out('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype) do
+ replace_url_placeholder(f.read)
+ end
+ end
+ else
+ @cgi.print(@cgi.header('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype))
+ File.open(filename, 'rb') do |f|
+ while str = f.gets do
+ @cgi.print(str)
+ end
+ end
+ end
+ rescue
+ else
+ return
+ end
+ end
+ end
+ rescue
+ end
+ message('Error', "There is a problem with sending file #{File.basename(filename)}.")
+ end
+
+ def send_htmlfile(filename,parse=false)
+ send_file(filename,parse)
+ end
+ def send_pdffile(filename) # this can take a lot of memory, look for alternative (fastcgi ?)
+ send_file(filename,false)
+ end
+
+end
+
+# tracing
+
+class WWW
+
+ def show_vars(a=@variables,title='')
+ if a && a.length > 0 then
+ if title.empty? then
+ str = ''
+ else
+ str = "<em>#{title}</em>"
+ end
+ str << "<br/><pre>\n"
+ a.keys.sort.each do |k|
+ if k && a[k] && ! a[k].empty? then
+ if k == 'password' then
+ val = if a[k] == 'default' then 'default' else '******' end
+ else
+ # str << "#{k} => #{a[k].sub(/^\s+/moi,'').sub(/\s+$/moi,'')}\n"
+ val = a[k].to_s.strip
+ val.gsub!("&","&amp;")
+ val.gsub!("<","&lt;")
+ val.gsub!(">","&gt;")
+ val.gsub!("\n","\n ")
+ end
+ str << "#{k} => #{val}\n"
+ end
+ end
+ str << "</pre><br/>\n"
+ return str
+ else
+ return ''
+ end
+ end
+
+ def status_data
+ show_vars(@session , 'Session' ) +
+ show_vars(@variables, 'Variables' ) +
+ show_vars(@interface, 'Interface' ) +
+ show_vars(ENV , 'Environment')
+ end
+
+ def report_status
+ check_template_file('status')
+ message('Status',status_data)
+ end
+
+end
+
+# attachments
+
+class WWW
+
+ def extract_sent_files(dir)
+ files = Array.new
+ if @cgi then
+ @cgi.params.keys.each do |tag|
+ begin
+ if filename = @cgi[tag].original_filename then
+ files << extract_file_content(dir,filename,@cgi[tag]) unless filename.empty?
+ end
+ rescue
+ end
+ end
+ elsif @webrick_request then
+ @webrick_request.query.keys.each do |tag|
+ begin
+ if filename = @webrick_request.query[tag].filename then
+ files << extract_file_content(dir,filename,@webrick_request.query[tag]) unless filename.empty?
+ end
+ rescue
+ end
+ end
+ end
+ @interface.set('log:attachments', files.compact.uniq.join('|'))
+ end
+
+ def extract_file_content(dir,filename,data)
+ filename = File.join(dir,File.basename(filename))
+ begin
+ @interface.set('log:attachclass', data.class.inspect)
+ if data.class == Tempfile then
+ begin
+ File.copy(data.path,filename)
+ rescue
+ begin
+ File.open(filename,'wb') do |f|
+ File.open(data.path,'rb') do |g|
+ while str = g.gets do
+ f.write(str)
+ end
+ end
+ end
+ rescue
+ @interface.set('log:attachstate', "saving tempfile #{filename} failed (#{$!})")
+ else
+ @interface.set('log:attachstate', "tempfile #{filename} has been saved")
+ end
+ else
+ @interface.set('log:attachstate', "#{data.path} copied to #{filename}")
+ end
+ elsif data.class == String then
+ begin
+ File.open(filename,'wb') do |f|
+ f.write(data)
+ end
+ rescue
+ @interface.set('log:attachstate', "saving string #{filename} failed (#{$!})")
+ else
+ @interface.set('log:attachstate', "string #{filename} has been saved")
+ end
+ elsif data.class == StringIO then
+ begin
+ File.open(filename,'wb') do |f|
+ f.write(data.read)
+ end
+ rescue
+ @interface.set('log:attachstate', "saving stringio #{filename} failed (#{$!})")
+ else
+ @interface.set('log:attachstate', "stringio #{filename} has been saved")
+ end
+ else
+ @interface.set('log:attachstate', "unknown attachment class #{data.class.to_s}")
+ end
+ rescue
+ begin File.delete(filename) ; rescue ; end
+ else
+ begin File.delete(filename) if FileTest.size(filename) == 0 ; rescue ; end
+ end
+ return File.basename(filename)
+ end
+
+end
+
+# configuration
+
+class WWW
+
+ def interface_base_name(str)
+ str.sub(/\.(pdf|htm|html)$/, '')
+ end
+
+ def located_interface_file(filename)
+ ['configurations', 'runners', 'scripts'].each do |tag|
+ datafile = File.join(@interface.get("path:#{tag}"),filename)
+ if FileTest.file?(datafile+'.encrypted') then
+ return datafile + '.encrypted'
+ elsif FileTest.file?(datafile) then
+ return datafile
+ end
+ end
+ return nil
+ end
+
+ def load_interface_file(filename=@@data_file)
+ reset_session() # no save yet
+ if datafile = located_interface_file(filename) then
+ nestedfiles = Array.new
+ begin
+ data = IO.read(datafile) || ''
+ unless data.empty? then
+ loop do # we need to load them recursively
+ done = false
+ data.gsub!(/^include\s*:\s*(.*?)\s*$/) do
+ includedname, done = $1, true
+ if nestedname = located_interface_file(includedname) then
+ begin
+ str = ("\n" + IO.read(nestedname) + "\n") || ''
+ rescue
+ nestedfiles << File.basename('-'+includedname)
+ ''
+ else
+ nestedfiles << File.basename('+'+includedname)
+ str
+ end
+ else
+ nestedfiles << File.basename('-'+includedname)
+ ''
+ end
+ end
+ break unless done
+ end
+ end
+ @interface.set('log:configurationfile', datafile + ' [' + nestedfiles.join(' ') + ']')
+ return data
+ rescue
+ end
+ end
+ @interface.set('log:configurationfile', filename + ' [not loaded]')
+ return nil
+ end
+
+ def fetch_session_interface_variables(data)
+ data.scan(/^variable\s*:\s*(.*?)\s*\=\s*(.*?)\s*$/) do
+ @interface.set($1, $2)
+ end
+ return true
+ end
+
+ def fetch_session_project_list(data)
+ projectlist, permitted = Array.new, false
+ data.scan(/^user\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*\,\s*(.*?)\s*$/) do
+ domain, username, password, projects = $1, $2, $3, $4
+ if @session.match?('domain',domain) && @session.match?('username',username) then
+ if same_passwords(password) then
+ projectlist, permitted = @interface.resolved(projects).split(@@re_bar), true
+ break
+ end
+ end
+ end
+ if permitted then
+ @interface.set('log:projectlist', '['+projectlist.join(' ')+']')
+ if projectlist.length == 0 then
+ return nil
+ else
+ return projectlist
+ end
+ else
+ @interface.set('log:projectlist', '[no projects]')
+ return nil
+ end
+ end
+
+ def fetch_session_command(data)
+ data.scan(/^process\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*$/) do
+ domain, process, command = $1, $2, $3
+ if @session.match?('domain',domain) && @session.match?('process',process) then
+ @session.set('command', @interface.resolved(command))
+ end
+ end
+ return @session.get('command')
+ end
+
+ def fetch_session_settings(data)
+ data.scan(/^setting\s*:\s*(.*?)\s*\,\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*$/) do
+ domain, process, variable, value = $1, $2, $3, $4
+ if @session.match?('domain',domain) && @session.match?('process',process) then
+ @interface.set(variable,value)
+ end
+ end
+ end
+
+ def get_command(action)
+ # @session.set('action', action)
+ # if @session.get('process') == 'none' then
+ # @interface.set('log:child','yes')
+ # @session.set('process', action)
+ # end
+ if data = load_interface_file() then
+ fetch_session_interface_variables(data)
+ if projectlist = fetch_session_project_list(data) then
+ data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
+ domain, project, gui, path, process = $1, $2, $3, $4, $5
+ if @session.match?('domain',domain) then
+ if @session.match?('project',project) then
+ if projectlist.include?(project) then
+ @session.set('process', @interface.resolved(process))
+ # break # no, else we end up in the parent (e.g. examplap instead of impose)
+ end
+ elsif ! action.empty? && project == action then
+ if projectlist.include?(action) then
+ @session.set('process', @interface.resolved(process))
+ # break # no, else we end up in the parent (e.g. examplap instead of impose)
+ end
+ end
+ end
+ end
+ fetch_session_command(data)
+ fetch_session_settings(data)
+ end
+ end
+ return ! @session.nothing?('command')
+ end
+
+ def get_file(filename)
+ @session.set('filename', filename)
+ if data = load_interface_file() then
+ fetch_session_interface_variables(data)
+ if projectlist = fetch_session_project_list(data) then
+ data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
+ domain, project, gui, path, process = $1, $2, $3, $4, $5
+ if @session.match?('domain',domain) then
+ guilist = @interface.resolved(gui).split(@@re_bar)
+ guilist.each do |g|
+ if /#{filename}$/ =~ g then
+ @session.set('gui', File.expand_path(@interface.resolved(g)))
+ @session.set('path', File.expand_path(@interface.resolved(path)))
+ @session.set('process', process)
+ break # take first matching interface
+ end
+ end
+ end
+ end
+ end
+ end
+ return ! (@session.nothing?('gui') && @session.nothing?('path') && @session.nothing?('process'))
+ end
+
+ def get_path(url='')
+ if data = load_interface_file() then
+ fetch_session_interface_variables(data)
+ if projectlist = fetch_session_project_list(data) then
+ data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
+ domain, project, gui, path, process = $1, $2, $3, $4, $5
+ if @session.match?('domain',domain) && @session.match?('project',project) then
+ @session.set('url', url)
+ @session.set('gui', '')
+ @session.set('path', File.expand_path(@interface.resolved(path)))
+ @session.set('process', '')
+ end
+ end
+ end
+ end
+ return ! @session.nothing?('path')
+ end
+
+ def get_gui()
+ if data = load_interface_file() then
+ fetch_session_interface_variables(data)
+ if projectlist = fetch_session_project_list(data) then
+ data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
+ domain, project, gui, path, process = $1, $2, $3, $4, $5
+ if @session.match?('domain',domain) && @session.match?('project',project) && projectlist.include?(project) then
+ @session.set('gui', File.expand_path(@interface.resolved(gui)))
+ @session.set('path', File.expand_path(@interface.resolved(path)))
+ @session.set('process', process) unless process == 'none'
+ break # take first matching interface
+ end
+ end
+ data.scan(/^admin\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*\,\s*(.*?)\s*$/) do
+ domain, project, task, option = $1, $2, $3, $4
+ if @session.match?('domain',domain) && @session.match?('project',project) && projectlist.include?(project) then
+ @session.set('task', task)
+ @session.set('option', option)
+ break # take first matching task
+ end
+ end
+ end
+ end
+ return ! (@session.nothing?('gui') && @session.nothing?('path') && @session.nothing?('process'))
+ end
+
+end
+
+class WWW
+
+ def send_reply(logdata='')
+ if @interface.true?('trace:run') then
+ send_result(logdata)
+ else
+ dir, tmp = dirname, tmp_path(dirname)
+ case @session.get('status')
+ when 'running: finished' then
+ resultname, replyname = 'result.pdf', 'reply.exa'
+ replyfile = File.join(tmp,replyname)
+ if FileTest.file?(replyfile) then
+ begin
+ data = IO.read(replyfile)
+ resultname = if data =~ /<exa:output>(.*?)<\/exa:output>/ then $1 else resultname end
+ rescue
+ plaintext(exareply('error in reply'))
+ return
+ end
+ end
+ resultfile = File.join(tmp,resultname)
+ if FileTest.file?(resultfile) then
+ if indirect?(resultfile) then
+ begin
+ File.makedirs(File.join(cache_root,dir))
+ FileUtils::mv(resultfile,File.join(cache_root,dir,resultname))
+ rescue
+ plaintext(exareply('unable to access cache'))
+ else
+ plaintext(exareply('big file', "cache/#{dir}/#{resultname}", "#{File.size?(resultfile)}"))
+ end
+ else
+ send_file(resultfile)
+ end
+ else
+ plaintext(exareply('no result'))
+ end
+ else # background, running, aborted
+ plaintext(exareply(@session.get('status')))
+ end
+ end
+ end
+
+ def send_url(fullname)
+ dir, tmp = dirname, tmp_path(dirname)
+ resultname, replyname = 'result.pdf', 'reply.exa'
+ replyfile = File.join(tmp,replyname)
+ resultfile = File.join(tmp,resultname)
+ if FileTest.file?(fullname) then
+ if indirect?(fullname) then
+ begin
+ File.makedirs(File.join(cache_root,dir))
+ targetname = File.join(cache_root,dir,resultname)
+ File.delete(targetname) rescue false # left overs
+ File.symlink(fullname,targetname) rescue message('Status',$!)
+ unless FileTest.file?(targetname) then
+ FileUtils::cp(fullname,targetname) rescue false
+ end
+ rescue
+ plaintext(exareply('unable to access cache'))
+ else
+ plaintext(exareply('big file', "cache/#{dir}/#{resultname}", "#{File.size?(fullname)}"))
+ end
+ else
+ send_file(fullname)
+ end
+ else
+ message('Status', 'The file is not found')
+ end
+ end
+
+ def send_result(logdata='')
+ check_template_file('exalogin','exalogin-template.htm')
+ dir, tmp = dirname, tmp_path(dirname)
+ resultname, replyname, logname = 'result.pdf', 'reply.exa', 'log.htm'
+ case @session.get('status')
+ when 'running: background' then
+ if st = @session.get('starttime') then # fuzzy
+ st = Time.now.to_i if st.empty?
+ if (Time.now.to_i - st.to_i) > @interface.get('process:timeout').to_i then
+ message('Status', 'Your request has been aborted (timeout)',true)
+ else
+ message('Status', 'Your request is queued',true,5,'exastatus')
+ end
+ end
+ when 'running: busy' then
+ if st = @session.get('starttime') then # fuzzy
+ st = Time.now.to_i if st.empty?
+ if (Time.now.to_i - st.to_i) > @interface.get('process:timeout').to_i then
+ message('Status', 'Your request has been aborted (timeout)',true)
+ else
+ message('Status', 'Your request is being processed',true,5,'exastatus')
+ end
+ end
+ when 'running: aborted' then
+ message('Status', 'Your request has been aborted (timeout)',true)
+ when 'running: finished' then
+ if @interface.true?('trace:run') then
+ logfile = File.join(tmp,logname)
+ begin
+ if f = File.open(logname,'w') then
+ if logdata.empty? then
+ begin
+ logdata = IO.read('www-watch.out')
+ rescue
+ logdata = 'no log data'
+ end
+ end
+ f << filled_template('Log',"<pre>#{CGI::escapeHTML(logdata)}</pre>")
+ f.close
+ end
+ rescue
+ message('Error', '')
+ end
+ if FileTest.file?(logfile) then
+ begin
+ File.makedirs(File.join(cache_root,dir))
+ FileUtils::mv(logfile,File.join(cache_root,dir,logname))
+ rescue
+ logdata = "<br/><br/>unable to access cache</a>"
+ else
+ logdata = "<br/><br/><a href='/cache/#{dir}/#{logname}'>#{logname}</a>"
+ end
+ else
+ logdata = ''
+ end
+ else
+ logdata = ''
+ end
+ # todo: generate reply.exa if no reply
+ replyfile = File.join(tmp,replyname)
+ if FileTest.file?(replyfile) then
+ begin
+ data = IO.read(replyfile)
+ resultname = if data =~ /<exa:output>(.*?)<\/exa:output>/ then $1 else resultname end
+ rescue
+ message('Error','There is a problem in handling this request (invalid reply).')
+ return
+ end
+ end
+ resultfile = File.join(tmp,resultname)
+ if FileTest.file?(resultfile) then
+ if indirect?(resultfile) then
+ begin
+ File.makedirs(File.join(cache_root,dir))
+ FileUtils::mv(resultfile,File.join(cache_root,dir,resultname))
+ rescue
+ str = "<br/><br/>unable to access cache</a>"
+ else
+ str = "<br/><br/><a href='/cache/#{dir}/#{resultname}'>#{resultname}</a>&nbsp;&nbsp;(#{File.size?(resultname)} bytes)"
+ end
+ message('Result', 'You can pick up the result here:' + str + logdata)
+ else
+ send_file(resultfile)
+ end
+ else
+ message('Error', 'There is a problem in handling this request (no result file).' + logdata)
+ end
+ end
+ end
+
+end
diff --git a/scripts/context/ruby/www/login.rb b/scripts/context/ruby/www/login.rb
new file mode 100644
index 000000000..1c88a97e6
--- /dev/null
+++ b/scripts/context/ruby/www/login.rb
@@ -0,0 +1,13 @@
+require 'www/lib'
+
+# basic login
+
+class WWW
+
+ def handle_login()
+ check_template_file('login','exalogin.htm')
+ set('password', '')
+ message('Login','')
+ end
+
+end
diff --git a/scripts/context/ruby/wwwclient.rb b/scripts/context/ruby/wwwclient.rb
new file mode 100644
index 000000000..8f5451a8d
--- /dev/null
+++ b/scripts/context/ruby/wwwclient.rb
@@ -0,0 +1,677 @@
+#!/usr/bin/env ruby
+
+# a direct request is just passed on
+#
+# exaclient --direct --request=somerequest.exa --result=somefile.pdf
+#
+# in an extended request the filename in the template file is replaced by the filename
+# given on the command line; templates are located on the current path and at parent
+# directories (two levels); the filename is expanded to a full path
+#
+# exaclient --extend --template=tmicare-l-h.exa --file=somefile.xml --result=somefile.pdf
+#
+# a constructed request is build out of the provided filename and action; the filename is
+# expanded to a full path
+#
+# exaclient --construct --action=tmicare-s-h.exa --file=somefile.xml --result=somefile.pdf
+#
+# in all cases, the result is either determined by a switch or taken from a reply file
+
+banner = ['WWWClient', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
+
+$: << File.dirname(File.expand_path($0))
+
+require 'base/switch'
+require 'base/logger'
+
+require 'timeout'
+require 'thread'
+require 'rexml/document'
+require 'net/http'
+
+class File
+
+ def File.backtracked(filename,level=3)
+ if level > 0 && filename && ! filename.empty? then
+ if FileTest.file?(filename) then
+ filename
+ else
+ File.backtracked('../'+filename,level-1)
+ end
+ else
+ filename
+ end
+ end
+
+ def File.expanded(filename)
+ File.expand_path(filename)
+ end
+
+end
+
+class Commands
+
+ include CommandBase
+
+end
+
+class Commands
+
+ @@namespace = "xmlns:exa='http://www.pragma-ade.com/schemas/example.rng'"
+ @@randchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "abcdefghijklmnopqrstuvwxyz"
+
+ def traceback
+ "(error: #{$!})" + "\n -- " + $@.join("\n >>")
+ end
+
+ def pdf(action,filename,enabled)
+ if enabled && FileTest.file?(filename) then
+ begin
+ report("pdf action #{action} on #{filename}")
+ case action
+ when 'close' then system("pdfclose --all")
+ when 'open' then system("pdfopen --file #{filename}")
+ end
+ rescue
+ # forget about it
+ end
+ end
+ end
+
+ def status(replyfile,str) # when block, then ok
+ begin
+ # def status(*whatever)
+ # end
+ File.open(replyfile,'w') do |f|
+ report("saving reply info in '#{replyfile}'")
+ f.puts("<?xml version='1.0'?>\n\n")
+ f.puts("<exa:reply #{@@namespace}>\n")
+ if block_given? then
+ f.puts(" <exa:status>ok</exa:status>\n")
+ f.puts(" #{yield}\n")
+ else
+ f.puts(" <exa:status>error</exa:status>\n")
+ end
+ f.puts(" <exa:comment>" + str + "</exa:comment>\n")
+ f.puts("</exa:reply>\n")
+ f.close
+ report("saving status: #{str}")
+ end
+ rescue
+ report("saving reply info in '#{replyfile}' fails")
+ ensure
+ exit
+ end
+ exit # to be real sure
+ end
+
+
+ def boundary_string (length) # copied from webrick/utils
+ rand_max = @@randchars.size
+ ret = ""
+ length.times do
+ ret << @@randchars[rand(rand_max)]
+ end
+ ret.upcase
+ end
+
+end
+
+class Commands
+
+ @@connecttimeout = 10*60 # ten minutes
+ @@processtimeout = 60*60 # an hour
+ @@polldelay = 5 # 5 seconds
+
+ def main
+
+ datatemplate = @commandline.option('template')
+ datafile = @commandline.option('file')
+ dataaction = @commandline.option('action')
+
+ if ! datatemplate.empty? then
+ report("template '#{datatemplate}' specified without --construct")
+ report("aborting")
+ elsif ! dataaction.empty? then
+ report("action data '#{dataaction}' specified without --construct or --extend")
+ report("aborting")
+ elsif ! datafile.empty? then
+ report("action file '#{datafile}' specified without --construct or --extend")
+ report("aborting")
+ else
+ report("assuming --direct")
+ direct()
+ end
+
+ end
+
+ def construct
+
+ requestfile = @commandline.option('request')
+ replyfile = @commandline.option('reply')
+
+ datatemplate = @commandline.option('template')
+ datafile = @commandline.option('file')
+ dataaction = @commandline.option('action')
+
+ domain = @commandline.option('domain')
+ project = @commandline.option('project')
+ username = @commandline.option('username')
+ password = @commandline.option('password')
+
+ threshold = @commandline.option('threshold')
+
+ datablob = ''
+
+ begin
+ datablob = IO.read(datatemplate)
+ rescue
+ datablob = ''
+ else
+ begin
+ request = REXML::Document.new(datablob)
+ if e = REXML::XPath.match(request.root,"/exa:request/exa:data") then
+ datablob = e.to_s.chomp
+ end
+ rescue
+ datablob = ''
+ end
+ end
+
+ begin
+ File.open(requestfile,'w') do |f|
+ f.puts "<?xml version='1.0'?>\n"
+ f.puts "<exa:request #{@@namespace}>\n"
+ f.puts " <exa:application>\n"
+ f.puts " <exa:action>#{dataaction}</exa:action>\n" unless dataaction.empty?
+ f.puts " <exa:filename>#{datafile}</exa:filename>\n" unless datafile.empty?
+ f.puts " <exa:threshold>#{threshold}</exa:threshold>\n" unless threshold.empty?
+ f.puts " </exa:application>\n"
+ f.puts " <exa:client>\n"
+ f.puts " <exa:domain>#{domain}</exa:domain>\n"
+ f.puts " <exa:project>#{project}</exa:project>\n"
+ f.puts " <exa:username>#{username}</exa:username>\n"
+ f.puts " <exa:password>#{password}</exa:password>\n"
+ f.puts " </exa:client>\n"
+ if datablob.empty? then
+ f.puts " <exa:data/>\n"
+ else
+ f.puts " #{datablob.chomp}\n"
+ end
+ f.puts "</exa:request>"
+ end
+ rescue
+ status(replyfile,"unable to create '#{requestfile}'")
+ end
+
+ direct()
+
+ end
+
+ def extend
+
+ requestfile = @commandline.option('request')
+ replyfile = @commandline.option('reply')
+
+ datatemplate = @commandline.option('template')
+ datafile = @commandline.option('file')
+ dataaction = @commandline.option('action')
+
+ threshold = @commandline.option('threshold')
+
+ if datatemplate.empty? then
+ status(replyfile,"invalid data template '#{datatemplate}'")
+ else
+ begin
+ if FileTest.file?(datatemplate) && oldrequest = IO.read(datatemplate) then
+ request, done = REXML::Document.new(oldrequest), false
+ if ! threshold.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:threshold") then
+ e.text, done = threshold, true
+ end
+ if ! dataaction.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:action") then
+ e.text, done = dataaction, true
+ end
+ if ! datafile.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:filename") then
+ e.text, done = datafile, true
+ end
+ #
+ if ! threshold.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application") then
+ e = e.add_element('exa:threshold')
+ e.add_text(threshold.to_s)
+ done = true
+ end
+ #
+ report("nothing replaced in template file") unless done
+ begin
+ File.open(requestfile,'w') do |f|
+ f.puts(newrequest.to_s)
+ end
+ rescue
+ status(replyfile,"unable to create '#{requestfile}'")
+ end
+ else
+ status(replyfile,"unable to read data template '#{datatemplate}'")
+ end
+ rescue
+ status(replyfile,"unable to handle data template '#{datatemplate}'")
+ end
+ end
+
+ direct()
+
+ end
+
+ def direct
+
+ requestpath = @commandline.option('path')
+ requestfile = @commandline.option('request')
+ replyfile = @commandline.option('reply')
+ resultfile = @commandline.option('result')
+ datatemplate = @commandline.option('template')
+ datafile = @commandline.option('file')
+ threshold = @commandline.option('threshold')
+ address = @commandline.option('address')
+ port = @commandline.option('port')
+ session_id = @commandline.option('session')
+ exaurl = @commandline.option('exaurl')
+
+ exaurl = "/#{exaurl}" unless exaurl =~ /^\//
+
+ address.sub!(/^http\:\/\//io) do
+ ''
+ end
+ address.sub!(/\:(\d+)$/io) do
+ port = $1
+ ''
+ end
+
+ autopdf = @commandline.option('autopdf')
+
+ dialogue = nil
+
+ resultfile.sub!(/\.[a-z]+?$/, '') # don't overwrite the source
+
+ unless requestpath.empty? then
+ begin
+ if FileTest.directory?(requestpath) then
+ if Dir.chdir(requestpath) then
+ report("gone to path '#{requestpath}'")
+ else
+ status(replyfile,"unable to go to path '#{requestpath}")
+ end
+ else
+ status(replyfile,"unable to locate '#{requestpath}'")
+ end
+ rescue
+ status(replyfile,"unable to handle '#{requestpath}'")
+ end
+ end
+
+ datafile = File.expand_path(datafile) unless datafile.empty?
+ datatemplate = File.backtracked(datatemplate,3) unless datatemplate.empty?
+
+ # request must be valid
+
+ status(replyfile,'no request file') if requestfile.empty?
+ status(replyfile,"invalid request file '#{requestfile}'") unless FileTest.file?(requestfile)
+
+ begin
+ request = IO.readlines(requestfile).join('')
+ request = REXML::Document.new(request)
+ status(replyfile,'invalid request (no request)') unless request.root.fully_expanded_name=='exa:request'
+ status(replyfile,'invalid request (no application block)') unless request.elements['exa:request'].elements['exa.application'] == nil # explicit nil test needed
+ rescue REXML::ParseException
+ status(replyfile,'invalid request (invalid xml file)')
+ rescue
+ status(replyfile,'invalid request (invalid file)')
+ else
+ report("using request file '#{requestfile}'")
+ end
+
+ # request can force session_id
+
+ if session_id && session_id.empty? then
+ begin
+ id = request.elements['exa:request'].elements['exa:application'].elements['exa:session'].text
+ rescue Exception
+ id = ''
+ ensure
+ if id && ! id.empty? then
+ session_id = id
+ end
+ end
+ end
+
+ # request can overload reply name
+
+ begin
+ rreplyfile = request.elements['exa:request'].elements['exa:application'].elements['exa:output'].text
+ rescue Exception
+ rreplyfile = nil
+ ensure
+ if rreplyfile && ! rreplyfile.empty? then
+ replyfile = rreplyfile
+ report("reply file '#{replyfile} set by request'")
+ else
+ report("using reply file '#{replyfile}'")
+ end
+ end
+
+ # request can overload result name
+
+ begin
+ rresultfile = request.elements['exa:request'].elements['exa:application'].elements['exa:result']
+ rescue Exception
+ rresultfile = nil
+ ensure
+ if rresultfile && ! rresultfile.empty? then
+ resultfile = rresultfile
+ report("result file '#{resultfile}' set by request")
+ else
+ report("using result file '#{resultfile}'")
+ end
+ end
+
+ # try to connect to server
+
+ start_time = Time.now
+
+ processtimeout = begin @commandline.option('timeout').to_i rescue @@processtimeout end
+ processtimeout = @@processtimeout if processtimeout == 0 # 'xx'.to_i => 0
+
+ dialogue = start_dialogue(address, port, processtimeout)
+
+ if dialogue then
+ # continue
+ else
+ status(replyfile,'no connection')
+ end
+
+ # post request
+
+ timeout (@@processtimeout-10) do # -10 so that we run into this one first
+ begin
+ report("posting request of type '#{exaurl}'")
+ report("using session id '#{session_id}'") if session_id && ! session_id.empty?
+ firstline, chunks, total = nil, 0, 0
+ body, boundary, crlf = '', boundary_string(32), "\x0d\x0a"
+ body << '--' + boundary + crlf
+ body << "Content-Disposition: form-data; name=\"exa:request\""
+ body << crlf
+ body << "Content-Type: text/plain"
+ body << crlf + crlf
+ body << request.to_s
+ body << crlf + '--' + boundary + crlf
+if session_id && ! session_id.empty? then
+ body << "Content-Disposition: form-data; name=\"exa:session\""
+ body << "Content-Type: text/plain"
+ body << crlf + crlf
+ body << session_id
+ body << crlf + '--' + boundary + crlf
+end
+ begin
+ File.open(datafile,'rb') do |df|
+ body << "Content-Disposition: form-data; name=\"filename\""
+ body << "Content-Type: text/plain"
+ body << crlf + crlf
+ body << datafile
+ body << crlf + '--' + boundary + crlf
+ body << "Content-Disposition: form-data; name=\"fakename\" ; filename=\"#{datafile}\""
+ body << "Content-Type: application/octetstream"
+ body << "Content-Transfer-Encoding: binary"
+ body << crlf + crlf
+ body << df.read
+ body << crlf + '--' + boundary + '--' + crlf
+ end
+ rescue
+ # skip
+ end
+ headers = Hash.new
+ headers['content-type'] = "multipart/form-data; boundary=#{boundary}"
+ headers['content-length'] = body.length.to_s
+ begin
+ File.open(resultfile,'wb') do |rf|
+ begin
+ # firstline is max 1024 but ok for reply
+ dialogue.post(exaurl,body,headers) do |str|
+ if ! firstline || firstline.empty? then
+ report('receiving result') if total == 0
+ firstline = str
+ end
+ total += 1
+ rf.write(str)
+ end
+ rescue
+ report("forced close #{traceback}")
+ end
+ end
+ rescue
+ status(replyfile,'cannot open file')
+ end
+ begin
+ File.delete(resultfile) if File.zero?(resultfile)
+ rescue
+ end
+ unless FileTest.file?(resultfile) then
+ report("deleting empty resultfile")
+ begin
+ File.delete(resultfile)
+ rescue
+ # nice try, an error anyway
+ end
+ status(replyfile,'empty file')
+ else
+ n, id, status = 0, '', ''
+ loop do
+ again = false
+ if ! dialogue then
+ again = true
+ elsif firstline =~ /(\<exa:reply)/moi then
+ begin
+ reply = REXML::Document.new(firstline)
+ id = (REXML::XPath.match(reply.root,"/exa:reply/exa:session/text()") || '').to_s
+ status = (REXML::XPath.match(reply.root,"/exa:reply/exa:status/text()") || '').to_s
+ rescue
+ report("error in parsing reply #{traceback}")
+ break
+ else
+ report("status: #{status}")
+ if (status =~ /^running\s*\:\s*(background|busy)$/i) && (! id.empty?) then
+ report("waiting for status reply (#{n*@@polldelay})")
+ again = true
+ end
+ end
+ end
+ if again then
+ n += 1
+ sleep(@@polldelay) # todo: duplicate when n > 1
+ unless dialogue then
+ report('reestablishing connection')
+ dialogue = start_dialogue(address, port, processtimeout)
+ end
+ if dialogue then
+ begin
+ File.open(resultfile,'wb') do |rf|
+ begin
+ body = "id=#{id}"
+ headers = Hash.new
+ headers['content-type'] = "application/x-www-form-urlencoded"
+ headers['content-length'] = body.length.to_s
+ total, firstline = 0, ''
+ dialogue.post("/exastatus",body,headers) do |str|
+ if ! firstline || firstline.empty? then
+ firstline = str
+ end
+ total += 1
+ rf.write(str)
+ end
+ rescue
+ report("forced close #{traceback}")
+ dialogue = nil
+ again = true
+ end
+ end
+ begin
+ File.delete(resultfile) if File.zero?(resultfile)
+ rescue
+ end
+ rescue
+ report("error in opening file #{traceback}")
+ status(replyfile,'cannot open file')
+ end
+ else
+ report("unable to make a connection")
+ status(replyfile,'unable to make a connection') # exit
+ end
+ else
+ break
+ end
+ end
+ case firstline
+ when /<\?xml\s*version=.*?\?>\s*<exa:reply/moi then
+ begin
+ File.delete(replyfile) if FileTest.file?(replyfile)
+ resultfile = replyfile if File.rename(resultfile,replyfile)
+ rescue
+ end
+ report("reply saved in '#{resultfile}'")
+ when /\%PDF\-/io then
+ report("done, file #{resultfile}, type pdf, #{total} chunks, #{File.size? rescue 0} bytes")
+ if resultfile =~ /\.pdf$/i then
+ report("file identified as 'pdf'")
+ elsif resultfile =~ /\..*$/o
+ report("result file suffix should be 'pdf'")
+ else
+ newresultfile = resultfile + '.pdf'
+ newresultfile.sub!(/\.pdf\.pdf/io, '.pdf')
+ pdf('close',newresultfile,autopdf)
+ begin
+ File.delete(newresultfile) if FileTest.file?(newresultfile)
+ resultfile = newresultfile if File.rename(resultfile,newresultfile)
+ rescue
+ report("adding 'pdf' suffix to result name failed")
+ else
+ report("'pdf' suffix added to result name")
+ end
+ end
+ report("result saved in '#{resultfile}'")
+ pdf('open',resultfile,autopdf)
+ status(replyfile,'ok') do
+ "<exa:filename>#{resultfile}</exa:filename>"
+ end
+ when /html/io then
+ report("done, file #{resultfile}, type html, #{total} chunks, #{File.size? rescue 0} bytes")
+ if resultfile =~ /\.(htm|html)$/i then
+ report("file identified as 'html'")
+ elsif resultfile =~ /\..*$/o
+ report("result file suffix should be 'htm'")
+ else
+ newresultfile = resultfile + '.htm'
+ begin
+ File.delete(newresultfile) if FileTest.file?(newresultfile)
+ resultfile = newresultfile if File.rename(resultfile,newresultfile)
+ rescue
+ report("adding 'htm' suffix to result name failed")
+ else
+ report("'htm' suffix added to result name")
+ end
+ end
+ report("result saved in '#{resultfile}'")
+ status(replyfile,'ok') do
+ "<exa:filename>#{resultfile}</exa:filename>"
+ end
+ else
+ report("no result file, first line #{firstline}")
+ status(replyfile,'no result file')
+ end
+ end
+ rescue TimeoutError
+ report("aborted due to time out")
+ status(replyfile,'time out')
+ rescue
+ report("aborted due to some problem #{traceback}")
+ status(replyfile,"no answer #{traceback}")
+ end
+ end
+
+ begin
+ report("run time: #{Time.now-start_time} seconds")
+ rescue
+ end
+
+ end
+
+ def start_dialogue(address, port, processtimeout)
+ timeout(@@connecttimeout) do
+ report("trying to connect to #{address}:#{port}")
+ begin
+ begin
+ if dialogue = Net::HTTP.new(address, port) then
+ # dialogue.set_debug_output $stderr
+ dialogue.read_timeout = processtimeout # set this before start
+ if dialogue.start then
+ report("connected to #{address}:#{port}, timeout: #{processtimeout}")
+ else
+ retry
+ end
+ else
+ retry
+ end
+ rescue
+ sleep(2)
+ retry
+ else
+ return dialogue
+ end
+ rescue TimeoutError
+ return nil
+ rescue
+ return nil
+ end
+ end
+ end
+
+end
+
+logger = Logger.new(banner.shift)
+commandline = CommandLine.new
+
+commandline.registerflag('autopdf')
+
+commandline.registervalue('path' , '')
+
+commandline.registervalue('request' , 'request.exa')
+commandline.registervalue('reply' , 'reply.exa')
+commandline.registervalue('result' , 'result')
+
+commandline.registervalue('template' , '')
+commandline.registervalue('file' , '')
+commandline.registervalue('action' , '')
+commandline.registervalue('timeout' , '')
+
+commandline.registervalue('domain' , 'default')
+commandline.registervalue('project' , 'default')
+commandline.registervalue('username' , 'guest')
+commandline.registervalue('password' , 'anonymous')
+commandline.registervalue('exaurl' , 'exarequest')
+commandline.registervalue('threshold' , '0')
+commandline.registervalue('session' , '')
+
+commandline.registervalue('address' , 'localhost')
+commandline.registervalue('port' , '80')
+
+commandline.registeraction('direct' , '[--path --request --reply --result --autopdf]')
+commandline.registeraction('construct', '[--path --request --reply --result --autopdf] --file --action')
+commandline.registeraction('extend' , '[--path --request --reply --result --autopdf] --file --action --template')
+
+commandline.registeraction('direct')
+commandline.registeraction('construct')
+commandline.registeraction('extend')
+
+commandline.registerflag('verbose')
+commandline.registeraction('help')
+commandline.registeraction('version')
+
+commandline.expand
+
+Commands.new(commandline,logger,banner).send(commandline.action || 'main')
diff --git a/scripts/context/ruby/wwwserver.rb b/scripts/context/ruby/wwwserver.rb
new file mode 100644
index 000000000..aa6352183
--- /dev/null
+++ b/scripts/context/ruby/wwwserver.rb
@@ -0,0 +1,292 @@
+#!/usr/env ruby
+
+banner = ['WWWServer', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
+
+$: << File.dirname(File.expand_path($0))
+
+require 'base/switch'
+require 'base/logger'
+
+require 'monitor'
+
+# class WWW < Monitor
+# end
+# class Server < Monitor
+# end
+
+require 'www/lib'
+require 'www/dir'
+require 'www/login'
+require 'www/exa'
+
+require 'tempfile'
+require 'ftools'
+require 'webrick'
+
+class Server
+
+ attr_accessor :document_root, :work_path, :logs_path, :port_number, :exa_url, :verbose, :trace, :direct
+
+ def initialize(logger)
+ @httpd = nil
+ @document_root = ''
+ @work_path = ''
+ @logs_path = ''
+ @port_number = 8061
+ @exa_url = 'http://localhost:8061'
+ @logger = logger
+ @n_of_clients = 500
+ @request_timeout = 5*60
+ @verbose = false
+ @trace = false
+ @direct = false
+ end
+
+ def report(str)
+ @logger.report(str) if @logger
+ end
+
+ def setup
+ if @document_root.empty? then
+ rootpath = File.expand_path($0)
+ @document_root = File.expand_path(File.join(File.dirname(rootpath),'..','documents'))
+ unless FileTest.directory?(@document_root) then # todo: optional
+ loop do
+ prevpath = rootpath.dup
+ rootpath = File.dirname(rootpath)
+ if prevpath == rootpath then
+ break
+ else
+ checkpath = File.join(rootpath,'documents')
+ # report("locating: #{checkpath}")
+ if FileTest.directory?(checkpath) then
+ @document_root = checkpath
+ break
+ else
+ checkpath = File.join(rootpath,'docroot/documents')
+ # report("locating: #{checkpath}")
+ if FileTest.directory?(checkpath) then
+ @document_root = checkpath
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ @document_root = File.join(Dir.pwd, 'documents') unless FileTest.directory?(@document_root)
+ unless FileTest.directory?(@document_root) then
+ report("invalid document root: #{@document_root}")
+ exit
+ else
+ report("using document root: #{@document_root}")
+ end
+ #
+ @work_path = File.expand_path(File.join(@document_root,'..','work')) if @work_path.empty?
+ # begin File.makedirs(@work_path) ; rescue ; end # no, let's auto-temp
+ if ! FileTest.directory?(@work_path) || ! FileTest.writable?(@work_path) then
+ @work_path = File.expand_path(File.join(Dir.tmpdir,'exaserver','work'))
+ begin File.makedirs(@logs_path) ; rescue ; end
+ end
+ report("using work path: #{@work_path}")
+ #
+ @logs_path = File.expand_path(File.join(@document_root,'..','logs')) if @logs_path.empty?
+ # begin File.makedirs(@logs_path) ; rescue ; end # no, let's auto-temp
+ if ! FileTest.directory?(@logs_path) || ! FileTest.writable?(@logs_path) then
+ @logs_path = File.expand_path(File.join(Dir.tmpdir,'exaserver','logs'))
+ begin File.makedirs(@logs_path) ; rescue ; end
+ end
+ report("using log path: #{@logs_path}")
+ #
+ if @logs_path.empty? then
+ @logfile = $stderr
+ @accfile = $stderr
+ else
+ @logfile = File.join(@logs_path,'exa-info.log')
+ @accfile = File.join(@logs_path,'exa-access.log')
+ begin File.delete(@logfile) ; rescue ; end
+ begin File.delete(@accfile) ; rescue ; end
+ end
+ #
+ begin
+ @httpd = WEBrick::HTTPServer.new(
+ :DocumentRoot => @document_root,
+ :DirectoryIndex => ['index.html','index.htm','showcase.pdf'],
+ :Port => @port_number.to_i,
+ :Logger => WEBrick::Log.new(@logfile, WEBrick::Log::INFO), # DEBUG
+ :RequestTimeout => @request_timeout,
+ :MaxClients => @n_of_clients,
+ :AccessLog => [
+ [ @accfile, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
+ [ @accfile, WEBrick::AccessLog::REFERER_LOG_FORMAT ],
+ [ @accfile, WEBrick::AccessLog::AGENT_LOG_FORMAT ],
+ # :CGIPathEnv => ENV["PATH"] # PATH environment variable for CGI.
+ ]
+ )
+ rescue
+ report("starting server at port: #{@port_number} failed")
+ exit
+ else
+ report("running server at port: #{@port_number}")
+ end
+
+ begin
+ #
+ @httpd.mount_proc("/dir") do |request,reply|
+ report("accepting /dir") if @verbose
+ web_session(request,reply).handle_dir
+ end
+ @httpd.mount_proc("/login") do |request,reply|
+ report("accepting /login") if @verbose
+ web_session(request,reply).handle_login
+ end
+ @httpd.mount("/cache", WEBrick::HTTPServlet::FileHandler, File.join(@work_path,'cache'))
+ # @httpd.mount_proc("/cache") do |request,reply|
+ # WEBrick::HTTPServlet::FileHandler(@httpd,@work_path) # not ok
+ # end
+ @httpd.mount_proc("/exalogin") do |request,reply|
+ report("accepting /exalogin") if @verbose
+ web_session(request,reply).handle_exalogin
+ end
+ @httpd.mount_proc("/exadefault") do |request,reply|
+ report("accepting /exadefault") if @verbose
+ web_session(request,reply).handle_exadefault
+ end
+ @httpd.mount_proc("/exainterface") do |request,reply|
+ report("accepting /exainterface") if @verbose
+ web_session(request,reply).handle_exainterface
+ end
+ @httpd.mount_proc("/exarequest") do |request,reply|
+ report("accepting /exarequest") if @verbose
+ web_session(request,reply).handle_exarequest
+ end
+ @httpd.mount_proc("/exacommand") do |request,reply|
+ report("accepting /exacommand") if @verbose
+ web_session(request,reply).handle_exacommand
+ end
+ @httpd.mount_proc("/exastatus") do |request,reply|
+ report("accepting /exastatus") if @verbose
+ web_session(request,reply).handle_exastatus
+ end
+ @httpd.mount_proc("/exaadmin") do |request,reply|
+ report("accepting /exaadmin") if @verbose
+ web_session(request,reply).handle_exaadmin
+ end
+ #
+ rescue
+ report("problem in starting server: #{$!}")
+ end
+ [:INT, :TERM, :EXIT].each do |signal|
+ trap(signal) do
+ @httpd.shutdown
+ end
+ end
+ end
+
+ def start
+ unless @httpd then
+ setup
+ @httpd.start
+ end
+ end
+
+ def stop
+ @httpd.shutdown if @httpd
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ private
+
+ def web_session(request,reply)
+ www = WWW.new(@httpd,request,reply)
+ www.set('path:work', @work_path)
+ www.set('path:logs', @logs_path)
+ www.set('path:root', File.dirname(@document_root))
+ www.set('process:exaurl', @exa_url)
+ www.set('trace:errors','yes') if @trace
+ www.set('process:background', 'no') if @direct
+ return www
+ end
+
+end
+
+class Commands
+
+ include CommandBase
+
+ def start
+ if server = setup then server.start end
+ end
+
+ def stop
+ if server = setup then server.stop end
+ end
+
+ def restart
+ if server = setup then server.restart end
+ end
+
+ private
+
+ def setup
+ server = Server.new(logger)
+ server.document_root = @commandline.option('root')
+ server.verbose = @commandline.option('verbose')
+ if @commandline.option('forcetemp') then
+ server.work_path = Dir.tmpdir + '/exa/work'
+ server.logs_path = Dir.tmpdir + '/exa/logs'
+ [server.work_path,server.logs_path].each do |d|
+ begin
+ File.makedirs(d) unless FileTest.directory?(d)
+ rescue
+ report("unable to create #{d}")
+ exit
+ end
+ unless FileTest.writable?(d) then
+ report("unable to access #{d}")
+ exit
+ end
+ end
+ else
+ server.work_path = @commandline.option('work')
+ server.logs_path = @commandline.option('logs')
+ end
+ server.port_number = @commandline.option('port')
+ server.exa_url = @commandline.option('url')
+ server.trace = @commandline.option('trace')
+ server.direct = @commandline.option('direct')
+ return server
+ end
+
+end
+
+logger = Logger.new(banner.shift)
+commandline = CommandLine.new
+
+commandline.registervalue('root' , '')
+commandline.registervalue('work' , '')
+commandline.registervalue('logs' , '')
+commandline.registervalue('address', 'localhost')
+commandline.registervalue('port' , '8061')
+commandline.registervalue('url' , 'http://localhost:8061')
+
+commandline.registeraction('start' , 'start the server [--root --forcetemp --work --logs --address --port --url]')
+commandline.registeraction('stop' , 'stop the server')
+commandline.registeraction('restart', 'restart the server')
+
+commandline.registerflag('forcetemp')
+commandline.registerflag('direct')
+commandline.registerflag('verbose')
+commandline.registerflag('trace')
+
+commandline.registeraction('help')
+commandline.registeraction('version')
+
+commandline.expand
+
+Commands.new(commandline,logger,banner).send(commandline.action || 'start')
+
diff --git a/scripts/context/ruby/wwwwatch.rb b/scripts/context/ruby/wwwwatch.rb
new file mode 100644
index 000000000..1f61ef479
--- /dev/null
+++ b/scripts/context/ruby/wwwwatch.rb
@@ -0,0 +1,464 @@
+#!/usr/bin/env ruby
+
+banner = ['WWWWatch', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
+
+$: << File.dirname(File.expand_path($0))
+
+require 'base/switch'
+require 'base/logger'
+
+require 'www/common'
+
+require 'monitor'
+require 'fileutils'
+require 'ftools'
+require 'tempfile'
+require 'timeout'
+require 'thread'
+
+class Watch < Monitor
+
+ include Common
+
+ @@session_prefix = ''
+ @@check_factor = 4
+ @@process_timeout = 1*60*60
+ @@fast_wait_loop = false
+
+ @@session_line = /^\s*(?![\#\%])(.*?)\s*\=\s*(.*?)\s*$/o
+ @@session_begin = 'begin exa session'
+ @@session_end = 'end exa session'
+
+ attr_accessor :root_path, :work_path, :delay, :max_threads, :max_age, :verbose
+
+ def initialize(logger) # we need to register all @vars here becase of the monitor
+ @threads = Hash.new
+ @files = Array.new
+ @stats = Hash.new
+ @skips = Hash.new
+ @root_path = ''
+ @work_path = Dir.tmpdir
+ @cache_path = @work_path
+ @last_action = Time.now
+ @delay = 1
+ @max_threads = 5
+ @max_age = @@process_timeout
+ @logger = logger
+ @verbose = false
+ [:INT, :TERM, :EXIT].each do |signal|
+ trap(signal) do
+ kill
+ exit
+ end
+ end
+ at_exit do
+ kill
+ end
+ end
+
+ def trace
+ if @verbose && @logger then
+ @logger.report("exception: #{$!})")
+ $@.each do |t|
+ @logger.report(">> #{t}")
+ end
+ end
+ end
+
+ def report(str)
+ @logger.report(str) if @logger
+ end
+
+ def setup
+ @threads = Hash.new
+ @files = Array.new
+ @stats = Hash.new
+ @skips = Hash.new
+ @root_path = File.expand_path(File.join(File.dirname($0),'..')) if @root_path.empty?
+ @work_path = File.expand_path(File.join(@root_path,'work','watch')) if @work_path.empty?
+ @cache_path = File.expand_path(File.join(@root_path,'work','cache')) if @work_path.empty?
+ begin File.makedirs(@work_path) ; rescue ; end
+ begin File.makedirs(@cache_path) ; rescue ; end
+ unless File.writable?(@work_path) then
+ @work_path = File.expand_path(File.join(Dir.tmpdir,'work','watch'))
+ begin File.makedirs(@work_path) ; rescue ; end
+ end
+ unless File.writable?(@cache_path) then
+ @cache_path = File.expand_path(File.join(Dir.tmpdir,'work','cache'))
+ begin File.makedirs(@cache_path) ; rescue ; end
+ end
+ unless File.writable?(@work_path) then
+ puts "no valid work path: #{@work_path}" ; exit
+ end
+ unless File.writable?(@cache_path) then
+ puts "no valid work path: #{@work_path}" ; # no reason to exit
+ end
+ @last_action = Time.now
+ report("watching path #{@work_path}") if @verbose
+ end
+
+ def lock(lck)
+ begin
+ report("watchdog: locking #{lck}") if @verbose
+ File.open(lck,'w') do |f|
+ f << Time.now
+ end
+ rescue
+ trace
+ end
+ end
+
+ def unlock(lck)
+ begin
+ report("watchdog: unlocking #{lck}") if @verbose
+ File.delete(lck)
+ rescue
+ trace
+ end
+ end
+
+ def kill
+ @threads.each do |t|
+ t.kill rescue false
+ end
+ end
+
+ def restart
+ @files = Array.new
+ @skips = Hash.new
+ @stats = Hash.new
+ kill # threads
+ end
+
+ def collect
+ begin
+ @files = Array.new
+ Dir.glob("#{@work_path}/#{@@session_prefix}*.ses").each do |sessionfile|
+ sessionfile = File.expand_path(sessionfile)
+ begin
+ if @threads.key?(sessionfile) then
+ # leave alone
+ elsif (Time.now - File.mtime(sessionfile)) > @max_age.to_i then
+ # delete
+ FileUtils::rm_r(sessionfile) rescue false
+ FileUtils::rm_r(sessionfile.sub(/ses$/,'dir')) rescue false
+ FileUtils::rm_r(sessionfile.sub(/ses$/,'lck')) rescue false
+ begin
+ FileUtils::rm_r(File.join(@cache_path, File.basename(sessionfile.sub(/ses$/,'dir'))))
+ rescue
+ report("watchdog: problems in cache cleanup #{$!}") # if @verbose
+ end
+ @stats.delete(sessionfile) rescue false
+ @skips.delete(sessionfile) rescue false
+ report("watchdog: removing session #{sessionfile}") if @verbose
+ elsif ! @skips.key?(sessionfile) then
+ @files << sessionfile
+ report("watchdog: checking session #{sessionfile}") if @verbose
+ end
+ rescue
+ # maybe purged in the meantime
+ end
+ end
+ rescue
+ if File.directory?(@work_path) then
+ @files = Array.new
+ else
+ # maybe dir is deleted (manual cleanup)
+ restart
+ end
+ end
+ begin
+ Dir.glob("#{@cache_path}/*.dir").each do |dirname|
+ begin
+ if (Time.now - File.mtime(dirname)) > @max_age.to_i then
+ begin
+ FileUtils::rm_r(dirname)
+ rescue
+ report("watchdog: problems in cache cleanup #{$!}") # if @verbose
+ end
+ end
+ rescue
+ # maybe purged in the meantime
+ end
+ end
+ rescue
+ end
+ end
+
+ def purge
+ begin
+ Dir.glob("#{@work_path}/#{@@session_prefix}*").each do |sessionfile|
+ sessionfile = File.expand_path(sessionfile)
+ begin
+ if (Time.now - File.mtime(sessionfile)) > @max_age.to_i then
+ begin
+ if FileTest.directory?(sessionfile) then
+ FileUtils::rm_r(sessionfile)
+ else
+ File.delete(sessionfile)
+ end
+ rescue
+ end
+ begin
+ @stats.delete(sessionfile)
+ @skips.delete(sessionfile)
+ rescue
+ end
+ report("watchdog: purging session #{sessionfile}") if @verbose
+ end
+ rescue
+ # maybe purged in the meantime
+ end
+ end
+ rescue
+ end
+ end
+
+ def loaded_session_data(filename)
+ begin
+ if data = IO.readlines(filename) then
+ return data if (data.first =~ /^[\#\%]\s*#{@@session_begin}/o) && (data.last =~ /^[\#\%]\s*#{@@session_end}/o)
+ end
+ rescue
+ trace
+ end
+ return nil
+ end
+
+ def load(sessionfile)
+ # we assume that we get an exception when the file is locked
+ begin
+ if data = loaded_session_data(sessionfile) then
+ report("watchdog: loading session #{sessionfile}") if @verbose
+ vars = Hash.new
+ data.each do |line|
+ begin
+ if line.chomp =~ /^(.*?)\s*\=\s*(.*?)\s*$/o then
+ key, value = $1, $2
+ vars[key] = value
+ end
+ rescue
+ end
+ end
+ return vars
+ else
+ return nil
+ end
+ rescue
+ trace
+ return nil
+ end
+ end
+
+ def save(sessionfile, vars)
+ begin
+ report("watchdog: saving session #{sessionfile}") if @verbose
+ if @stats.key?(sessionfile) then
+ @stats[sessionfile] = File.mtime(sessionfile)
+ elsif @stats[sessionfile] == File.mtime(sessionfile) then
+ else
+ # construct data first
+ str = "\# #{@@session_begin}\n"
+ for k,v in vars do
+ str << "#{k}=#{v}\n"
+ end
+ str << "\# #{@@session_end}\n"
+ # save as fast as possible
+ File.open(sessionfile,'w') do |f|
+ f.puts(str)
+ end
+ end
+ rescue
+ report("watchdog: unable to save session #{sessionfile}") if @verbose
+ trace
+ return false
+ else
+ return true
+ end
+ end
+
+ def launch
+ begin
+ @files.each do |sessionfile|
+ if @threads.length < @max_threads then
+ begin
+ if ! @skips.key?(sessionfile) && (vars = load(sessionfile)) then
+ if (id = vars['id']) && vars['status'] then
+ if vars['status'] == 'running: background' then
+ @last_action = Time.now
+ @threads[sessionfile] = Thread.new(vars, sessionfile) do |vars, sessionfile|
+ begin
+ report("watchdog: starting thread #{sessionfile}") if @verbose
+ dir = File.expand_path(sessionfile.sub(/ses$/,'dir'))
+ lck = File.expand_path(sessionfile.sub(/ses$/,'lck'))
+ start_of_run = Time.now
+ start_of_job = start_of_run.dup
+ max_runtime = @max_age
+ begin
+ start_of_job = vars['starttime'].to_i || start_of_run
+ start_of_job = start_of_run if start_of_job == 0
+ rescue
+ start_of_job = Time.now
+ end
+ begin
+ max_runtime = vars['maxtime'].to_i || @max_age
+ max_runtime = @max_age if max_runtime == 0
+ max_runtime = max_runtime - (Time.now.to_i - start_of_job.to_i)
+ rescue
+ max_runtime = @max_age
+ end
+ lock(lck)
+ if max_runtime > 0 then
+ command = vars['command'] || ''
+ if ! command.empty? then
+ vars['status'] = 'running: busy'
+ vars['timeout'] = max_runtime.to_s
+ save(sessionfile,vars)
+ timeout(max_runtime) do
+ begin
+ command = command_string(dir,command,'process.log')
+ report("watchdog: #{command}") if @verbose
+ system(command)
+ rescue TimeoutError
+ vars['status'] = 'running: timeout'
+ rescue
+ trace
+ vars['status'] = 'running: aborted'
+ else
+ vars['status'] = 'running: finished'
+ vars['runtime'] = sprintf("%.02f",(Time.now - start_of_run))
+ vars['endtime'] = Time.now.to_i.to_s
+ end
+ end
+ else
+ vars['status'] = 'running: aborted' # no command
+ end
+ else
+ vars['status'] = 'running: aborted' # not enough time
+ end
+ save(sessionfile,vars)
+ unlock(lck)
+ report("watchdog: ending thread #{sessionfile}") if @verbose
+ @threads.delete(sessionfile)
+ rescue
+ trace
+ end
+ end
+ else
+ report("watchdog: skipping - id (#{vars['id']}) / status (#{vars['status']})") if @verbose
+ end
+ @skips[sessionfile] = true
+ else
+ # not yet ok
+ end
+ else
+ # maybe a lock
+ end
+ rescue
+ trace
+ end
+ else
+ break
+ end
+ end
+ rescue
+ trace
+ end
+ end
+
+ def wait
+ begin
+ # report(Time.now.to_s) if @verbose
+ loop do
+ if @threads.length == @max_threads then
+ if @delay > @max_threads then
+ sleep(@delay)
+ else
+ sleep(@max_threads)
+ end
+ break if @@fast_wait_loop
+ else
+ sleep(@delay)
+ break
+ end
+ end
+ rescue
+ trace
+ end
+ end
+
+ def check
+ begin
+ time = Time.now
+ if (time - @last_action) > @@check_factor*@max_age then
+ report("watchdog: cleanup") if @verbose
+ @stats = Hash.new
+ @last_action = time
+ kill
+ end
+ rescue
+ trace
+ end
+ end
+
+ def cycle
+ loop do
+ begin
+ collect
+ launch
+ wait
+ check
+ rescue
+ trace
+ report("watchdog: some problem, restarting loop")
+ end
+ end
+ end
+
+end
+
+class Commands
+
+ include CommandBase
+
+ def watch
+ if watch = setup then watch.cycle end
+ end
+ def main
+ watch
+ end
+
+ private
+
+ def setup
+ watch = Watch.new(logger)
+ watch.root_path = @commandline.option('root')
+ watch.work_path = @commandline.option('work')
+ watch.verbose = @commandline.option('verbose')
+ begin
+ watch.max_threads = @commandline.option('threads').to_i
+ rescue
+ watch.max_threads = 5
+ end
+ watch.setup
+ return watch
+ end
+
+end
+
+logger = Logger.new(banner.shift)
+commandline = CommandLine.new
+
+commandline.registervalue('root' , '')
+commandline.registervalue('work' , '')
+commandline.registervalue('threads' , '5')
+
+commandline.registeraction('watch', '[--work=path] [--root=path]')
+
+commandline.registerflag('verbose')
+commandline.registeraction('help')
+commandline.registeraction('version')
+
+commandline.expand
+
+Commands.new(commandline,logger,banner).send(commandline.action || 'main')
diff --git a/scripts/context/stubs/mswin/texmfstart.bat b/scripts/context/stubs/mswin/texmfstart.bat
deleted file mode 100755
index 287f9c4fa..000000000
--- a/scripts/context/stubs/mswin/texmfstart.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-ruby c:\data\develop\context/ruby/texmfstart.rb %*
diff --git a/scripts/context/stubs/unix/ctxtools b/scripts/context/stubs/unix/ctxtools
new file mode 100755
index 000000000..5a6a1feb5
--- /dev/null
+++ b/scripts/context/stubs/unix/ctxtools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart ctxtools.rb $@
diff --git a/scripts/context/stubs/unix/ctxtools.bat b/scripts/context/stubs/unix/ctxtools.bat
deleted file mode 100755
index f1f5e019e..000000000
--- a/scripts/context/stubs/unix/ctxtools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart ctxtools.rb %*
diff --git a/scripts/context/stubs/unix/exatools b/scripts/context/stubs/unix/exatools
new file mode 100755
index 000000000..cededbb57
--- /dev/null
+++ b/scripts/context/stubs/unix/exatools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart exatools.rb $@
diff --git a/scripts/context/stubs/unix/exatools.bat b/scripts/context/stubs/unix/exatools.bat
deleted file mode 100755
index 57f798e82..000000000
--- a/scripts/context/stubs/unix/exatools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart exatools.rb %*
diff --git a/scripts/context/stubs/unix/makempy b/scripts/context/stubs/unix/makempy
new file mode 100755
index 000000000..c30a261f0
--- /dev/null
+++ b/scripts/context/stubs/unix/makempy
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart makempy.pl $@
diff --git a/scripts/context/stubs/unix/makempy.bat b/scripts/context/stubs/unix/makempy.bat
deleted file mode 100755
index e339058c6..000000000
--- a/scripts/context/stubs/unix/makempy.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart makempy.pl %*
diff --git a/scripts/context/stubs/unix/mpstools b/scripts/context/stubs/unix/mpstools
new file mode 100755
index 000000000..68fd485bc
--- /dev/null
+++ b/scripts/context/stubs/unix/mpstools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart mpstools.rb $@
diff --git a/scripts/context/stubs/unix/mpstools.bat b/scripts/context/stubs/unix/mpstools.bat
deleted file mode 100755
index df1732e17..000000000
--- a/scripts/context/stubs/unix/mpstools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart mpstools.rb %*
diff --git a/scripts/context/stubs/unix/mptopdf b/scripts/context/stubs/unix/mptopdf
new file mode 100755
index 000000000..a29448782
--- /dev/null
+++ b/scripts/context/stubs/unix/mptopdf
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart mptopdf.pl $@
diff --git a/scripts/context/stubs/unix/mptopdf.bat b/scripts/context/stubs/unix/mptopdf.bat
deleted file mode 100755
index 242854337..000000000
--- a/scripts/context/stubs/unix/mptopdf.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart mptopdf.pl %*
diff --git a/scripts/context/stubs/unix/pdftools b/scripts/context/stubs/unix/pdftools
new file mode 100755
index 000000000..fc6b9e864
--- /dev/null
+++ b/scripts/context/stubs/unix/pdftools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart pdftools.rb $@
diff --git a/scripts/context/stubs/unix/pdftools.bat b/scripts/context/stubs/unix/pdftools.bat
deleted file mode 100755
index adc48eacf..000000000
--- a/scripts/context/stubs/unix/pdftools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart pdftools.rb %*
diff --git a/scripts/context/stubs/unix/pstopdf b/scripts/context/stubs/unix/pstopdf
new file mode 100755
index 000000000..e1f0375e5
--- /dev/null
+++ b/scripts/context/stubs/unix/pstopdf
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart pstopdf.rb $@
diff --git a/scripts/context/stubs/unix/pstopdf.bat b/scripts/context/stubs/unix/pstopdf.bat
deleted file mode 100755
index 248e34caf..000000000
--- a/scripts/context/stubs/unix/pstopdf.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart pstopdf.rb %*
diff --git a/scripts/context/stubs/unix/runtools b/scripts/context/stubs/unix/runtools
new file mode 100755
index 000000000..3e4e2c505
--- /dev/null
+++ b/scripts/context/stubs/unix/runtools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart runtools.rb $@
diff --git a/scripts/context/stubs/unix/runtools.bat b/scripts/context/stubs/unix/runtools.bat
deleted file mode 100755
index 68a7b4f97..000000000
--- a/scripts/context/stubs/unix/runtools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart runtools.rb %*
diff --git a/scripts/context/stubs/unix/texexec b/scripts/context/stubs/unix/texexec
new file mode 100755
index 000000000..a8e297307
--- /dev/null
+++ b/scripts/context/stubs/unix/texexec
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart texexec.rb $@
diff --git a/scripts/context/stubs/unix/texexec.bat b/scripts/context/stubs/unix/texexec.bat
deleted file mode 100755
index 02b297b9c..000000000
--- a/scripts/context/stubs/unix/texexec.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart texexec.rb %*
diff --git a/scripts/context/stubs/unix/texfont b/scripts/context/stubs/unix/texfont
new file mode 100755
index 000000000..ec1bd57d5
--- /dev/null
+++ b/scripts/context/stubs/unix/texfont
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart texfont.pl $@
diff --git a/scripts/context/stubs/unix/texfont.bat b/scripts/context/stubs/unix/texfont.bat
deleted file mode 100755
index 3134bf14c..000000000
--- a/scripts/context/stubs/unix/texfont.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart texfont.pl %*
diff --git a/scripts/context/stubs/unix/texmfstart.bat b/scripts/context/stubs/unix/texmfstart.bat
deleted file mode 100755
index 287f9c4fa..000000000
--- a/scripts/context/stubs/unix/texmfstart.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-ruby c:\data\develop\context/ruby/texmfstart.rb %*
diff --git a/scripts/context/stubs/unix/textools b/scripts/context/stubs/unix/textools
new file mode 100755
index 000000000..5078ebce0
--- /dev/null
+++ b/scripts/context/stubs/unix/textools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart textools.rb $@
diff --git a/scripts/context/stubs/unix/textools.bat b/scripts/context/stubs/unix/textools.bat
deleted file mode 100755
index 727b4a36d..000000000
--- a/scripts/context/stubs/unix/textools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart textools.rb %*
diff --git a/scripts/context/stubs/unix/texutil b/scripts/context/stubs/unix/texutil
new file mode 100755
index 000000000..773176da5
--- /dev/null
+++ b/scripts/context/stubs/unix/texutil
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart texutil.rb $@
diff --git a/scripts/context/stubs/unix/texutil.bat b/scripts/context/stubs/unix/texutil.bat
deleted file mode 100755
index 1e63639bb..000000000
--- a/scripts/context/stubs/unix/texutil.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart texutil.rb %*
diff --git a/scripts/context/stubs/unix/tmftools b/scripts/context/stubs/unix/tmftools
new file mode 100755
index 000000000..9b52a5e53
--- /dev/null
+++ b/scripts/context/stubs/unix/tmftools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart tmftools.rb $@
diff --git a/scripts/context/stubs/unix/tmftools.bat b/scripts/context/stubs/unix/tmftools.bat
deleted file mode 100755
index c9c0c08bd..000000000
--- a/scripts/context/stubs/unix/tmftools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart tmftools.rb %*
diff --git a/scripts/context/stubs/unix/xmltools b/scripts/context/stubs/unix/xmltools
new file mode 100755
index 000000000..7e8c174ca
--- /dev/null
+++ b/scripts/context/stubs/unix/xmltools
@@ -0,0 +1,2 @@
+#!/bin/sh
+texmfstart xmltools.rb $@
diff --git a/scripts/context/stubs/unix/xmltools.bat b/scripts/context/stubs/unix/xmltools.bat
deleted file mode 100755
index 2de0e4457..000000000
--- a/scripts/context/stubs/unix/xmltools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart xmltools.rb %*