diff options
author | Karl Berry <karl@freefriends.org> | 2006-11-16 01:02:47 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-11-16 01:02:47 +0000 |
commit | 3c85b55e2f245fb7edac7e445f107e6d0bb423ae (patch) | |
tree | c62b7f80edafe0455310d08e09f4218127bf9e87 /Master/texmf-dist/scripts/context | |
parent | e5ecff27a26ef59e1dbe9702677687fd7107be3a (diff) |
context update
git-svn-id: svn://tug.org/texlive/trunk@2444 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/context')
90 files changed, 15214 insertions, 2676 deletions
diff --git a/Master/texmf-dist/scripts/context/lua/scite-ctx.lua b/Master/texmf-dist/scripts/context/lua/scite-ctx.lua new file mode 100644 index 00000000000..5836bcbfb71 --- /dev/null +++ b/Master/texmf-dist/scripts/context/lua/scite-ctx.lua @@ -0,0 +1,925 @@ +-- version : 1.0.0 - 07/2005 +-- author : Hans Hagen - PRAGMA ADE - www.pragma-ade.com +-- copyright : public domain or whatever suits +-- remark : part of the context distribution + +-- todo: name space for local functions + +-- loading: scite-ctx.properties + +-- # environment variable +-- # +-- # CTXSPELLPATH=t:/spell +-- # +-- # auto language detection +-- # +-- # % version =1.0 language=uk +-- # <?xml version='1.0' language='uk' ?> + +-- ext.lua.startup.script=$(SciteDefaultHome)/scite-ctx.lua +-- +-- # extension.$(file.patterns.context)=scite-ctx.lua +-- # extension.$(file.patterns.example)=scite-ctx.lua +-- +-- # ext.lua.reset=1 +-- # ext.lua.auto.reload=1 +-- # ext.lua.startup.script=t:/lua/scite-ctx.lua +-- +-- ctx.menulist.default=\ +-- wrap=wrap_text|\ +-- unwrap=unwrap_text|\ +-- sort=sort_text|\ +-- document=document_text|\ +-- quote=quote_text|\ +-- compound=compound_text|\ +-- check=check_text +-- +-- ctx.spellcheck.language=auto +-- ctx.spellcheck.wordsize=4 +-- ctx.spellcheck.wordpath=ENV(CTXSPELLPATH) +-- +-- ctx.spellcheck.wordfile.all=spell-uk.txt,spell-nl.txt +-- +-- ctx.spellcheck.wordfile.uk=spell-uk.txt +-- ctx.spellcheck.wordfile.nl=spell-nl.txt +-- ctx.spellcheck.wordsize.uk=4 +-- ctx.spellcheck.wordsize.nl=4 +-- +-- command.name.21.*=CTX Action List +-- command.subsystem.21.*=3 +-- command.21.*=show_menu $(ctx.menulist.default) +-- command.groupundo.21.*=yes +-- command.shortcut.21.*=Shift+F11 +-- +-- command.name.22.*=CTX Check Text +-- command.subsystem.22.*=3 +-- command.22.*=check_text +-- command.groupundo.22.*=yes +-- command.shortcut.22.*=Ctrl+L +-- +-- command.name.23.*=CTX Wrap Text +-- command.subsystem.23.*=3 +-- command.23.*=wrap_text +-- command.groupundo.23.*=yes +-- command.shortcut.23.*=Ctrl+M +-- +-- # command.21.*=check_text +-- # command.21.*=dofile e:\context\lua\scite-ctx.lua + +-- generic functions + +local crlf = "\n" + +function traceln(str) + trace(str .. crlf) + io.flush() +end + +table.len = table.getn +table.join = table.concat + +function table.found(tab, str) + local l, r, p + if string.len(str) == 0 then + return false + else + l, r = 1, table.len(tab) + while l <= r do + p = math.floor((l+r)/2) + if str < tab[p] then + r = p - 1 + elseif str > tab[p] then + l = p + 1 + else + return true + end + end + return false + end +end + +function string.grab(str, delimiter) + local list = {} + for snippet in string.gfind(str,delimiter) do + table.insert(list, snippet) + end + return list +end + +function string.join(list, delimiter) + local size, str = table.len(list), '' + if size > 0 then + str = list[1] + for i = 2, size, 1 do + str = str .. delimiter .. list[i] + end + end + return str +end + +function string.spacy(str) + if string.find(str,"^%s*$") then + return true + else + return false + end +end + +function string.alphacmp(a,b,i) -- slow but ok + if i and i > 0 then + return string.lower(string.gsub(string.sub(a,i),'0',' ')) < string.lower(string.gsub(string.sub(b,i),'0',' ')) + else + return string.lower(a) < string.lower(b) + end +end + +function table.alphasort(list,i) + table.sort(list, function(a,b) return string.alphacmp(a,b,i) end) +end + +function io.exists(filename) + local ok, result, message = pcall(io.open,filename) + if result then + io.close(result) + return true + else + return false + end +end + +function os.envvar(str) + if os.getenv(str) ~= '' then + return os.getenv(str) + elseif os.getenv(string.upper(str)) ~= '' then + return os.getenv(string.upper(str)) + elseif os.getenv(string.lower(str)) ~= '' then + return os.getenv(string.lower(str)) + else + return '' + end +end + +function string.expand(str) + return string.gsub(str, "ENV%((%w+)%)", os.envvar) +end + +function string.strip(str) + return string.gsub(string.gsub(str,"^%s+",''),"%s+$",'') +end + +function string.replace(original,pattern,replacement) + local str = string.gsub(original,pattern,replacement) +-- print(str) -- indirect, since else str + nofsubs + return str -- indirect, since else str + nofsubs +end + +-- support functions, maybe editor namespace + +-- function column_of_position(position) +-- local line = editor:LineFromPosition(position) +-- local oldposition = editor.CurrentPos +-- local column = 0 +-- editor:GotoPos(position) +-- while editor.CurrentPos ~= 0 and line == editor:LineFromPosition(editor.CurrentPos) do +-- editor:CharLeft() +-- column = column + 1 +-- end +-- editor:GotoPos(oldposition) +-- if line > 0 then +-- return column -1 +-- else +-- return column +-- end +-- end + +-- function line_of_position(position) +-- return editor:LineFromPosition(position) +-- end + +function extend_to_start() + local selectionstart = editor.SelectionStart + local selectionend = editor.SelectionEnd + local line = editor:LineFromPosition(selectionstart) + if line > 0 then + while line == editor:LineFromPosition(selectionstart-1) do + selectionstart = selectionstart - 1 + editor:SetSel(selectionstart,selectionend) + end + else + selectionstart = 0 + end + editor:SetSel(selectionstart,selectionend) + return selectionstart +end + +function extend_to_end() -- editor:LineEndExtend() does not work + local selectionstart = editor.SelectionStart + local selectionend = editor.SelectionEnd + local line = editor:LineFromPosition(selectionend) + while line == editor:LineFromPosition(selectionend+1) do + selectionend = selectionend + 1 + editor:SetSel(selectionstart,selectionend) + end + editor:SetSel(selectionstart,selectionend) + return selectionend +end + +function getfiletype() + local firstline = editor:GetLine(0) + if editor.Lexer == SCLEX_TEX then + return 'tex' + elseif editor.Lexer == SCLEX_XML then + return 'xml' + elseif string.find(firstline,"^%%") then + return 'tex' + elseif string.find(firstline,"^<%?xml") then + return 'xml' + else + return 'unknown' + end +end + +-- inspired by LuaExt's scite_Files + +function get_dir_list(mask) + local f + if props['PLAT_GTK'] and props['PLAT_GTK'] ~= "" then + f = io.popen('ls -1 ' .. mask) + else + mask = string.gsub(mask, '/','\\') + local tmpfile = 'scite-ctx.tmp' + local cmd = 'dir /b "' .. mask .. '" > ' .. tmpfile + os.execute(cmd) + f = io.open(tmpfile) + end + local files = {} + if not f then -- path check added + return files + end + for line in f:lines() do + table.insert(files, line) + end + f:close() + return files +end + +-- banner + +print("loading scite-ctx.lua definition file") +print("") +print("- see scite-ctx.properties for configuring info") +print("") +print("- ctx.spellcheck.wordpath set to " .. props['ctx.spellcheck.wordpath']) +if string.find(string.lower(props['ctx.spellcheck.wordpath']), "ctxspellpath") then + if os.getenv('ctxspellpath') then + print("- ctxspellpath set to " .. os.getenv('CTXSPELLPATH')) + else + print("- 'ctxspellpath is not set") + end + print("- ctx.spellcheck.wordpath expands to " .. string.expand(props['ctx.spellcheck.wordpath'])) +end +print("") +print("- ctx.wraptext.length is set to " .. props['ctx.wraptext.length']) +if props['ctx.helpinfo'] ~= '' then + print("- key bindings:") + print("") + print(string.replace(string.strip(props['ctx.helpinfo']),"%s*\|%s*","\n")) -- indirect, since else str + nofsubs +end +print("") +print("- recognized first lines:") +print("") +print("xml <?xml version='1.0' language='nl'") +print("tex % language=nl") + + +-- text functions + +-- written while listening to Talk Talk + +local magicstring = string.rep("<ctx-crlf/>", 2) + +function wrap_text() + + -- We always go to the end of a line, so in fact some of + -- the variables set next are not needed. + + local length = props["ctx.wraptext.length"] + + if length == '' then length = 80 else length = tonumber(length) end + + local startposition = editor.SelectionStart + local endposition = editor.SelectionEnd + + if startposition == endposition then return end + + editor:LineEndExtend() + + startposition = editor.SelectionStart + endposition = editor.SelectionEnd + + -- local startline = line_of_position(startposition) + -- local endline = line_of_position(endposition) + -- local startcolumn = column_of_position(startposition) + -- local endcolumn = column_of_position(endposition) + -- + -- editor:SetSel(startposition,endposition) + + local startline = props['SelectionStartLine'] + local endline = props['SelectionEndLine'] + local startcolumn = props['SelectionStartColumn'] - 1 + local endcolumn = props['SelectionEndColumn'] - 1 + + local indentation = string.rep(' ', startcolumn) +-- local selection = string.gsub(editor:GetSelText(),"[\n\r][\n\r]+", ' ' .. magicstring .. ' ') + local selection = string.gsub(editor:GetSelText(),"[\n\r][\n\r]", "\n") + local selection = string.gsub(selection,"\n\n+", ' ' .. magicstring .. ' ') + local replacement = '' + local templine = '' + + selection = string.gsub(selection,"^%s", '') + + for snippet in string.gfind(selection, "%S+") do + if snippet == magicstring then + replacement = replacement .. templine .. "\n\n" + templine = '' + elseif string.len(templine) + string.len(snippet) > length then + replacement = replacement .. templine .. "\n" + templine = indentation .. snippet + elseif string.len(templine) == 0 then + templine = indentation .. snippet + else + templine = templine .. ' ' .. snippet + end + end + + replacement = replacement .. templine + replacement = string.gsub(replacement, "^%s+", '') + + if endcolumn == 0 then + replacement = replacement .. "\n" + end + + editor:ReplaceSel(replacement) + +end + +function unwrap_text() + + local startposition = editor.SelectionStart + local endposition = editor.SelectionEnd + + if startposition == endposition then return end + + editor:HomeExtend() + editor:LineEndExtend() + + startposition = editor.SelectionStart + endposition = editor.SelectionEnd + + local magicstring = string.rep("<multiplelines/>", 2) + local selection = string.gsub(editor:GetSelText(),"[\n\r][\n\r]+", ' ' .. magicstring .. ' ') + local replacement = '' + + for snippet in string.gfind(selection, "%S+") do + if snippet == magicstring then + replacement = replacement .. "\n" + else + replacement = replacement .. snippet .. "\n" + end + end + + if endcolumn == 0 then replacement = replacement .. "\n" end + + editor:ReplaceSel(replacement) + +end + +function sort_text() + + local startposition = editor.SelectionStart + local endposition = editor.SelectionEnd + + if startposition == endposition then return end + + -- local startcolumn = column_of_position(startposition) + -- local endcolumn = column_of_position(endposition) + -- + -- editor:SetSel(startposition,endposition) + + local startline = props['SelectionStartLine'] + local endline = props['SelectionEndLine'] + local startcolumn = props['SelectionStartColumn'] - 1 + local endcolumn = props['SelectionEndColumn'] - 1 + + startposition = extend_to_start() + endposition = extend_to_end() + + local selection = string.gsub(editor:GetSelText(), "%s*$", '') + + list = string.grab(selection,"[^\n\r]+") + table.alphasort(list, startcolumn) + local replacement = table.concat(list, "\n") + + editor:GotoPos(startposition) + editor:SetSel(startposition,endposition) + + if endcolumn == 0 then replacement = replacement .. "\n" end + + editor:ReplaceSel(replacement) + +end + +function document_text() + + local startposition = editor.SelectionStart + local endposition = editor.SelectionEnd + + if startposition == endposition then return end + + startposition = extend_to_start() + endposition = extend_to_end() + + editor:SetSel(startposition,endposition) + + local filetype = getfiletype() + + local replacement = '' + for i = editor:LineFromPosition(startposition), editor:LineFromPosition(endposition) do + local str = editor:GetLine(i) + if filetype == 'xml' then + if string.find(str,"^<%!%-%- .* %-%->%s*$") then + replacement = replacement .. string.gsub(str,"^<%!%-%- (.*) %-%->(%s*)$", "%1\n") + elseif not string.spacy(str) then + replacement = replacement .. '<!-- ' .. string.gsub(str,"(%s*)$", '') .. " -->\n" + else + replacement = replacement .. str + end + else + if string.find(str,"^%%D%s+$") then + replacement = replacement .. "\n" + elseif string.find(str,"^%%D ") then + replacement = replacement .. string.gsub(str,"^%%D ", '') + else + replacement = replacement .. '%D ' .. str + end + end + end + + editor:ReplaceSel(string.gsub(replacement, "[\n\r]$", '')) + +end + +function quote_text() + + local filetype, leftquotation, rightquotation = getfiletype(), '', '' + + if filetype == 'xml' then + leftquotation, rightquotation = "<quotation>", "</quotation>" + leftquote, rightquote = "<quotation>", "</quote>" + else + leftquotation, rightquotation = "\\quotation {", "}" + leftquote, rightquote = "\\quote {", "}" + end + + local replacement = editor:GetSelText() + replacement = string.gsub(replacement, "\`\`(.-)\'\'", leftquotation .. "%1" .. rightquotation) + replacement = string.gsub(replacement, "\"(.-)\"", leftquotation .. "%1" .. rightquotation) + replacement = string.gsub(replacement, "\`(.-)\'", leftquote .. "%1" .. rightquote ) + replacement = string.gsub(replacement, "\'(.-)\'", leftquote .. "%1" .. rightquote ) + editor:ReplaceSel(replacement) + +end + +function compound_text() + + local filetype = getfiletype() + + if filetype == 'xml' then + editor:ReplaceSel(string.gsub(editor:GetSelText(),"(>[^<%-][^<%-]+)([-\/])(%w%w+)","%1<compound token='%2'/>%3")) + else + editor:ReplaceSel(string.gsub(editor:GetSelText(),"([^\|])([-\/]+)([^\|])","%1|%2|%3")) + end + +end + +-- written while listening to Alanis Morissette's acoustic +-- Jagged Little Pill and Tori Amos' Beekeeper after +-- reinstalling on my good old ATH-7 + +local language = props["ctx.spellcheck.language"] +local wordsize = props["ctx.spellcheck.wordsize"] +local wordpath = props["ctx.spellcheck.wordpath"] + +if language == '' then language = 'uk' end +if wordsize == '' then wordsize = 4 else wordsize = tonumber(wordsize) end + +local wordfile = "" +local wordlist = {} +local worddone = 0 + +-- we use wordlist as a hash so that we can add entries without the +-- need to sort and also use a fast (built in) search + +-- function kpsewhich_file(filename,filetype,progname) +-- local progflag, typeflag = '', '' +-- local tempname = os.tmpname() +-- if progname then +-- progflag = " --progname=" .. progname .. " " +-- end +-- if filetype then +-- typeflag = " --format=" .. filetype .. " " +-- end +-- local command = "kpsewhich" .. progflag .. typeflag .. " " .. filename .. " > " .. tempname +-- os.execute(command) +-- for line in io.lines(tempname) do +-- return string.gsub(line, "\s*$", '') +-- end +-- end + +function check_text() + + local dlanguage = props["ctx.spellcheck.language"] + local dwordsize = props["ctx.spellcheck.wordsize"] + local dwordpath = props["ctx.spellcheck.wordpath"] + + if dlanguage ~= '' then dlanguage = tostring(language) end + if dwordsize ~= '' then dwordsize = tonumber(wordsize) end + + local firstline, skipfirst = editor:GetLine(0), false + local filetype, wordskip, wordgood = getfiletype(), '', '' + + if filetype == 'tex' then + wordskip = "\\" + elseif filetype == 'xml' then + wordskip = "<" + wordgood = ">" + end + + if props["ctx.spellcheck.language"] == 'auto' then + if filetype == 'tex' then + -- % version =1.0 language=uk + firstline = string.gsub(firstline, "^%%%s*", '') + firstline = string.gsub(firstline, "%s*$", '') + for key, val in string.gfind(firstline,"(%w+)=(%w+)") do + if key == "language" then + language = val + traceln("auto document language " .. "'" .. language .. "' (tex)") + end + end + skipfirst = true + elseif filetype == 'xml' then + -- <?xml version='1.0' language='uk' ?> + firstline = string.gsub(firstline, "^%<%?xml%s*", '') + firstline = string.gsub(firstline, "%s*%?%>%s*$", '') + for key, val in string.gfind(firstline,"(%w+)=[\"\'](.-)[\"\']") do + if key == "language" then + language = val + traceln("auto document language " .. "'" .. language .. "' (xml)") + end + end + skipfirst = true + end + end + + local fname = props["ctx.spellcheck.wordfile." .. language] + local fsize = props["ctx.spellcheck.wordsize." .. language] + + if fsize ~= '' then wordsize = tonumber(fsize) end + + if fname ~= '' and fname ~= wordfile then + wordfile, worddone, wordlist = fname, 0, {} + for filename in string.gfind(wordfile,"[^%,]+") do + if wordpath ~= '' then + filename = string.expand(wordpath) .. '/' .. filename + end + if io.exists(filename) then + traceln("loading " .. filename) + for line in io.lines(filename) do + if not string.find(line,"^[\%\#\-]") then + str = string.gsub(line,"%s*$", '') + rawset(wordlist,str,true) -- table.insert(wordlist,str) + worddone = worddone + 1 + end + end + else + traceln("unknown file '" .. filename .."'") + end + end + traceln(worddone .. " words loaded") + end + + reset_text() + + if worddone == 0 then + traceln("no (valid) language or wordfile specified") + else + traceln("start checking") + if wordskip ~= '' then + traceln("ignoring " .. wordskip .. "..." .. wordgood) + end + local i, j, lastpos, startpos, endpos, snippet, len, first = 0, 0, -1, 0, 0, '', 0, 0 + local ok, skip, ch = false, false, '' + if skipfirst then first = string.len(firstline) end + for k = first, editor.TextLength do + ch = editor:textrange(k,k+1) + if wordgood ~= '' and ch == wordgood then + skip = false + elseif ch == wordskip then + skip = true + end + if string.find(ch,"%w") and not string.find(ch,"%d") then + if not skip then + if ok then + endpos = k + else + startpos = k + endpos = k + ok = true + end + end + elseif ok and not skip then + len = endpos - startpos + 1 + if len >= wordsize then + snippet = editor:textrange(startpos,endpos+1) + i = i + 1 + if wordlist[snippet] or wordlist[string.lower(snippet)] then -- table.found(wordlist,snippet) + j = j + 1 + else + editor:StartStyling(startpos,INDICS_MASK) + editor:SetStyling(len,INDIC2_MASK) -- INDIC0_MASK+2 + end + end + ok = false + elseif wordgood == '' then + skip = (ch == wordskip) + end + end + traceln(i .. " words checked, " .. (i-j) .. " errors") + end + +end + +function reset_text() + editor:StartStyling(0,INDICS_MASK) + editor:SetStyling(editor.TextLength,INDIC_PLAIN) +end + +-- menu + +local menuactions = {} +local menufunctions = {} + +function UserListShow(menutrigger, menulist) + local menuentries = {} + local list = string.grab(menulist,"[^%|]+") + menuactions = {} + for i=1, table.len(list) do + if list[i] ~= '' then + for key, val in string.gfind(list[i],"%s*(.+)=(.+)%s*") do + table.insert(menuentries,key) + rawset(menuactions,key,val) + end + end + end + local menustring = table.join(menuentries,'|') + if menustring == "" then + traceln("There are no templates defined for this file type.") + else + editor.AutoCSeparator = string.byte('|') + editor:UserListShow(menutrigger,menustring) + editor.AutoCSeparator = string.byte(' ') + end +end + +function OnUserListSelection(trigger,choice) + if menufunctions[trigger] and menuactions[choice] then + return menufunctions[trigger](menuactions[choice]) + else + return false + end +end + +-- main menu + +local menutrigger = 12 + +function show_menu(menulist) + UserListShow(menutrigger, menulist) +end + +function process_menu(action) + if not string.find(action,"%(%)$") then + assert(loadstring(action .. "()"))() + else + assert(loadstring(action))() + end +end + +menufunctions[12] = process_menu + +-- templates + +local templatetrigger = 13 + +-- local ctx_template_paths = { "./ctx-templates", "../ctx-templates", "../../ctx-templates" } +-- local ctx_auto_templates = false +-- local ctx_template_list = "" +-- local ctx_dir_list = { } +-- local ctx_dir_name = "./ctx-templates" + +-- local ctx_path_list = {} +-- local ctx_path_done = {} + +-- function ctx_list_loaded() +-- return ctx_dir_list and table.getn(ctx_dir_list) > 0 +-- end + +-- function insert_template(templatelist) +-- if props["ctx.template.scan"] == "yes" then +-- local current = props["FileDir"] .. "+" .. props["FileExt"] -- no name +-- local rescan = props["ctx.template.rescan"] == "yes" +-- local suffix = props["ctx.template.suffix."..props["FileExt"]] -- alas, no suffix expansion here +-- if rescan then +-- print("re-scanning enabled") +-- end +-- if current ~= ctx_file_path then +-- rescan = true +-- ctx_file_path = current +-- ctx_file_done = false +-- ctx_template_list = "" +-- end +-- if not ctx_file_done or rescan then +-- local pattern = "*.*" +-- for i, pathname in ipairs(ctx_template_paths) do +-- print("scanning " .. pathname .. " for " .. pattern) +-- ctx_dir_name = pathname +-- ctx_dir_list = get_dir_list(pathname .. "/" .. pattern) +-- if ctx_list_loaded() then +-- break +-- end +-- end +-- ctx_file_done = true +-- end +-- if ctx_list_loaded() then +-- ctx_template_list = "" +-- local pattern = "%." .. suffix .. "$" +-- for j, filename in ipairs(ctx_dir_list) do +-- if string.find(filename,pattern) then +-- local menuname = string.gsub(filename,"%..-$","") +-- if ctx_template_list ~= "" then +-- ctx_template_list = ctx_template_list .. "|" +-- end +-- ctx_template_list = ctx_template_list .. menuname .. "=" .. ctx_dir_name .. "/" .. filename +-- end +-- end +-- else +-- print("no template files found") +-- end +-- if ctx_template_list == "" then +-- ctx_auto_templates = false +-- print("no file related templates found") +-- else +-- ctx_auto_templates = true +-- templatelist = ctx_template_list +-- end +-- end +-- if templatelist ~= "" then +-- UserListShow(templatetrigger, templatelist) +-- end +-- end + +local ctx_template_paths = { "./ctx-templates", "../ctx-templates", "../../ctx-templates" } +local ctx_auto_templates = false +local ctx_template_list = "" + +local ctx_path_list = {} +local ctx_path_done = {} +local ctx_path_name = {} + +function ctx_list_loaded(path) + return ctx_path_list[path] and table.getn(ctx_path_list[path]) > 0 +end + +function insert_template(templatelist) + if props["ctx.template.scan"] == "yes" then + local path = props["FileDir"] + local rescan = props["ctx.template.rescan"] == "yes" + local suffix = props["ctx.template.suffix." .. props["FileExt"]] -- alas, no suffix expansion here + local current = path .. "+" .. props["FileExt"] + if rescan then + print("re-scanning enabled") + end + ctx_template_list = "" + if not ctx_path_done[path] or rescan then + local pattern = "*.*" + for i, pathname in ipairs(ctx_template_paths) do + print("scanning " .. string.gsub(path,"\\","/") .. "/" .. pathname) + ctx_path_name[path] = pathname + ctx_path_list[path] = get_dir_list(pathname .. "/" .. pattern) + if ctx_list_loaded(path) then + print("finished locating template files") + break + end + end + if ctx_list_loaded(path) then + print(table.getn(ctx_path_list[path]) .. " template files found") + else + print("no template files found") + end + end + if ctx_list_loaded(path) then + ctx_template_list = "" + local pattern = "%." .. suffix .. "$" + local n = 0 + for j, filename in ipairs(ctx_path_list[path]) do + if string.find(filename,pattern) then + n = n + 1 + local menuname = string.gsub(filename,"%..-$","") + if ctx_template_list ~= "" then + ctx_template_list = ctx_template_list .. "|" + end + ctx_template_list = ctx_template_list .. menuname .. "=" .. ctx_path_name[path] .. "/" .. filename + end + end + if not ctx_path_done[path] then + print(n .. " suitable template files found") + end + end + ctx_path_done[path] = true + if ctx_template_list == "" then + ctx_auto_templates = false + else + ctx_auto_templates = true + templatelist = ctx_template_list + end + else + ctx_auto_templates = false + end + if templatelist ~= "" then + UserListShow(templatetrigger, templatelist) + end +end + + +-- ctx.template.[whatever].[filetype] +-- ctx.template.[whatever].data.[filetype] +-- ctx.template.[whatever].file.[filetype] +-- ctx.template.[whatever].list.[filetype] + +function process_template_one(action) + local text = nil + if ctx_auto_templates then + local f = io.open(action,"r") + if f then + text = string.gsub(f:read("*all"),"\n$","") + f:close() + else + print("unable to auto load template file " .. text) + text = nil + end + end + if not text or text == "" then + text = props["ctx.template." .. action .. ".file"] + if not text or text == "" then + text = props["ctx.template." .. action .. ".data"] + if not text or text == "" then + text = props["ctx.template." .. action] + end + else + local f = io.open(text,"r") + if f then + text = string.gsub(f:read("*all"),"\n$","") + f:close() + else + print("unable to load template file " .. text) + text = nil + end + end + end + if text then + text = string.replace(text,"\\n","\n") + local pos = string.find(text,"%?") + text = string.replace(text,"%?","") + editor:insert(editor.CurrentPos,text) + if pos then + editor.CurrentPos = editor.CurrentPos + pos - 1 + editor.SelectionStart = editor.CurrentPos + editor.SelectionEnd = editor.CurrentPos + editor:GotoPos(editor.CurrentPos) + end + end +end + +menufunctions[13] = process_template_one +menufunctions[14] = process_template_two + +-- command.name.26.*=Open Logfile +-- command.subsystem.26.*=3 +-- command.26.*=open_log +-- command.save.before.26.*=2 +-- command.groupundo.26.*=yes +-- command.shortcut.26.*=Ctrl+E + +function open_log() + scite.Open(props['FileName'] .. ".log") +end diff --git a/Master/texmf-dist/scripts/context/perl/makempy.pl b/Master/texmf-dist/scripts/context/perl/makempy.pl index 49fd9bbd21a..f263dd4259d 100644 --- a/Master/texmf-dist/scripts/context/perl/makempy.pl +++ b/Master/texmf-dist/scripts/context/perl/makempy.pl @@ -46,8 +46,6 @@ my $format = "plain" ; # can be "context" for plain users too "force" => \$force , "pdftops" => \$pmethod , "xpdf" => \$pmethod , -# "acrobat" => \$amethod , # nowadays the reader is not that clear about this being permitted - "reader" => \$amethod , "gs" => \$gmethod , "ghostscript" => \$gmethod , "noclean" => \$noclean ) ; @@ -202,15 +200,13 @@ sub show_help_info report ("--force : force processing (ignore checksum)" ) ; report ("--silent : don't show messages" ) ; print "\n" ; - report ("--acrobat : use acrobat (reader) for pdf->ps (on unix)") ; report ("--pdftops : use pdftops (xpdf) pdf->ps") ; report ("--ghostscript : use ghostscript (gs) for pdf->ps") ; print "\n" ; report ("input file : metapost file with graphics") ; report ("programs needed : texexec and english context") ; report (" : pdftops from the xpdf suite, or") ; # page size buggy - report (" : pdf2ps and ghostscript, or") ; - report (" : acrobat reader for unix, and") ; + report (" : pdf2ps and ghostscript, and") ; report (" : pstoedit and ghostscript") ; report ("output file : metapost file with pictures") ; exit } diff --git a/Master/texmf-dist/scripts/context/perl/mptopdf.pl b/Master/texmf-dist/scripts/context/perl/mptopdf.pl index efbc5ba0122..f85c36891b9 100644 --- a/Master/texmf-dist/scripts/context/perl/mptopdf.pl +++ b/Master/texmf-dist/scripts/context/perl/mptopdf.pl @@ -5,7 +5,7 @@ eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $ #D \module #D [ file=mptopdf.pl, -#D version=2005.10.31, +#D version=2000.05.29, #D title=converting MP to PDF, #D subtitle=\MPTOPDF, #D author=Hans Hagen, @@ -22,6 +22,7 @@ eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $ use Config ; use Getopt::Long ; use strict ; +use File::Basename ; $Getopt::Long::passthrough = 1 ; # no error message $Getopt::Long::autoabbrev = 1 ; # partial switch accepted @@ -36,88 +37,118 @@ my $PassOn = '' ; "passon" => \$PassOn, "latex" => \$Latex ) ; -my $program = "MPtoPDF 1.3" ; -my $pattern = $ARGV[0] ; +my $program = "MPtoPDF 1.3.2" ; +my $pattern = "@ARGV" ; # was $ARGV[0] +my $miktex = 0 ; my $done = 0 ; my $report = '' ; my $texlatexswitch = " --tex=latex --format=latex " ; my $mplatexswitch = " --tex=latex " ; -my $dosish = ($Config{'osname'} =~ /^(ms)?dos|^os\/2|^(ms|cyg)win/i) ; -my $miktex = ($ENV{"TEXSYSTEM"} =~ /miktex/io); -my $escapeshell = ( ($ENV{'SHELL'}) && ($ENV{'SHELL'} =~ m/sh/i )); +my $dosish = ($Config{'osname'} =~/^(ms)?dos|^os\/2|^mswin/i) ; +my $escapeshell = (($ENV{'SHELL'}) && ($ENV{'SHELL'} =~ m/sh/i )); + +if ($ENV{"TEXSYSTEM"}) { + $miktex = ($ENV{"TEXSYSTEM"} =~ /miktex/io) ; +} my @files ; my $command = my $mpbin = '' ; -sub CopyFile # agressive copy, works for open files like in gs - { my ($From,$To) = @_ ; - return unless open(INP,"<$From") ; binmode INP ; - return unless open(OUT,">$To") ; binmode OUT ; - while (<INP>) { print OUT $_ } +# agressive copy, works for open files like in gs + +sub CopyFile { + my ($From,$To) = @_ ; + return unless open(INP,"<$From") ; + return unless open(OUT,">$To") ; + binmode INP ; + binmode OUT ; + while (<INP>) { + print OUT $_ ; + } close (INP) ; - close (OUT) } - -if (($pattern eq '')||($Help)) - { print "\n$program : provide MP output file (or pattern)\n" ; - exit } -elsif ($pattern =~ /\.mp$/io) - { shift @ARGV ; my $rest = join(" ", @ARGV) ; - if (open(INP,$pattern)) - { while (<INP>) - { if (/(documentstyle|documentclass|begin\{document\})/io) - { $Latex = 1 ; last } } - close (INP) } - if ($RawMP) - { if ($Latex) - { $rest .= " $mplatexswitch" } + close (OUT) ; +} + +if (($pattern eq '')||($Help)) { + print "\n$program : provide MP output file (or pattern)\n" ; + exit ; +} elsif ($pattern =~ /\.mp$/io) { + shift @ARGV ; my $rest = join(" ", @ARGV) ; + if (open(INP,$pattern)) { + while (<INP>) { + if (/(documentstyle|documentclass|begin\{document\})/io) { + $Latex = 1 ; last ; + } + } + close (INP) ; + } + if ($RawMP) { + if ($Latex) { + $rest .= " $mplatexswitch" ; + } if ($MetaFun) { - $mpbin = 'mpost --mem=metafun --progname=mpost' ; + $mpbin = "mpost --progname=mpost --mem=metafun" ; } else { - $mpbin = 'mpost --mem=mpost' ; + $mpbin = "mpost --mem=mpost" ; } + } else { + if ($Latex) { + $rest .= " $texlatexswitch" ; + } + $mpbin = "texexec --mptex $PassOn" ; } - else - { if ($Latex) - { $rest .= " $texlatexswitch" } - $mpbin = 'texexec --mptex $PassOn' } + print "\n$program : running '$mpbin'\n" ; my $error = system ("$mpbin $rest $pattern") ; - if ($error) - { print "\n$program : error while processing mp file\n" ; exit } - else - { $pattern =~ s/\.mp$//io ; - @files = glob "$pattern.*" } } -elsif (-e $pattern) - { @files = ($pattern) } -elsif ($pattern =~ /.\../o) - { @files = glob "$pattern" } -else - { $pattern .= '.*' ; - @files = glob "$pattern" } - -foreach my $file (@files) - { $_ = $file ; - if (s/\.(\d+|mps)$// && -e $file) - { if ($miktex) - { $command = "pdfetex -undump=mptopdf" } - else - { $command = "pdfetex --fmt=mptopdf --progname=context" } - if ($dosish) - { $command = "$command \\relax $file" } - else - { $command = "$command \\\\relax $file" } - #~ print $command ; + if ($error) { + print "\n$program : error while processing mp file\n" ; + exit ; + } else { + $pattern =~ s/\.mp$//io ; + @files = glob "$pattern.*" ; + } +} elsif (-e $pattern) { + @files = ($pattern) ; +} elsif ($pattern =~ /.\../o) { + @files = glob "$pattern" ; +} else { + $pattern .= '.*' ; + @files = glob "$pattern" ; +} + +foreach my $file (@files) { + $_ = $file ; + if (s/\.(\d+|mps)$// && -e $file) { + if ($miktex) { + $command = "pdfetex -undump=mptopdf" ; + } else { + $command = "pdfetex -fmt=mptopdf -progname=context" ; + } + if ($dosish) { + $command = "$command \\relax $file" ; + } else { + $command = "$command \\\\relax $file" ; + } system($command) ; - rename ("$_.pdf", "$_-$1.pdf") ; - if (-e "$_.pdf") { CopyFile ("$_.pdf", "$_-$1.pdf") } - if ($done) { $report .= " +" } + my $pdfsrc = basename($_).".pdf"; + rename ($pdfsrc, "$_-$1.pdf") ; + if (-e $pdfsrc) { + CopyFile ($pdfsrc, "$_-$1.pdf") ; + } + if ($done) { + $report .= " +" ; + } $report .= " $_-$1.pdf" ; - ++$done } } + ++$done ; + } +} -if ($report eq '') - { $report = '*' } +if ($report eq '') { + $report = '*' ; +} -if ($done) - { print "\n$program : $pattern is converted to$report\n" } -else - { print "\n$program : no filename matches $pattern\n" } +if ($done) { + print "\n$program : $pattern is converted to$report\n" ; +} else { + print "\n$program : no filename matches $pattern\n" ; +} diff --git a/Master/texmf-dist/scripts/context/perl/pdftrimwhite.pl b/Master/texmf-dist/scripts/context/perl/pdftrimwhite.pl new file mode 100644 index 00000000000..08c1de5d778 --- /dev/null +++ b/Master/texmf-dist/scripts/context/perl/pdftrimwhite.pl @@ -0,0 +1,525 @@ +eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q' + if 0; + +#D \module +#D [ file=pdftrimwhite.pl, +#D version=2000.07.13, +#D title=PDF postprocessing, +#D subtitle=cropping whitespace from pdf files, +#D author=Hans Hagen, +#D date=\currentdate, +#D copyright=PRAGMA ADE] + +#C This module is part of the \CONTEXT\ macro||package and is +#C therefore copyrighted by \PRAGMA. See readme.pdf for +#C details. + +#D This script can be used to crop margins that contain +#D useless information from a \PDF\ image. It does so by: +#D +#D \startitemize[packed,n] +#D \som cropping the image into an alternative file +#D \som determining the boundingbox of the alternative +#D \som cropping the image into a resulting file +#D \stoppacked +#D +#D In the process, some checks are carried out. Step~1 is +#D taken care of by \PDFTEX, step~2 by \GHOSTSCRIPT, using a +#D file generated by \PDFTOPS, and \PDFTEX\ is responsible +#D for step~3. +#D +#D \startuseMPgraphic{original} +#D numeric n ; n = 1cm ; +#D path p ; p := fullsquare xyscaled (8n,12n) ; +#D path q ; q := fullsquare xyscaled (2n,3n) shifted (n,n) ; +#D path r ; r := ((0,0)--(3n,0)) shifted (0, 5.5n) ; +#D path s ; s := ((0,0)--(3n,0)) shifted (0,-5.5n) ; +#D path t ; t := (-2n,-4n) ; +#D path u ; u := p enlarged -.75n ; +#D path v ; v := p enlarged (-1.75n,-2n) shifted (n,1.25n) ; +#D path w ; w := q enlarged .25n ; +#D fill p withcolor .7white ; +#D fill q withcolor .7green ; +#D draw r withpen pencircle scaled .25n withcolor .7green ; +#D draw s withpen pencircle scaled .25n withcolor .7green ; +#D draw t withpen pencircle scaled .50n withcolor .7green ; +#D draw u withpen pencircle scaled .10n withcolor white ; +#D draw v withpen pencircle scaled .10n withcolor .7red ; +#D draw w withpen pencircle scaled .10n ; +#D verbatimtex \tttf \setupframed[frame=off,align=left] etex ; +#D label (btex \framed{crap} etex, center r) ; +#D label (btex \framed{crap} etex, center s) ; +#D label (btex \framed{crap} etex, center t) ; +#D label (btex \framed{graphic} etex, center q) ; +#D label.urt(btex \framed{page} etex, llcorner p) ; +#D label.urt(btex \framed{crop} etex, llcorner u) ; +#D label.lft(btex \framed{leftcrop\\ +#D rightcrop\\ +#D topcrop\\ +#D bottomcrop} etex, .5[ulcorner v,llcorner v]) ; +#D label.bot(btex \framed{offset} etex, .5[llcorner w,lrcorner w]) ; +#D \stopuseMPgraphic +#D +#D \placefigure +#D [here][fig:cropcrap] +#D {Crops and offsets.} +#D {\useMPgraphic{original}} +#D +#D The \TEX\ part has two alternatives, one using \CONTEXT, and +#D another using plain \TEX. The \CONTEXT\ method is slower but +#D can be extended more easily. +#D +#D The script is executed as follows: +#D +#D \starttyping +#D cropcrap <original> [<result>] [<switches>] +#D \stoptyping +#D +#D The next call crops \type {test.pdf} to its natural +#D boundingbox. +#D +#D \starttyping +#D cropcrap test +#D \stoptyping +#D +#D If the file has some crap at the bottom, you can say: +#D +#D \starttyping +#D cropcrap test --bottomcrop=2cm +#D \stoptyping +#D +#D This clips 2cm from the bottom. You can clip on all sides +#D individually, in combination or at once, like in: +#D +#D \starttyping +#D cropcrap test --bottomcrop=2cm --crop=1cm +#D \stoptyping +#D +#D The final result is a tightly cropped image. In order to get +#D a 5mm margin around this image, you can say: +#D +#D \starttyping +#D cropcrap test --bottomcrop=2cm --offset=5mm +#D \stoptyping +#D +#D By default, the script intercepts logging messages and +#D writes them to a logfile with the same name as the +#D resulting image and the prefix \type {log}. If no name is +#D given, the name \type {cropcrap} is used for all resulting +#D files. +#D +#D By default, \CONTEXT\ is used. When installed properly, you +#D can also use plain \TEX, by adding a switch \type +#D {--plain}. Partial switched are accepted, so the next call +#D is valid: +#D +#D \starttyping +#D cropcrap test result --bot=2cm --off=5mm --plain +#D \stoptyping +#D +#D The current implementation uses an intermediate \POSTSCRIPT\ +#D file. This may change as \GHOSTSCRIPT\ gets more clever with +#D \PDF\ files. +#D +#D In \in {figure} [fig:cropcrap] the green rectangle is the +#D picture we want to keep. Around this picture, we want a +#D margin, represented by the black rectangle, and specified by +#D \type {--offset}. The white rectangle is the cropbox +#D defined by \type {--crop}. That way we get rid of header +#D and footerlines. The red rectangle results from an +#D additional \type {--leftcrop} and \type {-bottomcrop} and +#D takes care of some content, as represented by the green +#D dot. +#D +#D The \type {--verbose} switch can be used to disable the +#D interception of log messages. + +#D We load a few \PERL\ modules \unknown\ + +use Config ; +use Getopt::Long ; + +use strict ; + +#D \unknown\ and initialize them. + +Getopt::Long::Configure + ("auto_abbrev", + "ignore_case", + "pass_through") ; + +#D Before fetching the switches, we initialize the +#D variables. + +my $Crop = "0cm" ; + +my $LeftCrop = "0cm" ; +my $RightCrop = "0cm" ; +my $TopCrop = "0cm" ; +my $BottomCrop = "0cm" ; + +my $Offset = "0cm" ; + +my $GSbin = "" ; +my $Verbose = 0 ; +my $Help = 0 ; +my $UsePlain = 0 ; + +my $Page = 1 ; + +#D On \MSWINDOWS\ and \UNIX\ the following defaults, combined +#D with the check later, should work out okay. + +my $pdfps = "pdftops" ; +my $gs = "gs" ; + +my $thisisunix = $Config{'osname'} !~ /dos|mswin/i ; + +#D When no resulting file is given, we use \type {cropcrap} +#D as name (checked later). + +my $figurefile = "" ; +my $resultfile = "" ; +my $tempfile = "" ; + +my $programname = "cropcrap" ; + +#D Messages are temporarily saved and written to a log file +#D afterwards. + +my $results = "" ; +my $pipe = "" ; +my $result = "" ; + +#D Unfortunately we need this information, first since +#D \PDFTOPS\ does not honor the cropbox, and second because +#D the vertical coordinated are swapped. + +my $pwidth = 597 ; +my $pheight = 847 ; +my $hoffset = 0 ; +my $voffset = 0 ; + +#D A few more variables. + +my $width = my $height = my $llx = my $lly = my $urx = my $ury = 0 ; + +#D Here are the switches we accept. The \type {--gsbin} switch +#D is a bonus one, and the \type {--help} switch comes +#D naturally. + +&GetOptions + ( "leftcrop=s" => \$LeftCrop , + "rightcrop=s" => \$RightCrop , + "topcrop=s" => \$TopCrop , + "bottomcrop=s" => \$BottomCrop, + "crop=s" => \$Crop , + "offset=s" => \$Offset , + "verbose" => \$Verbose , + "gsbin=s" => \$GSbin , + "plain" => \$UsePlain , + "page=i" => \$Page , + "help" => \$Help ) ; + +#D If asked for, or if no file is given, we provide some +#D help information. + +sub PrintHelp + { print "This is PdfTrimWhite\n\n" . + "usage:\n\n" . + "cropcrap [switches] filename result\n\n" . + "switches:\n\n" . + "--crop=<dimen>\n" . + "--offset=<dimen>\n" . + "--leftcrop=<dimen>\n" . + "--rightcrop=<dimen>\n" . + "--topcrop=<dimen>\n" . + "--bottomcrop=<dimen>\n" . + "--gsbin=<string>\n" . + "--page=<number>\n" . + "--plain\n" . + "--verbose\n" } + +#D The preparations: + +sub GetItRight + { if ($Help) + { PrintHelp() ; exit } + $figurefile = $ARGV[0] ; $figurefile =~ s/\.pdf$//oi ; + $resultfile = $ARGV[1] ; $resultfile =~ s/\.pdf$//oi ; + $tempfile = "pdftrimwhite-$resultfile" ; + if ($figurefile eq '') + { PrintHelp() ; exit } + unless ($thisisunix) + { $gs = "gswin32c" } + if ($GSbin ne '') + { $gs = $GSbin } + unless (-e "$figurefile.pdf") + { print "Something is terribly wrong: no file found\n" ; + exit } + if (($resultfile eq '')||($resultfile=~/(^\-|\.)/io)) + { $resultfile = $programname } + $pipe = "2>&1" ; + if ($thisisunix) + { $pipe = "2>&1" } } + +#D Something common. + +sub SavePageData + { return "% saving page data +\\immediate\\openout\\scratchwrite=$figurefile.tmp +\\immediate\\write\\scratchwrite + {\\HOffsetBP\\space\\VOffsetBP\\space + \\FigureWidthBP\\space\\FigureHeightBP} +\\immediate\\closeout\\scratchwrite\n" } + +sub MakePageConTeXt + { return "% the real work +\\definepapersize + [Crap] + [width=\\FigureWidth, + height=\\FigureHeight] +\\setuppapersize + [Crap][Crap] +\\setuplayout + [topspace=0cm,backspace=0pt, + height=middle,width=middle, + header=0pt,footer=0pt] +\\starttext + \\startstandardmakeup + \\clip + [voffset=\\VOffset, + hoffset=\\HOffset, + width=\\FigureWidth, + height=\\FigureHeight] + {\\externalfigure[$figurefile.pdf][page=$Page]\\hss} + \\stopstandardmakeup +\\stoptext\n" } + +sub MakePagePlainTeX + { return "% the real work +\\output{} +\\hoffset=-1in +\\voffset=\\hoffset +\\pdfpageheight=\\FigureHeight +\\pdfpagewidth=\\FigureWidth +\\vbox to \\pdfpageheight + {\\offinterlineskip + \\vskip-\\VOffset + \\hbox to \\pdfpagewidth{\\hskip-\\HOffset\\box0\\hss} + \\vss} +\\end\n" } + +sub CalculateClip + { return "% some calculations +\\dimen0=\\figurewidth +\\dimen2=\\figureheight +\\dimen4=$Crop +\\dimen6=$Crop +\\advance\\dimen4 by $LeftCrop +\\advance\\dimen6 by $TopCrop +\\advance\\dimen0 by -\\dimen4 +\\advance\\dimen0 by -$Crop +\\advance\\dimen0 by -$RightCrop +\\advance\\dimen2 by -\\dimen6 +\\advance\\dimen2 by -$Crop +\\advance\\dimen2 by -$BottomCrop +\\edef\\FigureWidth {\\the\\dimen0} +\\edef\\FigureHeight{\\the\\dimen2} +\\edef\\HOffset {\\the\\dimen4} +\\edef\\VOffset {\\the\\dimen6} +\\ScaledPointsToWholeBigPoints{\\number\\dimen0}\\FigureWidthBP +\\ScaledPointsToWholeBigPoints{\\number\\dimen2}\\FigureHeightBP +\\ScaledPointsToWholeBigPoints{\\number\\dimen4}\\HOffsetBP +\\ScaledPointsToWholeBigPoints{\\number\\dimen6}\\VOffsetBP\n" } + +sub RecalculateClip + { return "% some calculations +\\dimen0=${width}bp +\\dimen2=${height}bp +\\dimen4=${hoffset}bp +\\dimen6=${pheight}bp +\\advance\\dimen0 by $Offset +\\advance\\dimen0 by $Offset +\\advance\\dimen2 by $Offset +\\advance\\dimen2 by $Offset +\\advance\\dimen4 by ${llx}bp +\\advance\\dimen4 by -$Offset +\\advance\\dimen6 by -${lly}bp +\\advance\\dimen6 by $Offset +\\advance\\dimen6 by -\\dimen2 +\\advance\\dimen6 by $TopCrop +\\edef\\FigureWidth {\\the\\dimen0} +\\edef\\FigureHeight{\\the\\dimen2} +\\edef\\HOffset {\\the\\dimen4} +\\edef\\VOffset {\\the\\dimen6}\n" } + +#D The previous scripts could be more sparse, but for the +#D moment we prefer readability. Both scripts save some +#D information in temporary file. We choose between them with +#D the following sub routine. + +#D The first pass: + +sub PrepareConTeXt + { return "% interface=en +\\setupoutput[pdftex] +\\getfiguredimensions[$figurefile.pdf][page=$Page]\n" } + +sub PreparePlainTeX + { return "% plain tex alternative, needs recent supp-mis +\\input supp-mis +\\pdfoutput=1 +\\newdimen\\figurewidth +\\newdimen\\figureheight +\\setbox0=\\hbox + {\\immediate\\pdfximage page $Page {$figurefile.pdf}\\pdfrefximage\\pdflastximage} +\\figurewidth=\\wd0 +\\figureheight=\\ht0\n" } + +sub PrepareFirstPass + { open (TEX, ">$tempfile.tex") ; + if ($UsePlain) + { print TEX + PreparePlainTeX . + CalculateClip . + SavePageData . + MakePagePlainTeX } + else + { print TEX + PrepareConTeXt . + CalculateClip . + SavePageData . + MakePageConTeXt } + close TEX } + +#D The second pass looks much like the first one, but this +#D time we don't save information, use the natural +#D boundingbox, and provide the offset. + +sub SetupConTeXt + { return "% interface=en +\\setupoutput[pdftex]\n" } + +sub SetupPlainTeX + { return "% plain tex alternative +\\pdfoutput=1 +\\setbox0=\\hbox + {\\immediate\\pdfximage page $Page {$figurefile.pdf}\\pdfrefximage\\pdflastximage}\n" } + +sub PrepareSecondPass + { open (TEX, ">$tempfile.tex") ; + if ($UsePlain) + { print TEX + SetupPlainTeX . + RecalculateClip . + MakePagePlainTeX } + else + { print TEX + SetupConTeXt . + RecalculateClip . + MakePageConTeXt } + close TEX } + +#D The information we save in the first pass, is loaded here. + +sub FetchPaperSize + { open (TMP,"$figurefile.tmp") ; + while (<TMP>) + { chomp ; + if (/^(\d+) (\d+) (\d+) (\d+) *$/oi) + { $hoffset = $1 ; + $voffset = $2 ; + $pwidth = $3 ; + $pheight = $4 ; + last } } + close (TMP) } + +#D Here we try to find the natural boundingbox. We need to +#D pick up the page dimensions here. + +sub RunTeX + { if ($UsePlain) + { $result = `pdftex -prog=pdftex -fmt=plain -int=batchmode $tempfile` } + else + { $result = `texexec --batch --once --purge $tempfile` } + print $result if $Verbose ; $results .= "$result\n" } + +sub FindBoundingBox + { $result = `$gs -sDEVICE=bbox -dNOPAUSE -dBATCH $tempfile.pdf $pipe` ; + print $result if $Verbose ; $results .= "$result\n" } + +sub IdentifyCropBox + { RunTeX() ; + FetchPaperSize () ; + FindBoundingBox() } + +#D Just to be sure, we check if there is some image data, so +#D that we can retry if something went wrong. Unfortunately we cannot +#D safely check on a high res boundingbox. + +my $digits = '([\-\d\.]+)' ; + +sub ValidatedCropBox + { if ($result =~ /BoundingBox:\s*$digits\s+$digits\s+$digits\s+$digits\s*/mois) + { $llx = $1 ; $lly = $2 ; $urx = $3 ; $ury = $4 } + else + { print "Something is terribly wrong: no boundingbox:\n$result\n" ; exit } + $width = abs($urx - $llx) ; + $height = abs($ury - $lly) ; + if ($width&&$height) + { return 1 } + else + { unless ($width) + { print "Something seems wrong: no width\n" ; + $LeftCrop = "0cm" ; $RightCrop = "0cm" ; $Crop = "0cm" } + unless ($height) + { print "Something seems wrong: no height\n" ; + $TopCrop = "0cm" ; $BottomCrop = "0cm" ; $Crop = "0cm" } + return 0 } } + +#D This is the main cropping routine. + +sub FixCropBox + { RunTeX() } + +#D For error tracing we save the log information in a file. + +sub RenameResult + { unlink "$resultfile.pdf" ; + rename "$tempfile.pdf", "$resultfile.pdf" } + +sub SaveLogInfo + { open (LOG, ">$resultfile.log") ; + print LOG $results ; + close (LOG) } + +#D We remove all temporary files. + +sub CleanUp + { unless ($Verbose) + { unlink "$tempfile.tex" ; + unlink "$tempfile.tuo" ; + unlink "$tempfile.tui" ; + unlink "$figurefile.tmp" } } + +#D Here it all comes together. + +GetItRight() ; + +PrepareFirstPass() ; + +IdentifyCropBox () ; + +unless (ValidatedCropBox()) + { PrepareFirstPass() ; + IdentifyCropBox () } + +if (ValidatedCropBox()) + { PrepareSecondPass() ; + FixCropBox() } + +RenameResult() ; +SaveLogInfo() ; + +CleanUp () ; diff --git a/Master/texmf-dist/scripts/context/perl/texexec.pl b/Master/texmf-dist/scripts/context/perl/texexec.pl index 2f3b89e0461..fb564bbd5ed 100644 --- a/Master/texmf-dist/scripts/context/perl/texexec.pl +++ b/Master/texmf-dist/scripts/context/perl/texexec.pl @@ -11,7 +11,7 @@ eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' && eval 'exec perl -w - #D copyright={PRAGMA / Hans Hagen \& Ton Otten}] #C #C This module is part of the \CONTEXT\ macro||package and is -#C therefore copyrighted by \PRAGMA. See licen-en.pdf for +#C therefore copyrighted by \PRAGMA. See readme.pdf for #C details. # Thanks to Tobias Burnus for the german translations. @@ -151,6 +151,7 @@ my $PdfCombine = 0; my $PdfOpen = 0; my $PdfClose = 0; my $AutoPdf = 0; +my $UseXPdf = 0; my $PrintFormat = 'standard'; my $ProducePdfT = 0; my $ProducePdfM = 0; @@ -255,6 +256,7 @@ my $MakeMpy = ''; "once" => \$RunOnce, "output=s" => \$OutputFormat, "pages=s" => \$Pages, + "paper=s" => \$PaperFormat, "paperformat=s" => \$PaperFormat, "passon=s" => \$PassOn, "path=s" => \$InpPath, @@ -275,6 +277,7 @@ my $MakeMpy = ''; "selection=s" => \$Selection, "combination=s" => \$Combination, "noduplex" => \$NoDuplex, + "offset=s" => \$PaperOffset, "paperoffset=s" => \$PaperOffset, "backspace=s" => \$BackSpace, "topspace=s" => \$TopSpace, @@ -310,6 +313,7 @@ my $MakeMpy = ''; "pdfclose" => \$PdfClose, "pdfopen" => \$PdfOpen, "autopdf" => \$AutoPdf, + "xpdf" => \$UseXPdf, "modefile=s" => \$ModeFile, # additional modes file "globalfile" => \$GlobalFile, "nomapfiles" => \$NoMapFiles, @@ -351,6 +355,12 @@ if ( $ARGV[0] && $ARGV[0] =~ /\.mpx$/io ) { # catch -tex=.... bug in mpost #### +if ($Version) { + $Purge = 1 ; + } + +#### + if ($Paranoid) { $ENV{shell_escape} = 'f' ; $ENV{openout_any} = 'p' ; @@ -406,6 +416,16 @@ if ($AutoPdf) { $PdfOpen = $PdfClose = 1 ; } +my $PdfOpenCall = "" ; + +if ($PdfOpen) { + $PdfOpenCall = "pdfopen --file" ; +} + +if ($UseXPdf && !$dosish) { + $PdfOpenCall = "xpdfopen" ; +} + # this is our hook into paranoid path extensions, assumes that # these three vars are part of path specs in texmf.cnf @@ -900,7 +920,7 @@ $OutputFormats{dviwindo} = "dviwindo"; $OutputFormats{dviview} = "dviview"; -my @ConTeXtFormats = ( "nl", "en", "de", "cz", "uk", "it", "ro", "xx"); +my @ConTeXtFormats = ( "nl", "en", "de", "fr", "cz", "uk", "it", "ro", "xx"); sub SetInterfaces { my ( $short, $long, $full ) = @_; @@ -915,8 +935,9 @@ sub SetInterfaces { SetInterfaces( "nl", "dutch", "dutch" ); SetInterfaces( "en", "english", "english" ); SetInterfaces( "de", "german", "german" ); +SetInterfaces( "fr", "french", "french" ); SetInterfaces( "cz", "czech", "czech" ); -SetInterfaces( "uk", "brittish", "english" ); +SetInterfaces( "uk", "british", "english" ); SetInterfaces( "it", "italian", "italian" ); SetInterfaces( "no", "norwegian", "norwegian" ); SetInterfaces( "ro", "romanian", "romanian" ); @@ -1203,6 +1224,11 @@ sub MakeOptionFile { print OPT "\\setupcolors[\\c!split=$Separation]\n"; } if ($SetupPath) { print OPT "\\setupsystem[\\c!directory=\{$SetupPath\}]\n" } + if ($dosish) { + print OPT "\\setupsystem[\\c!type=mswin]\n" + } else { # no darwin handling in old texexec + print OPT "\\setupsystem[\\c!type=unix]\n" + } print OPT "\\setupsystem[\\c!n=$KindOfRun]\n"; $_ = $PaperFormat; #unless (($PdfArrange)||($PdfSelect)||($PdfCombine)||($PdfCopy)) @@ -1945,11 +1971,11 @@ sub RunConTeXtFile { { unlink "$JobName.$JobSuffix"; } - if ((!$Problems) && ($PdfOpen)) { + if ((!$Problems) && ($PdfOpen) && ($PdfOpenCall)) { if ($Result ne '') { - System("pdfopen --file $Result.pdf") if -f "$Result.pdf" + System("$PdfOpenCall $Result.pdf") if -f "$Result.pdf" } else { - System("pdfopen --file $JobName.pdf") if -f "$JobName.pdf" + System("$PdfOpenCall $JobName.pdf") if -f "$JobName.pdf" } } } @@ -2008,7 +2034,11 @@ sub checktexformatpath { if ($texformats eq '') { if ($UseEnginePath) { if ($dosish) { - $texformats = `kpsewhich --engine=$TeXExecutable --expand-var=\$TEXFORMATS` ; + if ( $TeXShell =~ /MikTeX/io ) { + $texformats = `kpsewhich --alias=$TeXExecutable --expand-var=\$TEXFORMATS` ; + } else { + $texformats = `kpsewhich --engine=$TeXExecutable --expand-var=\$TEXFORMATS` ; + } } else { $texformats = `kpsewhich --engine=$TeXExecutable --expand-var=\\\$TEXFORMATS` ; } @@ -2363,7 +2393,11 @@ sub LocatedFormatPath { # watch out $engine is lowercase in kpse # expanded paths print " assuming engine : $EnginePath\n"; if (($UseEnginePath)&&($EngineDone)) { - $FormatPath = `$kpsewhich --engine=$EnginePath --show-path=fmt` ; + if ( $TeXShell =~ /MikTeX/io ) { + $FormatPath = `$kpsewhich --alias=$EnginePath --show-path=fmt` ; + } else { + $FormatPath = `$kpsewhich --engine=$EnginePath --show-path=fmt` ; + } } else { $FormatPath = `$kpsewhich --show-path=fmt` ; } @@ -2375,7 +2409,11 @@ sub LocatedFormatPath { # watch out $engine is lowercase in kpse if ($FormatPath eq '') { if (($UseEnginePath)&&($EngineDone)) { if ($dosish) { - $FormatPath = `$kpsewhich --engine=$EnginePath --expand-path=\$TEXFORMATS` ; + if ( $TeXShell =~ /MikTeX/io ) { + $FormatPath = `$kpsewhich --alias=$EnginePath --expand-path=\$TEXFORMATS` ; + } else { + $FormatPath = `$kpsewhich --engine=$EnginePath --expand-path=\$TEXFORMATS` ; + } } else { $FormatPath = `$kpsewhich --engine=$EnginePath --expand-path=\\\$TEXFORMATS` ; } @@ -2998,7 +3036,7 @@ sub show_version_info { $contextversion = <IN>; close IN; } - $contextversion =~ s/.*contextversion\{([0-9.]+)\}.*/$1/s; + $contextversion =~ s/.*contextversion\{([0-9\.\:\s]+)\}.*/$1/s; } $EnterBatchMode = 1; $Format = 'cont-en'; @@ -3007,7 +3045,7 @@ sub show_version_info { print " tex : $texengine\n" ; print " context : ver: $contextversion\n" ; print " cont-en : $type\n" ; - foreach my $a (qw(cz de it nl ro uk xx)) { + foreach my $a (qw(cz de fr it nl ro uk xx)) { my $test = Pipe("$kpsewhich -format='fmt' cont-$a") ; if (defined $test && $test) { $Format = 'cont-' . $a; @@ -3063,6 +3101,9 @@ $TotalTime = time - $TotalTime; unless ($HelpAsked) { print "\n total run time : $TotalTime seconds\n" } +print "\n" ; +print " warning : use 'texmfstart texexec' instead\n" ; + if ($Problems) { exit 1 } __DATA__ @@ -3108,8 +3149,9 @@ interface user interface =en English =nl Dutch =de German +=fr French =cz Czech -=uk Brittish +=uk British =it Italian ----------- language main hyphenation language @@ -3136,7 +3178,7 @@ module typeset tex/pl/mp module ----------- mptex run an MetaPost plus btex-etex cycle ----------- -mpxtex generatet an MetaPostmpx file +mpxtex generate an MetaPost mpx file ----------- noarrange process but ignore arrange ----------- @@ -3149,8 +3191,8 @@ automprun MetaPost at runtime when needed once run TeX only once (no TeXUtil either) ----------- output specials to use -=pdftex Han The Than's pdf backend -=dvips Thomas Rokicky's dvi to ps converter +=pdftex Han The Thanh's pdf backend +=dvips Tomas Rokicky's dvi to ps converter =dvipsone YandY's dvi to ps converter =dviwindo YandY's windows previewer =dvipdfm Mark Wicks' dvi to pdf converter @@ -3243,7 +3285,7 @@ verbose shows some additional info ----------- help show this or more, e.g. '--help interface' ----------- -alone bypass utilities (e.g. fmtutil for non-standard fmt's) +alone bypass utilities (e.g. fmtutil for non-standard fmts) ----------- texutil force TeXUtil run ----------- diff --git a/Master/texmf-dist/scripts/context/perl/texexec.rme b/Master/texmf-dist/scripts/context/perl/texexec.rme index f38b4be7fe0..43f142f9da4 100644 --- a/Master/texmf-dist/scripts/context/perl/texexec.rme +++ b/Master/texmf-dist/scripts/context/perl/texexec.rme @@ -120,7 +120,7 @@ for miktex set TeXVirginFlag to --initialize for miktex set TeXPassString to --alias=context --translate-file=natural.tcx for miktex set TeXBatchFlag to --interaction=batchmode for miktex set TeXNonStopFlag to --interaction=nonstopmode -for miktex set MpToTeXExecutable to mptotex +for miktex set MpToTeXExecutable to mpto for miktex set MpVirginFlag to --initialize for miktex set MpPassString to --alias=mpost --translate-file=natural.tcx for miktex set MpBatchFlag to --interaction=batchmode diff --git a/Master/texmf-dist/scripts/context/perl/texshow.pl b/Master/texmf-dist/scripts/context/perl/texshow.pl index 79222c9f03c..629c28f99ec 100644 --- a/Master/texmf-dist/scripts/context/perl/texshow.pl +++ b/Master/texmf-dist/scripts/context/perl/texshow.pl @@ -3,7 +3,7 @@ eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' && eval 'exec perl -w - #D \module #D [ file=texshow.pl, -#D version=2005.01.06, +#D version=2006.08.04, #D title=TeXShow, #D subtitle=showing \CONTEXT\ commands, #D author=Taco Hoekwater, @@ -23,6 +23,12 @@ eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' && eval 'exec perl -w - #D one to be used. #D #D Hans Hagen - Januari 2005 +#D +#D ChangeLog: +#D \startitemize +#D \item Add keyboard bindings for quitting the app: Ctrl-q,Ctrl-x,Alt-F4 (2006/07/19) +#D \item Support for define --resolve (2006/08/04) +#D \stopitemize use strict; use Getopt::Long ; @@ -63,7 +69,7 @@ my %crosslinks; print "\n"; -show('TeXShow-XML 0.2 beta','Taco Hoekwater 2004',"/"); +show('TeXShow-XML 0.3 beta','Taco Hoekwater 2006',"/"); print "\n"; @@ -220,6 +226,11 @@ sub initialize_display { $mainwindow -> bind ( "<BackSpace>", sub { delete_request() } ) ; $mainwindow -> bind ( "<Prior>", sub { prev_command() } ) ; $mainwindow -> bind ( "<Next>", sub { next_command() } ) ; + $mainwindow -> bind ( "<Control-x>", sub { exit(0) } ) ; + $mainwindow -> bind ( "<Control-X>", sub { exit(0) } ) ; + $mainwindow -> bind ( "<Control-q>", sub { exit(0) } ) ; + $mainwindow -> bind ( "<Control-Q>", sub { exit(0) } ) ; + $mainwindow -> bind ( "<Alt-F4>", sub { exit(0) } ) ; } sub show { @@ -312,41 +323,50 @@ sub setups_found { sub load_setup { my ($path,$filename) = @_; + my $localdefs = {}; unless (keys %{$setups{$filename}}) { if (open(IN,"<${path}$filename.xml")) { my $position = 0 ; local $/ = '</cd:command>'; while (my $data= <IN>) { - if ($data =~ /\<\/cd:interface/) { - next; - } - if ($data =~ /\<cd:interface/) { - $data =~ s/^(.*?)\<cd:command/\<cd:command/sm; - my $meta = $1; - } - # - $data =~ s/\s*\n\s*//g; - $data =~ /\<cd:command(.*?)\>/; - my $info = $1; - my ($name,$environment) = ('',''); - while ($info =~ s/^\s*(.*?)\s*=\s*(["'])(.*?)\2\s*//) { - my $a = $1; my $b = $3; - if ($a eq 'name') { - $name = $b; - } elsif ($a eq 'type') { - $environment = $b; + next if $data =~ /\<\/cd:interface/; + if ($data =~ /\<cd:interface/) { + $data =~ s/^(.*?)\<cd:command/\<cd:command/sm; + my $meta = $1; + while ($meta =~ s!<cd:define name=(['"])(.*?)\1>(.*?)</cd:define>!!sm) { + my $localdef = $2; + my $localval = $3; + $localdefs->{$localdef} = $localval; + } + } + # + if (keys %$localdefs) { + while ($data =~ /<cd:resolve/) { + $data =~ s/<cd:resolve name=(['"])(.*?)\1\/>/$localdefs->{$2}/ms; + } + } + $data =~ s/\s*\n\s*//g; + $data =~ /\<cd:command(.*?)\>/; + my $info = $1; + my ($name,$environment) = ('',''); + while ($info =~ s/^\s*(.*?)\s*=\s*(["'])(.*?)\2\s*//) { + my $a = $1; my $b = $3; + if ($a eq 'name') { + $name = $b; + } elsif ($a eq 'type') { + $environment = $b; + } + } + my $cmd = $name; + if ($environment) { + $cmd = "start" . $name; + } + $setups {$filename}{$cmd} = $data ; + $trees {$filename}{$cmd} = undef; + $positions {$filename}{$cmd} = ++$position ; + $crosslinks{$filename}[$position] = $cmd ; } - } - my $cmd = $name; - if ($environment) { - $cmd = "start" . $name; - } - $setups {$filename}{$cmd} = $data ; - $trees {$filename}{$cmd} = undef; - $positions {$filename}{$cmd} = ++$position ; - $crosslinks{$filename}[$position] = $cmd ; - } - close IN; + close IN; # now get explanations as well ... my $explname = $filename; $explname =~ s/cont-/expl-/; diff --git a/Master/texmf-dist/scripts/context/perl/texutil.pl b/Master/texmf-dist/scripts/context/perl/texutil.pl index 0a0d2975354..ca364501584 100644 --- a/Master/texmf-dist/scripts/context/perl/texutil.pl +++ b/Master/texmf-dist/scripts/context/perl/texutil.pl @@ -44,7 +44,7 @@ eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $ #D binary version, like scanning illustrations other than \EPS. #D I would suggest to keep an eye on the version number: -$Program = "TeXUtil 9.0.0 - ConTeXt / PRAGMA ADE 1992-2004" ; +$Program = "TeXUtil 9.0.1 - ConTeXt / PRAGMA ADE 1992-2006" ; #D By the way, this is my first \PERL\ script, which means #D that it will be improved as soon as I find new and/or more @@ -54,6 +54,11 @@ $Program = "TeXUtil 9.0.0 - ConTeXt / PRAGMA ADE 1992-2004" ; #D users in english as well as some other languages. One can #D set his favourite language by saying something like: +#D This one has a real old date, 1992. This is because it is +#D a converted modula program that was used in the very early +#D days of our \TEX\ history (macros for proper inclusion of +#D graphics were among the first thatwe wrote). + #D \starttypen #D perl texutil.pl --int=de --fig *.eps *.tif *.pdf *.png *.jpg #D \stoptypen @@ -823,7 +828,7 @@ sub InitializeKeys { my $filename = $ARGV[0] ; return unless (open(TEX,"$filename.tex")) ; for ($i=0;$i<=255;$i++) - { @Filter[$i] = $i } + { $Filter[$i] = $i } if ($TcXPath eq '') { foreach (@paths) { my $p = checked_path($_) . 'kpsewhich' ; @@ -848,7 +853,7 @@ sub InitializeKeys { Report ("LoadedFilter", $Translation) ; while (<ASC>) { if (/^(\d+)\s*(\d+)/) - { @Filter[$2] = $1 } } + { $Filter[$2] = $1 } } close (ASC) } elsif ($TcXPath ne '') { Report ("WrongFilterPath", $TcXPath) } @@ -861,7 +866,7 @@ sub HandleKey { ++$SortN ; $RestOfLine =~ s/\{(.*)\}/$1/o ; my ($lan, $enc, $str, $chr, $map, $alf) = split(/\}\s*\{/, $RestOfLine) ; - if ($str =~ /^(\d+)/) { $str = ''.chr(@Filter[$1]) } + if ($str =~ /^(\d+)/) { $str = ''.chr($Filter[$1]) } $map = chr(ord($MAP[$i])+128) ; $STR[$SortN] = $str ; $CHR[$SortN] = $chr ; @@ -1279,8 +1284,8 @@ sub HandleRegister # the } { makes sure that local {} is ok # if ($Key eq "") { $Key = SanitizedString($Entry) } - if ($SortMethod ne '') - { $ProcessHigh = 0 } + # if ($SortMethod ne '') + # { $ProcessHigh = 0 } if ($ProcessHigh) { $Key = HighConverted($Key) } $KeyTag = substr $Key, 0, 1 ; @@ -2075,7 +2080,7 @@ sub SaveFigurePresets { $Figures[$NOfFigures] .= ",t=\{$FTit\}" } if ($FCre) { $Figures[$NOfFigures] .= ",c=\{$FCre\}" } - $Figures[$NOfFigures] .= ",s=$FSiz]\n" } } + $Figures[$NOfFigures] .= ",s=$FSiz]" } } #D The \EPS\ to \PDF\ conversion pipe to \GHOSTSCRIPT\ is #D inspired by a script posted by Sebastian Ratz at the @@ -2580,8 +2585,10 @@ sub FlushFigures open ( TUF, ">$OutputFile" ) ; print TUF "%\n" . "% $Program / Figures\n" . "%\n" ; print TUF "\\thisisfigureversion\{1996.06.01\}\n" . "%\n" ; + # a joins is nicer for ($n=1 ; $n<=$NOfFigures ; ++$n) - { print TUF $Figures[$n] } + { print TUF "$Figures[$n]%\n" } + print TUF "\\endinput"; close (TUF) ; if ($NOfFigures) { Report("OutputFile", $OutputFile ) } @@ -2772,7 +2779,9 @@ if ($PurgeAllFiles) { push @forsuresuffixes, @texnonesuffixes ; @texnonesuffixes = [] } sub PurgeFiles # no my in foreach - { my $pattern = $ARGV[0] ; my $strippedname, $basename ; + { my $pattern = $ARGV[0] ; + my $strippedname ; + my $basename ; my @files = () ; if ($pattern eq '') { $pattern = "*.*" ; @@ -2895,8 +2904,11 @@ elsif ($PurgeFiles ) { PurgeFiles } elsif ($PurgeAllFiles ) { PurgeFiles } elsif ($AnalyzeFile ) { AnalyzeFile } elsif ($FilterPages ) { FilterPages } -elsif ($SciteApi ) { GenerateSciteApi } elsif ($ProcessHelp ) { ShowHelpInfo } # redundant else { ShowHelpInfo } +print "\n" ; +print " remark : 'texutil' is now part of 'texexec'\n" ; +print " warning : use 'texmfstart texutil' instead\n" ; + #D So far. diff --git a/Master/texmf-dist/scripts/context/ruby/base/ctx.rb b/Master/texmf-dist/scripts/context/ruby/base/ctx.rb index 852c3f7046e..0baf5a88b1b 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/ctx.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/ctx.rb @@ -23,6 +23,8 @@ class CtxRunner attr_reader :environments, :modules, :filters + @@suffix = 'prep' + def initialize(jobname=nil,logger=nil) if @logger = logger then def report(str='') @@ -52,17 +54,42 @@ class CtxRunner end if not @ctxname then - report('provide ctx file') + report('no ctx file specified') return end - if not FileTest.file?(@ctxname) and defaultname and FileTest.file?(defaultname) then - @ctxname = defaultname - end - + # name can be kpse:res-make.ctx if not FileTest.file?(@ctxname) then - report('provide ctx file') - return + fullname, done = '', false + if @ctxname =~ /^kpse:/ then + begin + if fullname = Kpse.found(@ctxname.sub(/^kpse:/,'')) then + @ctxname, done = fullname, true + end + rescue + # should not happen + end + else + ['..','../..'].each do |path| + begin + fullname = File.join(path,@ctxname) + if FileTest.file?(fullname) then + @ctxname, done = fullname, true + end + rescue + # probably strange join + end + break if done + end + end + if ! done && defaultname && FileTest.file?(defaultname) then + report("using default ctxfile #{defaultname}") + @ctxname = defaultname + end + if not done then + report('no ctx file found') + return false + end end @xmldata = IO.read(@ctxname) @@ -101,16 +128,44 @@ class CtxRunner @modules << justtext(mod) end REXML::XPath.each(@xmldata.root,"/ctx:job/ctx:process/ctx:resources/ctx:filter") do |fil| - @filters << justtext(mod) + @filters << justtext(fil) + end + commands = Hash.new + REXML::XPath.each(@xmldata.root,"/ctx:job/ctx:preprocess/ctx:processors/ctx:processor") do |pre| + begin + commands[pre.attributes['name']] = pre + rescue + end + end + suffix = @@suffix + begin + suffix = REXML::XPath.match(@xmldata.root,"/ctx:job/ctx:preprocess/@suffix").to_s + rescue + suffix = @@suffix + else + if suffix && suffix.empty? then suffix = @@suffix end end REXML::XPath.each(@xmldata.root,"/ctx:job/ctx:preprocess/ctx:files") do |files| REXML::XPath.each(files,"ctx:file") do |pattern| preprocessor = pattern.attributes['processor'] if preprocessor and not preprocessor.empty? then + begin + variables['old'] = @jobname + variables['new'] = "" + REXML::XPath.each(pattern,"ctx:value") do |value| + if name = value.attributes['name'] then + substititute(value,variables[name.to_s]) + end + end + rescue + report('unable to resolve file pattern') + return + end pattern = justtext(pattern) Dir.glob(pattern).each do |oldfile| - newfile = "#{oldfile}.prep" + newfile = "#{oldfile}.#{suffix}" if File.needsupdate(oldfile,newfile) then + report("#{oldfile} needs preprocessing") begin File.delete(newfile) rescue @@ -118,11 +173,17 @@ class CtxRunner end # there can be a sequence of processors preprocessor.split(',').each do |pp| - if command = REXML::XPath.first(@xmldata.root,"/ctx:job/ctx:preprocess/ctx:processors/ctx:processor[@name='#{pp}']") then + if command = commands[pp] then # a lie: no <?xml ...?> command = REXML::Document.new(command.to_s) # don't infect original command = command.elements["ctx:processor"] - report("preprocessing #{oldfile} using #{pp}") + begin + if suf = command.attributes['suffix'] then + newfile = "#{oldfile}.#{suf}" + end + rescue + end + report("preprocessing #{oldfile} into #{newfile} using #{pp}") REXML::XPath.each(command,"ctx:old") do |value| replace(value,oldfile) end REXML::XPath.each(command,"ctx:new") do |value| replace(value,newfile) end variables['old'] = oldfile @@ -172,7 +233,8 @@ class CtxRunner log << "<?xml version='1.0' standalone='yes'?>\n\n" log << "<ctx:preplist>\n" @prepfiles.keys.sort.each do |prep| - log << "\t<ctx:prepfile done='#{yes_or_no(@prepfiles[prep])}'>#{File.basename(prep)}</ctx:prepfile>\n" + # log << "\t<ctx:prepfile done='#{yes_or_no(@prepfiles[prep])}'>#{File.basename(prep)}</ctx:prepfile>\n" + log << "\t<ctx:prepfile done='#{yes_or_no(@prepfiles[prep])}'>#{prep}</ctx:prepfile>\n" end log << "</ctx:preplist>\n" log.close @@ -196,17 +258,27 @@ class CtxRunner name = e.attributes.get_attribute(attribute).to_s name = e.text.to_s if name.empty? name.strip! if name - if name and not name.empty? and FileTest.file?(name) then - if f = File.open(name,'r') and i = REXML::Document.new(f) then - report("including ctx file #{name}") - REXML::XPath.each(i.root,"*") do |ii| - xmldata.root.insert_after(e,ii) - more = true + done = false + if name and not name.empty? then + ['.',File.dirname(@ctxname),'..','../..'].each do |path| + begin + fullname = if path == '.' then name else File.join(path,name) end + if FileTest.file?(fullname) then + if f = File.open(fullname,'r') and i = REXML::Document.new(f) then + report("including ctx file #{name}") + REXML::XPath.each(i.root,"*") do |ii| + xmldata.root.insert_after(e,ii) + more = true + end + end + done = true + end + rescue end end - else - report("no valid ctx inclusion file #{name}") + break if done end + report("no valid ctx inclusion file #{name}") unless done rescue Exception # skip this file ensure @@ -278,7 +350,10 @@ class CtxRunner when 'suffix' then if str =~ /^.*\.(.*?)$/o then $1 else '' end when 'nosuffix' then if str =~ /^(.*)\..*?$/o then $1 else str end when 'nopath' then if str =~ /^.*[\\\/](.*?)$/o then $1 else str end - else str + when 'full' then str + when 'complete' then str + when 'expand' then File.expand_path(str).gsub(/\\/,"/") + else str end end diff --git a/Master/texmf-dist/scripts/context/ruby/base/exa.rb b/Master/texmf-dist/scripts/context/ruby/base/exa.rb new file mode 100644 index 00000000000..fdc5b5093e1 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/exa.rb @@ -0,0 +1,406 @@ +# \setuplayout[width=3cm] +# +# tex.setup.setuplayout.width.[integer|real|dimension|string|key] +# tex.[mp]var.whatever.width.[integer|real|dimension|string|key] + +require 'ftools' +require 'md5' + +# this can become a lua thing + +# no .*? but 0-9a-z\:\. because other too slow (and greedy) + +class Hash + + def subset(pattern) + h = Hash.new + r = /^#{pattern.gsub('.','\.')}/ + self.keys.each do |k| + h[k] = self[k].dup if k =~ r + end + return h + end + +end + +module ExaEncrypt + + def ExaEncrypt.encrypt_base(logger, oldfilename, newfilename) + if FileTest.file?(oldfilename) then + logger.report("checking #{oldfilename}") if logger + if data = IO.read(oldfilename) then + done = false + # cfg file: + # + # banner : exa configuration file + # user : domain, name = password, projectlist + # + if data =~ /^\s*banner\s*\:\s*exa\s*configuration\s*file/ then + data.gsub!(/^(\s*user\s*\:\s*.+?\s*\,\s*.+?\s*\=\s*)(.+?)(\s*\,\s*.+\s*)$/) do + pre, password, post = $1, $2, $3 + unless password =~ /MD5:/i then + done = true + password = "MD5:" + MD5.new(password).hexdigest.upcase + end + "#{pre}#{password}#{post}" + end + else + data.gsub!(/<exa:password([^>]*?)>(.*?)<\/exa:password>/mois) do + attributes, password = $1, $2 + unless password =~ /^([0-9A-F][0-9A-F])+$/ then + done = true + password = MD5.new(password).hexdigest.upcase + attributes = " encryption='md5'#{attributes}" + end + "<exa:password#{attributes}>#{password}</exa:password>" + end + end + begin + File.open(newfilename,'w') do |f| + f.puts(data) + end + rescue + logger.report("#{newfilename} cannot be written") if logger + else + logger.report("#{oldfilename} encrypted into #{newfilename}") if done and logger + end + end + end + end + +end + +module ExaModes + + @@modefile = 'examodes.tex' + + @@request = /(<exa:request.*?)(>.*?<\/exa:request>)/mo + @@redone = /<exa:request[^>]*?texified=([\'\"])yes\1.*?>/mo + @@reload = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@recalc = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)([\.\:]calcmath)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@rename = /<(exa:variable)([^>]+?label\=)([\"\'])([0-9A-Za-z\-\.\:]+?)(\3[^\/]*?)>(.*?)<(\/exa:variable)>/mo + @@refile = /<(exa:filename|exa:filelist)>(.*?)<(\/\1)>/mo + + def ExaModes.cleanup_request(logger,filename='request.exa',modefile=@@modefile) + begin File.delete(filename+'.raw') ; rescue ; end + begin File.delete(modefile) ; rescue ; end + if FileTest.file?(filename) then + data, done = nil, false + begin + data = IO.read(filename) + rescue + data = nil + end + if data =~ @@request and data !~ @@redone then + data.gsub!(@@rename) do + done = true + '<' + $1 + $2 + $3 + $4 + $5 + '>' + + texifiedstr($4,$6) + + '<' + $7 + '>' + end + data.gsub!(@@refile) do + done = true + '<' + $1 + '>' + + cleanpath($2) + + '<' + $3 + '>' + end + data.gsub!(@@recalc) do + done = true + '<' + $1 + $2 + $3 + $4 + ":raw" + $6 + '>' + $7 + '<' + $8 + '>' + + '<' + $1 + $2 + $3 + $4 + $6 + '>' + + calculatortexmath($7,false) + + '<' + $8 + '>' + end + if done then + data.gsub!(@@request) do + $1 + " texified='yes'" + $2 + end + begin File.copy(filename, filename+'.raw') ; rescue ; end + begin + logger.report("rewriting #{filename}") if logger + File.open(filename,'w') do |f| + f.puts(data) + end + rescue + logger.report("#{filename} cannot be rewritten") if logger + end + end + else + logger.report("#{filename} is already ok") if logger + end + @variables = Hash.new + data.scan(@@reload) do + @variables[$4] = $5 + end + vars = @variables.subset('data.tex.var') + mpvars = @variables.subset('data.tex.mpvar') + modes = @variables.subset('data.tex.mode') + setups = @variables.subset('data.tex.setup') + if not (modes.empty? and setups.empty? and vars.empty? and mpvars.empty?) then + begin + File.open(modefile,'w') do |mod| + logger.report("saving modes and setups in #{modefile}") if logger + if not modes.empty? then + for key in modes.keys do + k = key.dup + k.gsub!(/\./,'-') + mod.puts("\\enablemode[#{k}-#{modes[key]}]\n") + if modes[key] =~ /(on|yes|start)/o then # ! ! ! ! ! + mod.puts("\\enablemode[#{k}]\n") + end + end + mod.puts("\n\\readfile{cont-mod}{}{}\n") + end + if not setups.empty? then + for key in setups.keys + if key =~ /^(.+?)\.(.+?)\.(.+?)$/o then + command, key, type, value = $1, $2, $3, setups[key] + value = cleanedup(key,type,value) + mod.puts("\\#{$1}[#{key}=#{value}]\n") + elsif key =~ /^(.+?)\.(.+?)$/o then + command, type, value = $1, $2, setups[key] + mod.puts("\\#{$1}[#{value}]\n") + end + end + end + savevaroptions(vars, 'setvariables', mod) + savevaroptions(mpvars,'setMPvariables',mod) + end + rescue + logger.report("#{modefile} cannot be saved") if logger + end + else + logger.report("#{modefile} is not created") if logger + end + end + end + + private + + def ExaModes.autoparenthesis(str) + if str =~ /[\+\-]/o then '[1]' + str + '[1]' else str end + end + + def ExaModes.cleanedup(key,type,value) + if type == 'dimension' then + unless value =~ /(cm|mm|in|bp|sp|pt|dd|em|ex)/o + value + 'pt' + else + value + end + elsif type == 'calcmath' then + '{' + calculatortexmath(value,true) + '}' + elsif type =~ /^filename|filelist$/ or key =~ /^filename|filelist$/ then + cleanpath(value) + else + value + end + end + + def ExaModes.cleanpath(str) + (str ||'').gsub(/\\/o,'/') + end + + def ExaModes.texifiedstr(key,val) + case key + when 'filename' then + cleanpath(val) + when 'filelist' then + cleanpath(val) + else + val + end + end + + def ExaModes.savevaroptions(vars,setvariables,mod) + if not vars.empty? then + for key in vars.keys do + # var.whatever.width.dimension.value + if key =~ /^(.+?)\.(.+?)\.(.+?)$/o then + tag, key, type, value = $1, $2, $3, vars[key] + value = cleanedup(key,type,value) + mod.puts("\\#{setvariables}[#{tag}][#{key}=#{value}]\n") + elsif key =~ /^(.+?)\.(.+?)$/o then + tag, key, value = $1, $2, vars[key] + mod.puts("\\#{setvariables}[#{tag}][#{key}=#{value}]\n") + end + end + end + end + + def ExaModes.calculatortexmath(str,tx=true) + if tx then + bdisp, edisp = "\\displaymath\{", "\}" + binln, einln = "\\inlinemath\{" , "\}" + egraf = "\\endgraf" + else + bdisp, edisp = "<displaytexmath>", "</displaytexmath>" + binln, einln = "<inlinetexmath>" , "</inlinetexmath>" + egraf = "<p/>" + end + str.gsub!(/\n\s*\n+/mois, "\\ENDGRAF ") + str.gsub!(/(\[\[)\s*(.*?)\s*(\]\])/mos) do + $1 + docalculatortexmath($2) + $3 + end + str.gsub!(/(\\ENDGRAF)+\s*(\[\[)\s*(.*?)\s*(\]\])/mois) do + $1 + bdisp + $3 + edisp + end + str.gsub!(/(\[\[)\s*(.*?)\s*(\]\])/o) do + binln + $2 + einln + end + str.gsub!(/\\ENDGRAF/mos, egraf) + str + end + + def ExaModes.docalculatortexmath(str) + str.gsub!(/\n/o) { ' ' } + str.gsub!(/\s+/o) { ' ' } + str.gsub!(/>/o) { '>' } + str.gsub!(/</o) { '<' } + str.gsub!(/&.*?;/o) { } + level = 0 + str.gsub!(/([\(\)])/o) do |chr| + if chr == '(' then + level = level + 1 + chr = '[' + level.to_s + ']' + elsif chr == ')' then + chr = '[' + level.to_s + ']' + level = level - 1 + end + chr + end + # ...E... + loop do + break unless str.gsub!(/([\d\.]+)E([\-\+]{0,1}[\d\.]+)/o) do + "\{\\SCINOT\{#{$1}\}\{#{$2}\}\}" + end + end + # ^-.. + loop do + break unless str.gsub!(/\^([\-\+]*\d+)/o) do + "\^\{#{$1}\}" + end + end + # ^(...) + loop do + break unless str.gsub!(/\^(\[\d+\])(.*?)\1/o) do + "\^\{#{$2}\}" + end + end + # 1/x^2 + loop do + break unless str.gsub!(/([\d\w\.]+)\/([\d\w\.]+)\^([\d\w\.]+)/o) do + "@\{#{$1}\}\{#{$2}\^\{#{$3}\}\}" + end + end + # int(a,b,c) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?),(.*?),(.*?)\2/o) do + "\\#{$1.upcase}\^\{#{$4}\}\_\{#{$5}\}\{#{autoparenthesis($3)}\}" + end + end + # int(a,b) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?),(.*?)\2/o) do + "\\#{$1.upcase}\_\{#{$4}\}\{#{autoparenthesis($3)}\}" + end + end + # int(a) + loop do + break unless str.gsub!(/(int|sum|prod)(\[\d+\])(.*?)\2/o) do + "\\#{$1.upcase}\{#{autoparenthesis($3)}\}" + end + end + # sin(x) => {\sin(x)} + loop do + break unless str.gsub!(/(median|min|max|round|sqrt|sin|cos|tan|sinh|cosh|tanh|ln|log)\s*(\[\d+\])(.*?)\2/o) do + "\{\\#{$1.upcase}\{#{$2}#{$3}#{$2}\}\}" + end + end + # mean + str.gsub!(/(mean)(\[\d+\])(.*?)\2/o) do + "\{\\OVERLINE\{#{$3}\}\}" + end + # sin x => {\sin(x)} + # ... + # (1+x)/(1+x) => \frac{1+x}{1+x} + loop do + break unless str.gsub!(/(\[\d+\])(.*?)\1\/(\[\d+\])(.*?)\3/o) do + "@\{#{$2}\}\{#{$4}\}" + end + end + # (1+x)/x => \frac{1+x}{x} + loop do + break unless str.gsub!(/(\[\d+\])(.*?)\1\/([a-zA-Z0-9]+)/o) do + "@\{#{$2}\}\{#{$3}\}" + end + end + # 1/(1+x) => \frac{1}{1+x} + loop do + break unless str.gsub!(/([a-zA-Z0-9]+)\/(\[\d+\])(.*?)\2/o) do + "@\{#{$1}\}\{#{$3}\}" + end + end + # 1/x => \frac{1}{x} + loop do + break unless str.gsub!(/([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)/o) do + "@\{#{$1}\}\{#{$2}\}" + end + end + # + str.gsub!(/\@/o) do + "\\FRAC " + end + str.gsub!(/\*/o) do + " " + end + str.gsub!(/\<\=/o) do + "\\LE " + end + str.gsub!(/\>\=/o) do + "\\GE " + end + str.gsub!(/\=/o) do + "\\EQ " + end + str.gsub!(/\</o) do + "\\LT " + end + str.gsub!(/\>/) do + "\\GT " + end + str.gsub!(/(D)(\[\d+\])(.*?)\2/o) do + "\{\\FRAC\{\\MBOX{d}\}\{\\MBOX{d}x\}\{#{$2}#{$3}#{$2}\}\}" + end + str.gsub!(/(exp)(\[\d+\])(.*?)\2/o) do + "\{e^\{#{$3}\}\}" + end + str.gsub!(/(abs)(\[\d+\])(.*?)\2/o) do + "\{\\left\|#{$3}\\right\|\}" + end + str.gsub!(/D([x|y])/o) do + "\\FRAC\{\{\\rm d\}#{$1}\}\{\{\\rm d\}x\}" + end + str.gsub!(/D([f|g])(\[\d+\])(.*?)\2/o) do + "\{\\rm #{$1}\}'#{$2}#{$3}#{$2}" + end + str.gsub!(/([f|g])(\[\d+\])(.*?)\2/o) do + "\{\\rm #{$1}\}#{$2}#{$3}#{$2}" + end + str.gsub!(/(pi|inf)/io) do + "\\#{$1} " + end + loop do + break unless str.gsub!(/(\[\d+?\])(.*?)\1/o) do + "\\left(#{$2}\\right)" + end + end + str.gsub!(/\\([A-Z]+?)([\s\{\^\_\\])/io) do + "\\#{$1.downcase}#{$2}" + end + str + end + +end + +# ExaModes.cleanup_request() diff --git a/Master/texmf-dist/scripts/context/ruby/base/file.rb b/Master/texmf-dist/scripts/context/ruby/base/file.rb index 42fb346c409..39bb7d46731 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/file.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/file.rb @@ -105,12 +105,13 @@ class File end def File.silentdelete(filename) - begin File.delete(filename) ; rescue ; end + File.delete(filename) rescue false end def File.silentcopy(oldname,newname) return if File.expand_path(oldname) == File.expand_path(newname) - begin File.copy(oldname,newname) ; rescue ; end + File.makedirs(File.dirname(newname)) rescue false + File.copy(oldname,newname) rescue false end def File.silentrename(oldname,newname) @@ -118,11 +119,12 @@ class File # maybe working over multiple file systems or # apps may have mildly locked files (like gs does) return if File.expand_path(oldname) == File.expand_path(newname) - begin File.delete(newname) ; rescue ; end + File.delete(newname) rescue false begin File.rename(oldname,newname) rescue - begin File.copy(oldname,newname) ; rescue ; end + File.makedirs(File.dirname(newname)) rescue false + File.copy(oldname,newname) rescue false end end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpse.rb b/Master/texmf-dist/scripts/context/ruby/base/kpse.rb index dc4898ffc58..b0321672fc1 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/kpse.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/kpse.rb @@ -18,6 +18,26 @@ require 'rbconfig' # # miktex has mem|fmt|base paths +class String + + def split_path + if self =~ /\;/o || self =~ /^[a-z]\:/io then + self.split(";") + else + self.split(":") + end + end + +end + +class Array + + def join_path + self.join(File::PATH_SEPARATOR) + end + +end + module Kpse @@located = Hash.new @@ -32,11 +52,21 @@ module Kpse @@crossover = true @@mswindows = Config::CONFIG['host_os'] =~ /mswin/ - @@distribution = 'miktex' if ENV['PATH'] =~ /miktex[\\\/]bin/o + # @@distribution = 'miktex' if ENV['PATH'] =~ /miktex[\\\/]bin/o - @@usekpserunner = false || ENV['KPSEFAST'] == 'yes' + if ENV['PATH'] =~ /(.*?)miktex[\\\/]bin/i then + @@distribution = 'miktex' unless $1 =~ /(texmf\-mswin[\/\\]bin|bin[\/\\]win32)/i + end + + @@re_true = /yes|on|true|1/i - require 'base/tool' if @@usekpserunner + if (ENV['KPSEFAST'] =~ @@re_true) || (ENV['CTXMINIMAL'] =~ @@re_true) then + @@usekpserunner = true + require 'base/kpsefast' + require 'base/kpserunner' + else + @@usekpserunner = false + end if @@crossover then ENV.keys.each do |k| @@ -113,12 +143,10 @@ module Kpse begin if @@problems then results = '' + elsif @@usekpserunner then + results = KpseRunner.kpsewhich(arguments).chomp else - if @@usekpserunner then - results = KpseRunner.kpsewhich(arguments).chomp - else - results = `kpsewhich #{arguments}`.chomp - end + results = `kpsewhich #{arguments}`.chomp end rescue puts "unable to run kpsewhich" if @@tracing @@ -132,7 +160,7 @@ module Kpse # maybe we should check for writeability unless @@paths.key?('formatpaths') then begin - setpath('formatpaths',run("--show-path=fmt").gsub(/\\/,'/').split(File::PATH_SEPARATOR)) + setpath('formatpaths',run("--show-path=fmt").gsub(/\\/,'/').split_path) rescue setpath('formatpaths',[]) end @@ -189,30 +217,51 @@ module Kpse end # locate writable path if ! formatpath.empty? then - formatpath.split(File::PATH_SEPARATOR).each do |fp| - fp.gsub!(/\\/,'/') + formatpaths, done = formatpath.split_path, false + formatpaths.collect! do |fp| + fp.gsub!(/\\/o,'/') + fp.gsub!(/\/\/$/o,'/') # remove funny patterns - fp.sub!(/^!!/,'') - fp.sub!(/\/+$/,'') - fp.sub!(/unsetengine/,if enginepath then engine else '' end) - if ! fp.empty? && (fp != '.') then - # strip (possible engine) and test for writeability - fpp = fp.sub(/#{engine}\/*$/,'') + fp.sub!(/^!!/o,'') + fp.sub!(/\/+$/o,'') + fp.sub!(/(unsetengine|unset)/o,if enginepath then engine else '' end) + fp + end + formatpaths.delete_if do |fp| + fp.empty? || fp == '.' + end + # the engine path may not yet be present, find first writable + formatpaths.each do |fp| + # strip (possible engine) and test for writeability + fpp = fp.sub(/#{engine}\/*$/o,'') + if FileTest.directory?(fpp) && FileTest.writable?(fpp) then + # use this path + formatpath, done = fp.dup, true + break + end + end + unless done then + formatpaths.each do |fp| + fpp = fp.sub(/#{engine}\/*$/o,'') + File.makedirs(fpp) rescue false # maybe we don't have an path yet if FileTest.directory?(fpp) && FileTest.writable?(fpp) then # use this path - formatpath = fp.dup + formatpath, done = fp.dup, true break end end end + unless done then + formatpath = '.' + end end # needed ! - begin File.makedirs(formatpath) ; rescue ; end ; + File.makedirs(formatpath) rescue false # fall back to current path formatpath = '.' if formatpath.empty? || ! FileTest.writable?(formatpath) # append engine but prevent duplicates formatpath = File.join(formatpath.sub(/\/*#{engine}\/*$/,''), engine) if enginepath - begin File.makedirs(formatpath) ; rescue ; end ; + File.makedirs(formatpath) rescue false setpath(engine,formatpath) # ENV['engine'] = savedengine end @@ -283,6 +332,10 @@ module Kpse `#{cmd}` end + def Kpse.searchmethod + if @@usekpserunner then 'kpsefast' else 'kpsewhich' end + end + private def Kpse.setvariable(key,value) diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpse/drb.rb b/Master/texmf-dist/scripts/context/ruby/base/kpse/drb.rb new file mode 100644 index 00000000000..db1ce0eec87 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpse/drb.rb @@ -0,0 +1,57 @@ +require 'drb' +require 'base/kpse/trees' + +class KpseServer + + attr_accessor :port + + def initialize(port=7000) + @port = port + end + + def start + puts "starting drb service at port #{@port}" + DRb.start_service("druby://localhost:#{@port}", KpseTrees.new) + trap(:INT) do + DRb.stop_service + end + DRb.thread.join + end + + def stop + # todo + end + +end + +class KpseClient + + attr_accessor :port + + def initialize(port=7000) + @port = port + @kpse = nil + end + + def start + # only needed when callbacks are used / slow, due to Socket::getaddrinfo + # DRb.start_service + end + + def object + @kpse = DRbObject.new(nil,"druby://localhost:#{@port}") + end + +end + + +# SERVER_URI="druby://localhost:8787" +# +# # Start a local DRbServer to handle callbacks. +# # +# # Not necessary for this small example, but will be required +# # as soon as we pass a non-marshallable object as an argument +# # to a dRuby call. +# DRb.start_service +# +# timeserver = DRbObject.new_with_uri(SERVER_URI) diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpse/soap.rb b/Master/texmf-dist/scripts/context/ruby/base/kpse/soap.rb new file mode 100644 index 00000000000..c9ed75c446b --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpse/soap.rb @@ -0,0 +1,79 @@ +require 'soap/rpc/standaloneServer' +require 'soap/rpc/driver' + +require 'base/kpse/trees' + +class KpseService < SOAP::RPC::StandaloneServer + + def on_init + kpse = KpseTrees.new + add_method(kpse, 'choose', 'files', 'environment') + add_method(kpse, 'load', 'files', 'environment') + add_method(kpse, 'expand_variables', 'tree') + add_method(kpse, 'expand_braces', 'tree', 'str') + add_method(kpse, 'expand_path', 'tree', 'str') + add_method(kpse, 'expand_var', 'tree', 'str') + add_method(kpse, 'show_path', 'tree', 'str') + add_method(kpse, 'var_value', 'tree', 'str') + add_method(kpse, 'find_file', 'tree', 'filename') + add_method(kpse, 'find_files', 'tree', 'filename', 'first') + end + +end + +class KpseServer + + @@url = 'http://kpse.thismachine.org/KpseService' + + attr_accessor :port + + def initialize(port=7000) + @port = port + @server = nil + end + + def start + puts "starting soap service at port #{@port}" + @server = KpseService.new('KpseServer', @@url, '0.0.0.0', @port.to_i) + trap(:INT) do + @server.shutdown + end + status = @server.start + end + + def stop + @server.shutdown rescue false + end + +end + +class KpseClient + + @@url = 'http://kpse.thismachine.org/KpseService' + + attr_accessor :port + + def initialize(port=7000) + @port = port + @kpse = nil + end + + def start + @kpse = SOAP::RPC::Driver.new("http://localhost:#{port}/", @@url) + @kpse.add_method('choose','files', 'environment') + @kpse.add_method('load','files', 'environment') + @kpse.add_method('expand_variables', 'tree') + @kpse.add_method('expand_braces', 'tree', 'str') + @kpse.add_method('expand_path', 'tree', 'str') + @kpse.add_method('expand_var', 'tree', 'str') + @kpse.add_method('show_path', 'tree', 'str') + @kpse.add_method('var_value', 'tree', 'str') + @kpse.add_method('find_file', 'tree', 'filename') + @kpse.add_method('find_files', 'tree', 'filename', 'first') + end + + def object + @kpse + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpse/trees.rb b/Master/texmf-dist/scripts/context/ruby/base/kpse/trees.rb new file mode 100644 index 00000000000..9c872eb18c1 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpse/trees.rb @@ -0,0 +1,84 @@ +require 'monitor' +require 'base/kpsefast' + +class KpseTrees < Monitor + + def initialize + @trees = Hash.new + end + + def pattern(filenames) + filenames.join('|').gsub(/\\+/o,'/').downcase + end + + def choose(filenames,environment) + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "enabling tree #{current}" + current + end + + def fetch(filenames,environment) # will send whole object ! + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "fetching tree #{current}" + @trees[current] + end + + def load(filenames,environment) + current = pattern(filenames) + puts "loading tree #{current}" + @trees[current] = KpseFast.new + @trees[current].push_environment(environment) + @trees[current].load_cnf(filenames) + @trees[current].expand_variables + @trees[current].load_lsr + end + + def set(tree,key,value) + case key + when 'progname' then @trees[tree].progname = value + when 'engine' then @trees[tree].engine = value + when 'format' then @trees[tree].format = value + end + end + def get(tree,key) + case key + when 'progname' then @trees[tree].progname + when 'engine' then @trees[tree].engine + when 'format' then @trees[tree].format + end + end + + def load_cnf(tree) + @trees[tree].load_cnf + end + def load_lsr(tree) + @trees[tree].load_lsr + end + def expand_variables(tree) + @trees[tree].expand_variables + end + def expand_braces(tree,str) + @trees[tree].expand_braces(str) + end + def expand_path(tree,str) + @trees[tree].expand_path(str) + end + def expand_var(tree,str) + @trees[tree].expand_var(str) + end + def show_path(tree,str) + @trees[tree].show_path(str) + end + def var_value(tree,str) + @trees[tree].var_value(str) + end + def find_file(tree,filename) + @trees[tree].find_file(filename) + end + def find_files(tree,filename,first) + @trees[tree].find_files(filename,first) + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpsedirect.rb b/Master/texmf-dist/scripts/context/ruby/base/kpsedirect.rb new file mode 100644 index 00000000000..6fa8c8601a9 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpsedirect.rb @@ -0,0 +1,34 @@ +class KpseDirect + + attr_accessor :progname, :format, :engine + + def initialize + @progname, @format, @engine = '', '', '' + end + + def expand_path(str) + clean_name(`kpsewhich -expand-path=#{str}`.chomp) + end + + def expand_var(str) + clean_name(`kpsewhich -expand-var=#{str}`.chomp) + end + + def find_file(str) + clean_name(`kpsewhich #{_progname_} #{_format_} #{str}`.chomp) + end + + def _progname_ + if @progname.empty? then '' else "-progname=#{@progname}" end + end + def _format_ + if @format.empty? then '' else "-format=\"#{@format}\"" end + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpsefast.rb b/Master/texmf-dist/scripts/context/ruby/base/kpsefast.rb index 52ab28d0fe7..20c07c70a4e 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/kpsefast.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/kpsefast.rb @@ -10,6 +10,26 @@ # todo: multiple cnf files +class String + + def split_path + if self =~ /\;/o || self =~ /^[a-z]\:/io then + self.split(";") + else + self.split(":") + end + end + +end + +class Array + + def join_path + self.join(File::PATH_SEPARATOR) + end + +end + class File def File.locate_file(path,name) @@ -37,7 +57,70 @@ class File end -class KPSEFAST +module KpseUtil + + # to be adapted, see loading cnf file + + @@texmftrees = ['texmf-local','texmf.local','../..','texmf'] # '../..' is for gwtex + @@texmfcnf = 'texmf.cnf' + + def KpseUtil::identify + # we mainly need to identify the local tex stuff and wse assume that + # the texmfcnf variable is set; otherwise we need to expand the + # TEXMF variable and that takes time since it may involve more + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + ENV['SELFAUTOPARENT'] = ownpath + else + ENV['SELFAUTOPARENT'] = '.' # fall back + # may be too tricky: + # + # (ENV['PATH'] ||'').split_path.each do |p| + # if p.gsub!(/texmf.*?$/o, '') then + # ENV['SELFAUTOPARENT'] = p + # break + # end + # end + end + filenames = Array.new + if ENV['TEXMFCNF'] && ! ENV['TEXMFCNF'].empty? then + ENV['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif ENV['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + @@texmftrees.each do |tree| + filenames << File.join(ENV['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end + end + loop do + busy = false + filenames.collect! do |f| + f.gsub(/\$([a-zA-Z0-9\_\-]+)/o) do + if (! ENV[$1]) || (ENV[$1] == $1) then + "$#{$1}" + else + busy = true + ENV[$1] + end + end + end + break unless busy + end + filenames.delete_if do |f| + ! FileTest.file?(f) + end + return filenames + end + + def KpseUtil::environment + Hash.new.merge(ENV) + end + +end + +class KpseFast # formats are an incredible inconsistent mess @@ -45,6 +128,8 @@ class KPSEFAST @@formats = Hash.new @@suffixmap = Hash.new + @@texmfcnf = 'texmf.cnf' + @@suffixes['gf'] = ['.<resolution>gf'] # todo @@suffixes['pk'] = ['.<resolution>pk'] # todo @@suffixes['tfm'] = ['.tfm'] @@ -121,7 +206,7 @@ class KPSEFAST @@formats['bst'] = '' @@formats['cnf'] = '' @@formats['ls-R'] = '' - @@formats['fmt'] = '' + @@formats['fmt'] = 'TEXFORMATS' @@formats['map'] = 'TEXFONTMAPS' @@formats['mem'] = 'MPMEMS' @@formats['mf'] = 'MFINPUTS' @@ -170,27 +255,41 @@ class KPSEFAST @@cacheversion = '1' def initialize - @rootpath = '' - @treepath = '' - @progname = 'kpsewhich' - @engine = 'pdfetex' - @variables = Hash.new - @expansions = Hash.new - @files = Hash.new - @found = Hash.new - @kpsevars = Hash.new - @lsrfiles = Array.new - @cnffiles = Array.new - @verbose = true - @remember = true - @scandisk = true - @diskcache = true - @renewcache = false - @isolate = false - - @diskcache = false - @cachepath = nil - @cachefile = 'tmftools.log' + @rootpath = '' + @treepath = '' + @progname = 'kpsewhich' + @engine = 'pdfetex' + @variables = Hash.new + @expansions = Hash.new + @files = Hash.new + @found = Hash.new + @kpsevars = Hash.new + @lsrfiles = Array.new + @cnffiles = Array.new + @verbose = true + @remember = true + @scandisk = true + @diskcache = true + @renewcache = false + @isolate = false + + @diskcache = false + @cachepath = nil + @cachefile = 'tmftools.log' + + @environment = ENV + end + + def set(key,value) + case key + when 'progname' then @progname = value + when 'engine' then @engine = value + when 'format' then @format = value + end + end + + def push_environment(env) + @environment = env end # {$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c} @@ -201,36 +300,42 @@ class KPSEFAST # # since we live in scriptpath we need a slightly different method - def load_cnf - ownpath = File.expand_path($0) - if ownpath.gsub!(/texmf.*?$/o, '') then - ENV['SELFAUTOPARENT'] = ownpath - else - ENV['SELFAUTOPARENT'] = '.' - end - unless @treepath.empty? then + def load_cnf(filenames=nil) + unless filenames then + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + @environment['SELFAUTOPARENT'] = ownpath + else + @environment['SELFAUTOPARENT'] = '.' + end + unless @treepath.empty? then + unless @rootpath.empty? then + @treepath = @treepath.split(',').collect do |p| File.join(@rootpath,p) end.join(',') + end + @environment['TEXMF'] = @treepath + # only the first one + @environment['TEXMFCNF'] = File.join(@treepath.split(',').first,'texmf/web2c') + end unless @rootpath.empty? then - @treepath = @treepath.split(',').collect do |p| File.join(@rootpath,p) end.join(',') + @environment['TEXMFCNF'] = File.join(@rootpath,'texmf/web2c') + @environment['SELFAUTOPARENT'] = @rootpath + @isolate = true end - ENV['TEXMF'] = @treepath - ENV['TEXMFCNF'] = File.join(@treepath.split(',').first,'texmf/web2c') - end - unless @rootpath.empty? then - ENV['TEXMFCNF'] = File.join(@rootpath,'texmf/web2c') - ENV['SELFAUTOPARENT'] = @rootpath - @isolate = true - end - filenames = Array.new - if ENV['TEXMFCNF'] and not ENV['TEXMFCNF'].empty? then - filenames << File.join(ENV['TEXMFCNF'],'texmf.cnf') - elsif ENV['SELFAUTOPARENT'] == '.' then - filenames << File.join('.','texmf.cnf') - else - ['texmf-local','texmf'].each do |tree| - filenames << File.join(ENV['SELFAUTOPARENT'],tree,'web2c','texmf.cnf') + filenames = Array.new + if @environment['TEXMFCNF'] and not @environment['TEXMFCNF'].empty? then + @environment['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif @environment['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + ['texmf-local','texmf'].each do |tree| + filenames << File.join(@environment['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end end end # <root>/texmf/web2c/texmf.cnf + filenames = _expanded_path_(filenames) @rootpath = filenames.first 3.times do @rootpath = File.dirname(@rootpath) @@ -280,9 +385,9 @@ class KPSEFAST @files = Hash.new if @diskcache then ['HOME','TEMP','TMP','TMPDIR'].each do |key| - if ENV[key] then - if FileTest.directory?(ENV[key]) then - @cachepath = ENV[key] + if @environment[key] then + if FileTest.directory?(@environment[key]) then + @cachepath = @environment[key] @cachefile = [@rootpath.gsub(/[^A-Z0-9]/io, '-').gsub(/\-+/,'-'),File.basename(@cachefile)].join('-') break end @@ -342,14 +447,14 @@ class KPSEFAST def expand_variables @expansions = Hash.new if @isolate then - @variables['TEXMFCNF'] = ENV['TEXMFCNF'].dup - @variables['SELFAUTOPARENT'] = ENV['SELFAUTOPARENT'].dup + @variables['TEXMFCNF'] = @environment['TEXMFCNF'].dup + @variables['SELFAUTOPARENT'] = @environment['SELFAUTOPARENT'].dup else - ENV.keys.each do |e| + @environment.keys.each do |e| if e =~ /^([a-zA-Z]+)\_(.*)\s*$/o then - @expansions["#{$1}.#{$2}"] = ENV[e].dup + @expansions["#{$1}.#{$2}"] = (@environment[e] ||'').dup else - @expansions[e] = ENV[e].dup + @expansions[e] = (@environment[e] ||'').dup end end end @@ -465,7 +570,8 @@ class KPSEFAST end end pathlist = pathlist.uniq.collect do |path| - p = path.gsub(/^\/+/o) do '' end + p = path + # p.gsub(/^\/+/o) do '' end # p.gsub!(/(.)\/\/(.)/o) do "#{$1}/#{$2}" end # p.gsub!(/\/\/+$/o) do '//' end p.gsub!(/\/\/+/o) do '//' end @@ -485,8 +591,8 @@ class KPSEFAST end def var_of_format_or_suffix(str) - if @@formats.key?(@format) then - @@formats[@format] + if @@formats.key?(str) then + @@formats[str] elsif @@suffixmap.key?(File.extname(str)) then # extname includes . @@formats[@@suffixmap[File.extname(str)]] # extname includes . else @@ -496,7 +602,7 @@ class KPSEFAST end -class KPSEFAST +class KpseFast # test things @@ -536,23 +642,23 @@ class KPSEFAST puts "" puts @variables ["KPSE_TEST_PATTERN_#{i}"] puts "" - puts expand_path("KPSE_TEST_PATTERN_#{i}").split(File::PATH_SEPARATOR) + puts expand_path("KPSE_TEST_PATTERN_#{i}").split_path puts "" end end end -class KPSEFAST +class KpseFast # kpse stuff def expand_braces(str) # output variable and brace expansion of STRING. - _expanded_path_(original_variable(str).split(";")).join(File::PATH_SEPARATOR) + _expanded_path_(original_variable(str).split_path).join_path end def expand_path(str) # output complete path expansion of STRING. - _expanded_path_(expanded_variable(str).split(";")).join(File::PATH_SEPARATOR) + _expanded_path_(expanded_variable(str).split_path).join_path end def expand_var(str) # output variable expansion of STRING. @@ -560,7 +666,7 @@ class KPSEFAST end def show_path(str) # output search path for file type NAME - expanded_path(var_of_format(str)).join(File::PATH_SEPARATOR) + expanded_path(str).join_path end def var_value(str) # output the value of variable $STRING. @@ -569,97 +675,117 @@ class KPSEFAST end -class KPSEFAST +class KpseFast + + def _is_cnf_?(filename) + filename == File.basename((@cnffiles.first rescue @@texmfcnf) || @@texmfcnf) + end def find_file(filename) - find_files(filename,true) + if _is_cnf_?(filename) then + @cnffiles.first rescue '' + else + [find_files(filename,true)].flatten.first || '' + end end def find_files(filename,first=false) - if @remember then - stamp = "#{filename}--#{@format}--#{@engine}--#{@progname}" - return @found[stamp] if @found.key?(stamp) - end - pathlist = expanded_path(filename) - result = [] - filelist = if @files.key?(filename) then @files[filename].uniq else nil end - done = false - pathlist.each do |path| - doscan = if path =~ /^\!\!/o then false else true end - recurse = if path =~ /\/\/$/o then true else false end - pathname = path.dup - pathname.gsub!(/^\!+/o, '') + if _is_cnf_?(filename) then + result = @cnffiles.dup + else + if @remember then + # stamp = "#{filename}--#{@format}--#{@engine}--#{@progname}" + stamp = "#{filename}--#{@engine}--#{@progname}" + return @found[stamp] if @found.key?(stamp) + end + pathlist = expanded_path(filename) + result = [] + filelist = if @files.key?(filename) then @files[filename].uniq else nil end done = false - if not done and filelist then - # checking for exact match - if filelist.include?(pathname) then - result << pathname + if pathlist.size == 0 then + if FileTest.file?(filename) then done = true + result << '.' end - if not done and recurse then - # checking for fuzzy // - pathname.gsub!(/\/+$/o, '/.*') - # pathname.gsub!(/\/\//o,'/[\/]*/') - pathname.gsub!(/\/\//o,'/.*?/') - re = /^#{pathname}/ - filelist.each do |f| - if re =~ f then - result << f # duplicates will be filtered later + else + pathlist.each do |path| + doscan = if path =~ /^\!\!/o then false else true end + recurse = if path =~ /\/\/$/o then true else false end + pathname = path.dup + pathname.gsub!(/^\!+/o, '') + done = false + if not done and filelist then + # checking for exact match + if filelist.include?(pathname) then + result << pathname done = true end - break if done + if not done and recurse then + # checking for fuzzy // + pathname.gsub!(/\/+$/o, '/.*') + # pathname.gsub!(/\/\//o,'/[\/]*/') + pathname.gsub!(/\/\//o,'/.*?/') + re = /^#{pathname}/ + filelist.each do |f| + if re =~ f then + result << f # duplicates will be filtered later + done = true + end + break if done + end + end end + if not done and doscan then + # checking for path itself + pname = pathname.sub(/\.\*$/,'') + if not pname =~ /\*/o and FileTest.file?(File.join(pname,filename)) then + result << pname + done = true + end + end + break if done and first end end - if not done and doscan then - # checking for path itself - pname = pathname.sub(/\.\*$/,'') - if not pname =~ /\*/o and FileTest.file?(File.join(pname,filename)) then - result << pname - done = true - end - end - break if done and first - end - if not done and @scandisk then - pathlist.each do |path| - pathname = path.dup - unless pathname.gsub!(/^\!+/o, '') then # !! prevents scan - recurse = pathname.gsub!(/\/+$/o, '') - complex = pathname.gsub!(/\/\//o,'/*/') - if recurse then - if complex then - if ok = File.glob_file("#{pathname}/**/#{filename}") then + if not done and @scandisk then + pathlist.each do |path| + pathname = path.dup + unless pathname.gsub!(/^\!+/o, '') then # !! prevents scan + recurse = pathname.gsub!(/\/+$/o, '') + complex = pathname.gsub!(/\/\//o,'/*/') + if recurse then + if complex then + if ok = File.glob_file("#{pathname}/**/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif ok = File.locate_file(pathname,filename) then result << File.dirname(ok) done = true end - elsif ok = File.locate_file(pathname,filename) then - result << File.dirname(ok) - done = true - end - elsif complex then - if ok = File.glob_file("#{pathname}/#{filename}") then - result << File.dirname(ok) + elsif complex then + if ok = File.glob_file("#{pathname}/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif FileTest.file?(File.join(pathname,filename)) then + result << pathname done = true end - elsif FileTest.file?(File.join(pathname,filename)) then - result << pathname - done = true + break if done and first end - break if done and first end end + result = result.uniq.collect do |pathname| + File.join(pathname,filename) + end + @found[stamp] = result if @remember end - result = result.uniq.collect do |pathname| - File.join(pathname,filename) - end - @found[stamp] = result if @remember return result # redundant end end -class KPSEFAST +class KpseFast class FileData attr_accessor :tag, :name, :size, :date @@ -673,12 +799,9 @@ class KPSEFAST end def report case @tag - when 1 - "deleted | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" - when 2 - "present | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" - when 3 - "obsolete | #{' '*8} | #{' '*16} | #{@name}" + when 1 then "deleted | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 2 then "present | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 3 then "obsolete | #{' '*8} | #{' '*16} | #{@name}" end end end @@ -769,88 +892,16 @@ class KPSEFAST end -module KpseRunner - - @@kpse = nil - - def KpseRunner.kpsewhich(arg='') - options, arguments = split_args(arg) - unless @@kpse then - @@kpse = KPSEFAST.new - @@kpse.load_cnf - @@kpse.progname = options['progname'] || '' - @@kpse.engine = options['engine'] || '' - @@kpse.format = options['format'] || '' - @@kpse.expand_variables - @@kpse.load_lsr - else - @@kpse.progname = options['progname'] || '' - @@kpse.engine = options['engine'] || '' - @@kpse.format = options['format'] || '' - @@kpse.expand_variables - end - if option = options['expand-braces'] and not option.empty? then - @@kpse.expand_braces(option) - elsif option = options['expand-path'] and not option.empty? then - @@kpse.expand_path(option) - elsif option = options['expand-var'] and not option.empty? then - @@kpse.expand_var(option) - elsif option = options['show-path'] and not option.empty? then - @@kpse.show_path(option) - elsif option = options['var-value'] and not option.empty? then - @@kpse.expand_var(option) - elsif arguments.size > 0 then - files = Array.new - arguments.each do |option| - if file = @@kpse.find_file(option) and not file.empty? then - files << file - end - end - files.join("\n") - else - '' - end - end - - def KpseRunner.kpsereset - @@kpse = nil - end - - private +# if false then - def KpseRunner.split_args(arg) - vars, args = Hash.new, Array.new - arg.gsub!(/([\"\'])(.*?)\1/o) do - $2.gsub(' ','<space/>') - end - arg = arg.split(/\s+/o) - arg.collect! do |a| - a.gsub('<space/>',' ') - end - arg.each do |a| - if a =~ /^(.*?)\=(.*?)$/o then - k, v = $1, $2 - vars[k.sub(/^\-+/,'')] = v - else - args << a - end - end - # puts vars.inspect - # puts args.inspect - return vars, args - end - -end - -if false then + # k = KpseFast.new # (root) + # k.set_test_patterns + # k.load_cnf + # k.expand_variables + # k.load_lsr - k = KPSEFAST.new # (root) - k.set_test_patterns - k.load_cnf - k.expand_variables - k.load_lsr + # k.show_test_patterns - k.show_test_patterns # puts k.list_variables # puts k.list_expansions # k.list_lsr @@ -876,6 +927,6 @@ if false then # puts "expand value $TEXINPUTS.context" # puts k.var_value("$TEXINPUTS.context") - exit + # exit -end +# end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpseremote.rb b/Master/texmf-dist/scripts/context/ruby/base/kpseremote.rb new file mode 100644 index 00000000000..9a73b88b00e --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpseremote.rb @@ -0,0 +1,116 @@ +require 'base/kpsefast' + +case ENV['KPSEMETHOD'] + when /soap/o then require 'base/kpse/soap' + when /drb/o then require 'base/kpse/drb' + else require 'base/kpse/drb' +end + +class KpseRemote + + @@port = ENV['KPSEPORT'] || 7000 + @@method = ENV['KPSEMETHOD'] || 'drb' + + def KpseRemote::available? + @@method && @@port + end + + def KpseRemote::start_server(port=nil) + kpse = KpseServer.new(port || @@port) + kpse.start + end + + def KpseRemote::start_client(port=nil) # keeps object in server + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpse = kpseclient.object + tree = kpse.choose(KpseUtil::identify, KpseUtil::environment) + [kpse, tree] + end + + def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil + end + + def initialize(port=nil) + if KpseRemote::available? then + begin + @kpse, @tree = KpseRemote::start_client(port) + rescue + @kpse, @tree = nil, nil + end + else + @kpse, @tree = nil, nil + end + end + + def progname=(value) + @kpse.set(@tree,'progname',value) + end + def format=(value) + @kpse.set(@tree,'format',value) + end + def engine=(value) + @kpse.set(@tree,'engine',value) + end + + def progname + @kpse.get(@tree,'progname') + end + def format + @kpse.get(@tree,'format') + end + def engine + @kpse.get(@tree,'engine') + end + + def load + @kpse.load(KpseUtil::identify, KpseUtil::environment) + end + def okay? + @kpse && @tree + end + def set(key,value) + @kpse.set(@tree,key,value) + end + def load_cnf + @kpse.load_cnf(@tree) + end + def load_lsr + @kpse.load_lsr(@tree) + end + def expand_variables + @kpse.expand_variables(@tree) + end + def expand_braces(str) + clean_name(@kpse.expand_braces(@tree,str)) + end + def expand_path(str) + clean_name(@kpse.expand_path(@tree,str)) + end + def expand_var(str) + clean_name(@kpse.expand_var(@tree,str)) + end + def show_path(str) + clean_name(@kpse.show_path(@tree,str)) + end + def var_value(str) + clean_name(@kpse.var_value(@tree,str)) + end + def find_file(filename) + clean_name(@kpse.find_file(@tree,filename)) + end + def find_files(filename,first=false) + # dodo: each filename + @kpse.find_files(@tree,filename,first) + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpserunner.rb b/Master/texmf-dist/scripts/context/ruby/base/kpserunner.rb new file mode 100644 index 00000000000..cfc2ad4fbfd --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/kpserunner.rb @@ -0,0 +1,87 @@ +require 'base/kpsefast' + +module KpseRunner + + @@kpse = nil + + def KpseRunner.kpsewhich(arg='') + options, arguments = split_args(arg) + unless @@kpse then + if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then + require 'base/kpseremote' + @@kpse = KpseRemote.new + else + @@kpse = nil + end + if @@kpse && @@kpse.okay? then + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + else + require 'base/kpsefast' + @@kpse = KpseFast.new + @@kpse.load_cnf + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + @@kpse.expand_variables + @@kpse.load_lsr + end + else + @@kpse.progname = options['progname'] || '' + @@kpse.engine = options['engine'] || '' + @@kpse.format = options['format'] || '' + @@kpse.expand_variables + end + if option = options['expand-braces'] and not option.empty? then + @@kpse.expand_braces(option) + elsif option = options['expand-path'] and not option.empty? then + @@kpse.expand_path(option) + elsif option = options['expand-var'] and not option.empty? then + @@kpse.expand_var(option) + elsif option = options['show-path'] and not option.empty? then + @@kpse.show_path(option) + elsif option = options['var-value'] and not option.empty? then + @@kpse.expand_var(option) + elsif arguments.size > 0 then + files = Array.new + arguments.each do |option| + if file = @@kpse.find_file(option) and not file.empty? then + files << file + end + end + files.join("\n") + else + '' + end + end + + def KpseRunner.kpsereset + @@kpse = nil + end + + private + + def KpseRunner.split_args(arg) + vars, args = Hash.new, Array.new + arg.gsub!(/([\"\'])(.*?)\1/o) do + $2.gsub(' ','<space/>') + end + arg = arg.split(/\s+/o) + arg.collect! do |a| + a.gsub('<space/>',' ') + end + arg.each do |a| + if a =~ /^(.*?)\=(.*?)$/o then + k, v = $1, $2 + vars[k.sub(/^\-+/,'')] = v + else + args << a + end + end + # puts vars.inspect + # puts args.inspect + return vars, args + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/merge.rb b/Master/texmf-dist/scripts/context/ruby/base/merge.rb new file mode 100644 index 00000000000..c6ea6c09e66 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/merge.rb @@ -0,0 +1,139 @@ +# module : base/merge +# copyright : PRAGMA Advanced Document Engineering +# version : 2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# --selfmerg ewill create stand alone script (--selfcleanup does the opposite) + +# this module will package all the used modules in the file itself +# so that we can relocate the file at wish, usage: +# +# merge: +# +# unless SelfMerge::ok? && SelfMerge::merge then +# puts("merging should happen on the path were the base inserts reside") +# end +# +# cleanup: +# +# unless SelfMerge::cleanup then +# puts("merging should happen on the path were the base inserts reside") +# end + +module SelfMerge + + @@kpsemergestart = "\# kpse_merge_start" + @@kpsemergestop = "\# kpse_merge_stop" + @@kpsemergefile = "\# kpse_merge_file: " + @@kpsemergedone = "\# kpse_merge_done: " + + @@filename = File.basename($0) + @@ownpath = File.expand_path(File.dirname($0)) + @@modroot = '(base|graphics|rslb|www)' # needed in regex in order not to mess up SelfMerge + @@modules = $".collect do |file| File.expand_path(file) end + + @@modules.delete_if do |file| + file !~ /^#{@@ownpath}\/#{@@modroot}.*$/ + end + + def SelfMerge::ok? + begin + @@modules.each do |file| + return false unless FileTest.file?(file) + end + rescue + return false + else + return true + end + end + + def SelfMerge::merge + begin + if SelfMerge::ok? && rbfile = IO.read(@@filename) then + begin + inserts = "#{@@kpsemergestart}\n\n" + @@modules.each do |file| + inserts << "#{@@kpsemergefile}'#{file}'\n\n" + inserts << IO.read(file).gsub(/^#.*?\n$/,'') + inserts << "\n\n" + end + inserts << "#{@@kpsemergestop}\n\n" + # no gsub! else we end up in SelfMerge + rbfile.sub!(/#{@@kpsemergestart}\s*#{@@kpsemergestop}/mois) do + inserts + end + rbfile.gsub!(/^(.*)(require [\"\'].*?#{@@modroot}.*)$/) do + pre, post = $1, $2 + if pre =~ /#{@@kpsemergedone}/ then + "#{pre}#{post}" + else + "#{pre}#{@@kpsemergedone}#{post}" + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::cleanup + begin + if rbfile = IO.read(@@filename) then + begin + rbfile.sub!(/#{@@kpsemergestart}(.*)#{@@kpsemergestop}\s*/mois) do + "#{@@kpsemergestart}\n\n#{@@kpsemergestop}\n\n" + end + rbfile.gsub!(/^(.*#{@@kpsemergedone}.*)$/) do + str = $1 + if str =~ /require [\"\']/ then + str.gsub(/#{@@kpsemergedone}/, '') + else + str + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::replace + if SelfMerge::ok? then + SelfMerge::cleanup + SelfMerge::merge + end + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/mp.rb b/Master/texmf-dist/scripts/context/ruby/base/mp.rb new file mode 100644 index 00000000000..b30be111091 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/base/mp.rb @@ -0,0 +1,167 @@ +# module : base/mp +# copyright : PRAGMA Advanced Document Engineering +# version : 2005-2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +module MPTools + + @@definitions, @@start, @@stop, @@before, @@after = Hash.new, Hash.new, Hash.new, Hash.new, Hash.new + + + @@definitions['plain'] = <<EOT +\\gdef\\mpxshipout{\\shipout\\hbox\\bgroup + \\setbox0=\\hbox\\bgroup} + +\\gdef\\stopmpxshipout{\\egroup \\dimen0=\\ht0 \\advance\\dimen0\\dp0 + \\dimen1=\\ht0 \\dimen2=\\dp0 + \\setbox0=\\hbox\\bgroup + \\box0 + \\ifnum\\dimen0>0 \\vrule width1sp height\\dimen1 depth\\dimen2 + \\else \\vrule width1sp height1sp depth0sp\\relax + \\fi\\egroup + \\ht0=0pt \\dp0=0pt \\box0 \\egroup} +EOT + + @@start ['plain'] = "" + @@before['plain'] = "\\mpxshipout" + @@after ['plain'] = "\\stopmpxshipout" + @@stop ['plain'] = "\\end{document}" + + @@definitions['context'] = <<EOT +\\ifx\\startMPXpage\\undefined + + \\ifx\\loadallfontmapfiles\\undefined \\let\\loadallfontmapfiles\\relax \\fi + + \\gdef\\startMPXpage + {\\shipout\\hbox + \\bgroup + \\setbox0=\\hbox + \\bgroup} + + \\gdef\\stopMPXpage + {\\egroup + \\dimen0=\\ht0 + \\advance\\dimen0\\dp0 + \\dimen1=\\ht0 + \\dimen2=\\dp0 + \\setbox0=\\hbox\\bgroup + \\box0 + \\ifnum\\dimen0>0 + \\vrule width 1sp height \\dimen1 depth \\dimen2 + \\else + \\vrule width 1sp height 1sp depth 0sp \\relax + \\fi + \\egroup + \\ht0=0pt + \\dp0=0pt + \\loadallfontmapfiles + \\box0 + \\egroup} + +\\fi + +\\ifx\\starttext\\undefined + + \\let\\starttext\\relax + \\def\\stoptext{\\end{document}} + +\\fi +EOT + + @@start ['context'] = "\\starttext" + @@before['context'] = "\\startMPXpage" + @@after ['context'] = "\\stopMPXpage" + @@stop ['context'] = "\\stoptext" + + # todo: \usemodule[m-mpx ] and test fo defined + + def MPTools::mptotex(from,to=nil,method='plain') + begin + if from && data = IO.read(from) then + f = if to then File.open(to,'w') else $stdout end + f.puts("% file: #{from}") + f.puts("") + f.puts(@@definitions[method]) + unless @@start[method].empty? then + f.puts("") + f.puts(@@start[method]) + end + data.gsub!(/[^\\]%.*?$/mo) do + '' + end + data.scan(/(verbatim|b)tex\s*(.*?)\s*etex/mo) do + tag, text = $1, $2 + f.puts("") + if tag == 'b' then + f.puts(@@before[method]) + f.puts("#{text}%") + f.puts(@@after [method]) + else + f.puts("#{text}") + end + f.puts("") + end + f.puts("") + f.puts(@@stop[method]) + f.close + else + return false + end + rescue + File.delete(to) rescue false + return false + else + return true + end + end + + @@splitMPlines = false + + def MPTools::splitmplines(str) + if @@splitMPlines then + btex, verbatimtex, strings, result = Array.new, Array.new, Array.new, str.dup + # protect texts + result.gsub!(/btex\s*(.*?)\s*etex/) do + btex << $1 + "btex(#{btex.length-1})" + end + result.gsub!(/verbatimtex\s*(.*?)\s*etex/) do + verbatimtex << $1 + "verbatimtex(#{verbatimtex.length-1})" + end + result.gsub!(/\"(.*?)\"/) do + strings << $1 + "\"#{strings.length-1}\"" + end + result.gsub!(/\;/) do + ";\n" + end + result.gsub!(/(.{80,})(\-\-\-|\-\-|\.\.\.|\.\.)/) do + "#{$1}#{$2}\n" + end + result.gsub!(/\n[\s\n]+/mois) do + "\n" + end + result.gsub!(/btex\((\d+)\)/) do + "btex #{btex[$1.to_i]} etex" + end + result.gsub!(/verbatimtex\((\d+)\)/) do + "verbatimtex #{verbatimtex[$1.to_i]} etex" + end + result.gsub!(/\"(\d+)\"/) do + "\"#{strings[$1.to_i]}\"" + end + # return result # let's catch xetex bug + return result.gsub(/\^\^(M|J)/o, "\n") + else + # return str # let's catch xetex bug + return str.gsub(/\^\^(M|J)/o, "\n") + end + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/base/pdf.rb b/Master/texmf-dist/scripts/context/ruby/base/pdf.rb index d8cbf9e0523..9f4e9a6c3f6 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/pdf.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/pdf.rb @@ -1,14 +1,34 @@ module PDFview - @files = Hash.new + @files = Hash.new + @opencalls = Hash.new + @closecalls = Hash.new + @allcalls = Hash.new + + @method = 'default' # 'xpdf' + + @opencalls['default'] = "pdfopen --file" + @opencalls['xpdf'] = "xpdfopen" + + @closecalls['default'] = "pdfclose --file" + @closecalls['xpdf'] = nil + + @allcalls['default'] = "pdfclose --all" + @allcalls['xpdf'] = nil + + def PDFview.setmethod(method) + @method = method + end def PDFview.open(*list) begin [*list].flatten.each do |file| filename = fullname(file) if FileTest.file?(filename) then - result = `pdfopen --file #{filename} 2>&1` - @files[filename] = true + if @opencalls[@method] then + result = `#{@opencalls[@method]} #{filename} 2>&1` + @files[filename] = true + end end end rescue @@ -20,7 +40,9 @@ module PDFview filename = fullname(file) begin if @files.key?(filename) then - result = `pdfclose --file #{filename} 2>&1` + if @closecalls[@method] then + result = `#{@closecalls[@method]} #{filename} 2>&1` + end else closeall return @@ -33,7 +55,9 @@ module PDFview def PDFview.closeall begin - result = `pdfclose --all 2>&1` + if @allcalls[@method] then + result = `#{@allcalls[@method]} 2>&1` + end rescue end @files.clear diff --git a/Master/texmf-dist/scripts/context/ruby/base/state.rb b/Master/texmf-dist/scripts/context/ruby/base/state.rb index f57231592a7..4b208812875 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/state.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/state.rb @@ -56,7 +56,7 @@ class FileState sum = '' begin if FileTest.file?(filename) && (data = IO.read(filename)) then - data.gsub!(/\n.*?(#{[omit].flatten.join('|')}).*?\n/ms,"\n") if omit + data.gsub!(/\n.*?(#{[omit].flatten.join('|')}).*?\n/) do "\n" end if omit sum = MD5.new(data).hexdigest.upcase end rescue diff --git a/Master/texmf-dist/scripts/context/ruby/base/switch.rb b/Master/texmf-dist/scripts/context/ruby/base/switch.rb index 64d518bd42f..5187a795f03 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/switch.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/switch.rb @@ -33,12 +33,18 @@ end class File + @@update_eps = 1 + def File.needsupdate(oldname,newname) begin - if $mswindows then - return File.stat(oldname).mtime > File.stat(newname).mtime + oldtime = File.stat(oldname).mtime.to_i + newtime = File.stat(newname).mtime.to_i + if newtime >= oldtime then + return false + elsif oldtime-newtime < @@update_eps then + return false else - return File.stat(oldname).mtime != File.stat(newname).mtime + return true end rescue return true @@ -46,11 +52,12 @@ class File end def File.syncmtimes(oldname,newname) + return begin if $mswindows then - # does not work (yet) - t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here - File.utime(0,t,oldname,newname) + # does not work (yet) / gives future timestamp + # t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here + # File.utime(0,t,oldname,newname) else t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here File.utime(0,t,oldname,newname) @@ -493,15 +500,21 @@ class CommandLine unless foundkey then @registered.each do |option, shortcut, kind| n = 0 - if option =~ /^#{key}/i then - case n - when 0 - foundkey, foundkind = option, kind - n = 1 - when 1 - # ambiguous matches, like --fix => --fixme --fixyou - foundkey, foundkind = nil, nil - break + begin + re = /^#{key}/i + rescue + key = key.inspect.sub(/^\"(.*)\"$/) do $1 end + re = /^#{key}/i + ensure + if option =~ re then + case n + when 0 + foundkey, foundkind, n = option, kind, 1 + when 1 + # ambiguous matches, like --fix => --fixme --fixyou + foundkey, foundkind = nil, nil + break + end end end end diff --git a/Master/texmf-dist/scripts/context/ruby/base/system.rb b/Master/texmf-dist/scripts/context/ruby/base/system.rb index ed8c2756e2b..267a22cb98f 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/system.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/system.rb @@ -33,6 +33,13 @@ module System if @@mswindows then 'nul' else '/dev/null/' end end + def System.unix? + not @@mswindows + end + def System.mswin? + @@mswindows + end + def System.binnames(str) if @@binnames.key?(str) then @@binnames[str] @@ -41,6 +48,18 @@ module System end end + def System.prependengine(str) + if str =~ /^\S+\.(pl|rb|lua|py)/io then + case $1 + when 'pl' then return "perl #{str}" + when 'rb' then return "ruby #{str}" + when 'lua' then return "lua #{str}" + when 'py' then return "python #{str}" + end + end + return str + end + def System.locatedprogram(program) if @@located.key?(program) then return @@located[program] @@ -49,7 +68,7 @@ module System if binname =~ /\..*$/io then @@binpaths.each do |path| if FileTest.file?(str = File.join(path,binname)) then - return @@located[program] = str + return @@located[program] = System.prependengine(str) end end end @@ -57,7 +76,7 @@ module System @@binpaths.each do |path| @@binsuffixes.each do |suffix| if FileTest.file?(str = File.join(path,"#{binname}#{suffix}")) then - return @@located[program] = str + return @@located[program] = System.prependengine(str) end end end @@ -74,7 +93,7 @@ module System end program = program + ' ' + arguments if ! arguments.empty? program.gsub!(/\s+/io, ' ') - program.gsub!(/(\.\/)+/io, '') + #program.gsub!(/(\/\.\/)+/io, '/') program.gsub!(/\\/io, '/') return program end diff --git a/Master/texmf-dist/scripts/context/ruby/base/tex.rb b/Master/texmf-dist/scripts/context/ruby/base/tex.rb index 2cc9d25422e..a6403dcde2b 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/tex.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/tex.rb @@ -8,8 +8,11 @@ # info : j.hagen@xs4all.nl # www : www.pragma-ade.com -# todo: write systemcall for mpost to file so that it can be run -# faster +# todo: +# +# - write systemcall for mpost to file so that it can be run faster +# - use -8bit and -progname +# # report ? @@ -20,6 +23,7 @@ require 'base/state' require 'base/pdf' require 'base/file' require 'base/ctx' +require 'base/mp' class String @@ -33,6 +37,12 @@ class String end +# class String + # def utf_bom? + # self.match(/^\357\273\277/o).length>0 rescue false + # end +# end + class Array def standard? @@ -43,6 +53,10 @@ class Array end end + def join_path + self.join(File::PATH_SEPARATOR) + end + end class TEX @@ -56,6 +70,7 @@ class TEX @@texengines = Hash.new @@mpsengines = Hash.new @@backends = Hash.new + @@mappaths = Hash.new @@runoptions = Hash.new @@texformats = Hash.new @@mpsformats = Hash.new @@ -68,11 +83,32 @@ class TEX @@texmethods = Hash.new @@mpsmethods = Hash.new - ['tex','pdftex','pdfetex','standard'] .each do |e| @@texengines[e] = 'pdfetex' end + @@pdftex = 'pdftex' # new default, pdfetex is gone + + ENV['PATH'].split(File::PATH_SEPARATOR).each do |p| + if System.unix? then + pp, pe = "#{p}/pdftex" , "#{p}/pdfetex" + else + pp, pe = "#{p}/pdftex.exe", "#{p}/pdfetex.exe" + end + if FileTest.file?(pe) then + # we assume no update + @@pdftex = 'pdfetex' + break + elsif FileTest.file?(pp) then + # we assume an update + @@pdftex = 'pdftex' + break + end + end + + ['etex','pdfetex','standard'] .each do |e| @@texengines[e] = @@pdftex end + ['tex','pdftex'] .each do |e| @@texengines[e] = 'pdftex' end ['aleph','omega'] .each do |e| @@texengines[e] = 'aleph' end ['xetex'] .each do |e| @@texengines[e] = 'xetex' end + ['luatex'] .each do |e| @@texengines[e] = 'luatex' end - ['metapost','mpost','standard'] .each do |e| @@mpsengines[e] = 'mpost' end + ['metapost','mpost', 'standard'] .each do |e| @@mpsengines[e] = 'mpost' end ['pdfetex','pdftex','pdf','pdftex','standard'] .each do |b| @@backends[b] = 'pdftex' end ['dvipdfmx','dvipdfm','dpx','dpm'] .each do |b| @@backends[b] = 'dvipdfmx' end @@ -80,6 +116,13 @@ class TEX ['dvips','ps'] .each do |b| @@backends[b] = 'dvips' end ['dvipsone'] .each do |b| @@backends[b] = 'dvipsone' end ['acrobat','adobe','distiller'] .each do |b| @@backends[b] = 'acrobat' end + ['xdv','xdv2pdf'] .each do |b| @@backends[b] = 'xdv2pdf' end + + ['tex','standard'] .each do |b| @@mappaths[b] = 'dvips' end + ['pdftex','pdfetex'] .each do |b| @@mappaths[b] = 'pdftex' end + ['aleph','omega','xetex'] .each do |b| @@mappaths[b] = 'dvipdfm' end + ['dvipdfm', 'dvipdfmx', 'xdvipdfmx'] .each do |b| @@mappaths[b] = 'dvipdfm' end + ['xdv','xdv2pdf'] .each do |b| @@mappaths[b] = 'dvips' end # todo norwegian (no) @@ -88,6 +131,7 @@ class TEX ['cont-nl','nl','dutch'] .each do |f| @@texformats[f] = 'cont-nl' end ['cont-de','de','german'] .each do |f| @@texformats[f] = 'cont-de' end ['cont-it','it','italian'] .each do |f| @@texformats[f] = 'cont-it' end + ['cont-fr','fr','french'] .each do |f| @@texformats[f] = 'cont-fr' end ['cont-cz','cz','czech'] .each do |f| @@texformats[f] = 'cont-cz' end ['cont-ro','ro','romanian'] .each do |f| @@texformats[f] = 'cont-ro' end ['cont-uk','uk','brittish'] .each do |f| @@texformats[f] = 'cont-uk' end @@ -98,12 +142,13 @@ class TEX ['plain','mpost'] .each do |f| @@mpsformats[f] = 'plain' end ['metafun','context','standard'] .each do |f| @@mpsformats[f] = 'metafun' end - ['pdfetex','aleph','omega'] .each do |p| @@prognames[p] = 'context' end + ['pdftex','pdfetex','aleph','omega', + 'xetex','luatex'] .each do |p| @@prognames[p] = 'context' end ['mpost'] .each do |p| @@prognames[p] = 'metafun' end ['plain','default','standard','mptopdf'] .each do |f| @@texmethods[f] = 'plain' end ['cont-en','cont-nl','cont-de','cont-it', - 'cont-cz','cont-ro','cont-uk'] .each do |f| @@texmethods[f] = 'context' end + 'cont-fr','cont-cz','cont-ro','cont-uk'] .each do |f| @@texmethods[f] = 'context' end ['latex'] .each do |f| @@texmethods[f] = 'latex' end ['plain','default','standard'] .each do |f| @@mpsmethods[f] = 'plain' end @@ -113,28 +158,35 @@ class TEX @@mpsmakestr['plain'] = "\\dump" ['cont-en','cont-nl','cont-de','cont-it', - 'cont-cz','cont-ro','cont-uk'] .each do |f| @@texprocstr[f] = "\\emergencyend" end + 'cont-fr','cont-cz','cont-ro','cont-uk'] .each do |f| @@texprocstr[f] = "\\emergencyend" end - @@runoptions['xetex'] = ['--no-pdf'] + # @@runoptions['xetex'] = ['--output-driver \\\"-d 4 -V 5\\\"'] # we need the pos pass + @@runoptions['xetex'] = ['--8bit -no-pdf'] # from now on we assume (x)dvipdfmx to be used + @@runoptions['pdfetex'] = ['--8bit '] + @@runoptions['pdftex'] = ['--8bit '] # pdftex is now pdfetex + @@runoptions['luatex'] = ['--8bit '] + @@runoptions['aleph'] = ['--8bit '] @@booleanvars = [ 'batchmode', 'nonstopmode', 'fast', 'fastdisabled', 'silentmode', 'final', 'paranoid', 'notparanoid', 'nobanner', 'once', 'allpatterns', - 'nompmode', 'nomprun', 'automprun', + 'nompmode', 'nomprun', 'automprun', 'combine', 'nomapfiles', 'local', 'arrange', 'noarrange', 'forcexml', 'foxet', + 'alpha', 'beta', 'mpyforce', 'forcempy', 'forcetexutil', 'texutil', 'globalfile', 'autopath', - 'purge', 'purgeall', 'autopdf', 'simplerun', 'verbose', - 'nooptionfile' + 'purge', 'purgeall', 'keep', 'autopdf', 'xpdf', 'simplerun', 'verbose', + 'nooptionfile', 'nobackend', 'noctx', 'utfbom' ] @@stringvars = [ 'modefile', 'result', 'suffix', 'response', 'path', 'filters', 'usemodules', 'environments', 'separation', 'setuppath', - 'arguments', 'input', 'output', 'randomseed', 'modes', 'filename', - 'modefile', 'ctxfile' + 'arguments', 'input', 'output', 'randomseed', 'modes', 'mode', 'filename', + 'ctxfile', 'printformat', 'paperformat', 'paperoffset', + 'timeout' ] @@standardvars = [ 'mainlanguage', 'bodyfont', 'language' @@ -148,16 +200,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) @@ -201,13 +259,18 @@ class TEX setvariable('engine', 'standard') # replaced by tex/mpsengine setvariable('backend', 'pdftex') setvariable('runs', '8') - setvariable('randomseed', rand(1440).to_s) + setvariable('randomseed', rand(1440).to_s) # we want the same seed for one run # files setvariable('files', []) # defaults setvariable('texengine', 'standard') setvariable('mpsengine', 'standard') setvariable('backend', 'standard') + setvariable('error', '') + end + + def error? + not getvariable('error').empty? end def runtime @@ -218,10 +281,17 @@ class TEX report("runtime: #{runtime}") end + def runcommand(something) + command = [something].flatten.join(' ') + report("running: #{command}") if getvariable('verbose') + system(command) + end + def inspect(name=nil) if ! name || name.empty? then name = [booleanvars,stringvars,standardvars,knownvars] end + str = '' # allocate [name].flatten.each do |n| if str = getvariable(n) then unless (str.class == String) && str.empty? then @@ -249,11 +319,14 @@ class TEX begin Dir.glob("#{@@temprunfile}*").each do |name| if File.file?(name) && (File.splitname(name)[1] !~ /(pdf|dvi)/o) then - begin File.delete(name) ; rescue ; end + File.delete(name) rescue false end end rescue end + ['mpgraph.mp'].each do |file| + (File.delete(file) if (FileTest.size?(file) rescue 10) < 10) rescue false + end end def backends() @@backends.keys.sort end @@ -301,11 +374,12 @@ class TEX end def prefixed(format,engine) + # format case engine - when /etex|eetex|pdfetex|pdfeetex|pdfxtex|xpdfetex|eomega|aleph|xetex/io then - "*#{format}" - else - format + when /etex|pdftex|pdfetex|aleph|xetex|luatex/io then + "*#{format}" + else + format end end @@ -318,18 +392,24 @@ class TEX if str.class == String then str.split(',') else str.flatten end end - def validtexformat(str) validsomething(str,@@texformats) end - def validmpsformat(str) validsomething(str,@@mpsformats) end - def validtexengine(str) validsomething(str,@@texengines) end - def validmpsengine(str) validsomething(str,@@mpsengines) end + def validtexformat(str) validsomething(str,@@texformats,'tex') end + def validmpsformat(str) validsomething(str,@@mpsformats,'mp' ) end + def validtexengine(str) validsomething(str,@@texengines,'pdfetex') end + def validmpsengine(str) validsomething(str,@@mpsengines,'mpost' ) end def validtexmethod(str) [validsomething(str,@@texmethods)].flatten.first end def validmpsmethod(str) [validsomething(str,@@mpsmethods)].flatten.first end - def validsomething(str,something) + def validsomething(str,something,type=nil) if str then list = [str].flatten.collect do |s| - something[s] + if something[s] then + something[s] + elsif type && s =~ /\.#{type}$/ then + s + else + nil + end end .compact.uniq if list.length>0 then if str.class == String then list.first else list end @@ -384,20 +464,20 @@ class TEX # upward compatibility problems) but stuck to the bas/mem/fmt flags if engine then case validmpsengine(engine) - when /mpost/ then "--mem" - when /mfont/ then "--bas" - else "--fmt" + when /mpost/ then "-mem" + when /mfont/ then "-bas" + else "-fmt" end else - "--fmt" + "-fmt" end end def prognameflag(progname=nil) case getvariable('distribution') - when 'standard' then prefix = "--progname" - when /web2c/io then prefix = "--progname" - when /miktex/io then prefix = "--alias" + when 'standard' then prefix = "-progname" + when /web2c/io then prefix = "-progname" + when /miktex/io then prefix = "-alias" else return "" end if progname then @@ -422,7 +502,7 @@ class TEX if Kpse.miktex? then "-tcx=#{file}" else - "--translate-file=#{file}" + "-translate-file=#{file}" end end @@ -436,7 +516,7 @@ class TEX filename = Kpse.found('context.tex') version = 'unknown' begin - if FileTest.file?(filename) && IO.read(filename).match(/\\contextversion\{(\d+\.\d+\.\d+)\}/) then + if FileTest.file?(filename) && IO.read(filename).match(/\\contextversion\{(\d+\.\d+\.\d+.*?)\}/) then version = $1 end rescue @@ -445,6 +525,10 @@ class TEX end def makeformats + + checktestversion + + report("using search method '#{Kpse.searchmethod}'") if getvariable('fast') then report('using existing database') else @@ -463,24 +547,27 @@ class TEX # save current path savedpath = Dir.getwd # generate tex formats + unless texformats || mpsformats then + report('provide valid format (name.tex, name.mp, ...) or format id (metafun, en, nl, ...)') + end if texformats && texengine && (progname = validprogname(getvariable('progname'),texengine)) then report("using tex engine #{texengine}") texformatpath = if getvariable('local') then '.' else Kpse.formatpath(texengine,true) end # can be empty, to do report("using tex format path #{texformatpath}") - begin - Dir.chdir(texformatpath) - rescue - end - if texformats.length > 0 then - makeuserfile - makeresponsefile - end - texformats.each do |texformat| - report("generating tex format #{texformat}") - command = [quoted(texengine),prognameflag(progname),iniflag,tcxflag,prefixed(texformat,texengine),texmakeextras(texformat)].join(' ') - report(command) if getvariable('verbose') - system(command) + Dir.chdir(texformatpath) rescue false + if FileTest.writable?(texformatpath) then + if texformats.length > 0 then + makeuserfile + makeresponsefile + end + texformats.each do |texformat| + report("generating tex format #{texformat}") + runcommand([quoted(texengine),prognameflag(progname),iniflag,tcxflag,prefixed(texformat,texengine),texmakeextras(texformat)]) + end + else + report("unable to make format due to lack of permissions") + texformatpath = '' end else texformatpath = '' @@ -490,15 +577,16 @@ class TEX report("using mp engine #{mpsengine}") mpsformatpath = if getvariable('local') then '.' else Kpse.formatpath(mpsengine,false) end report("using mps format path #{mpsformatpath}") - begin - Dir.chdir(mpsformatpath) - rescue - end - mpsformats.each do |mpsformat| - report("generating mps format #{mpsformat}") - command = [quoted(mpsengine),prognameflag(progname),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)].join(' ') - report(command) if getvariable('verbose') - system(command) + Dir.chdir(mpsformatpath) rescue false + if FileTest.writable?(mpsformatpath) then + mpsformats.each do |mpsformat| + report("generating mps format #{mpsformat}") + # runcommand([quoted(mpsengine),prognameflag(progname),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)]) + runcommand([quoted(mpsengine),iniflag,tcxflag,mpsformat,mpsmakeextras(mpsformat)]) + end + else + report("unable to make format due to lack of permissions") + mpsformatpath = '' end else mpsformatpath = '' @@ -560,7 +648,7 @@ class TEX f.close if FileTest.file?(tempfilename('tex')) then format = File.basename(name) - engine = if name =~ /(pdfetex|aleph|xetex)[\/\\]#{format}/ then $1 else '' end + engine = if name =~ /(pdftex|pdfetex|aleph|xetex|luatex)[\/\\]#{format}/ then $1 else '' end if engine.empty? then engineflag = "" else @@ -571,8 +659,8 @@ class TEX interface = $1.sub(/cont\-/,'') results.push('') results.push("testing interface #{interface}") - flags = ['--process','--batch','--once',"--interface=#{interface}",engineflag] - # result = Kpse.pipescript('newtexexec',tempfilename,flags) + flags = ['--noctx','--process','--batch','--once',"--interface=#{interface}",engineflag] + # result = Kpse.pipescript('texexec',tempfilename,flags) result = runtexexec([tempfilename], flags, 1) if FileTest.file?("#{@@temprunfile}.log") then logdata = IO.read("#{@@temprunfile}.log") @@ -663,6 +751,7 @@ class TEX @@preamblekeys = [ ['tex','texengine'], + ['engine','texengine'], ['program','texengine'], ['translate','tcxfilter'], ['tcx','tcxfilter'], @@ -670,22 +759,42 @@ class TEX ['mode','mode'], ['ctx','ctxfile'], ['version','contextversion'], - ['format','texformat'], - ['interface','texformat'] + ['format','texformats'], + ['interface','texformats'], ] + @@re_utf_bom = /^\357\273\277/o + def scantexpreamble(filename) begin if FileTest.file?(filename) and tex = File.open(filename) then + bomdone = false while str = tex.gets and str.chomp! do + unless bomdone then + if str.sub!(@@re_utf_bom, '') + report("utf mode forced (bom found)") + setvariable('utfbom',true) + end + bomdone = true + end if str =~ /^\%\s*(.*)/o then - vars = Hash.new + # we only accept lines with key=value pairs + vars, ok = Hash.new, true $1.split(/\s+/o).each do |s| k, v = s.split('=') - vars[k] = v + if k && v then + vars[k] = v + else + ok = false + break + end end - @@preamblekeys.each do |v| - setvariable(v[1],vars[v[0]]) if vars.key?(v[0]) + if ok then + # we have a valid line + @@preamblekeys.each do |v| + setvariable(v[1],vars[v[0]]) if vars.key?(v[0]) && vars[v[0]] + end + break end else break @@ -699,37 +808,37 @@ class TEX end def scantexcontent(filename) - if FileTest.file?(filename) and tex = File.open(filename) then + if FileTest.file?(filename) and tex = File.open(filename) then while str = tex.gets do case str.chomp when /^\%/o then # next when /\\(starttekst|stoptekst|startonderdeel|startdocument|startoverzicht)/o then - setvariable('texformat','nl') ; break + setvariable('texformats','nl') ; break when /\\(stelle|verwende|umgebung|benutze)/o then - setvariable('texformat','de') ; break + setvariable('texformats','de') ; break when /\\(stel|gebruik|omgeving)/o then - setvariable('texformat','nl') ; break + setvariable('texformats','nl') ; break when /\\(use|setup|environment)/o then - setvariable('texformat','en') ; break + setvariable('texformats','en') ; break when /\\(usa|imposta|ambiente)/o then - setvariable('texformat','it') ; break + setvariable('texformats','it') ; break when /(height|width|style)=/o then - setvariable('texformat','en') ; break + setvariable('texformats','en') ; break when /(hoehe|breite|schrift)=/o then - setvariable('texformat','de') ; break + setvariable('texformats','de') ; break when /(hoogte|breedte|letter)=/o then - setvariable('texformat','nl') ; break + setvariable('texformats','nl') ; break when /(altezza|ampiezza|stile)=/o then - setvariable('texformat','it') ; break + setvariable('texformats','it') ; break when /externfiguur/o then - setvariable('texformat','nl') ; break + setvariable('texformats','nl') ; break when /externalfigure/o then - setvariable('texformat','en') ; break + setvariable('texformats','en') ; break when /externeabbildung/o then - setvariable('texformat','de') ; break + setvariable('texformats','de') ; break when /figuraesterna/o then - setvariable('texformat','it') ; break + setvariable('texformats','it') ; break end end tex.close @@ -770,36 +879,11 @@ class TEX end end - def makestubfile(rawname,forcexml=false) - if tmp = File.open(File.suffixed(rawname,'run'),'w') then + def makestubfile(rawname,rawbase,forcexml=false) + if tmp = openedfile(File.suffixed(rawbase,'run')) then tmp << "\\starttext\n" if forcexml then - if FileTest.file?(rawname) && (xml = File.open(rawname)) then - xml.each do |line| - case line - when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then - category, key, value, rest = $1, $2, $3, $4 - case category - when 'job' then - case key - when 'control' then - setvariable(value,if rest.empty? then true else rest end) - when 'mode', 'modes' then - tmp << "\\enablemode[#{value}]\n" - when 'stylefile', 'environment' then - tmp << "\\environment #{value}\n" - when 'module' then - tmp << "\\usemodule[#{value}]\n" - when 'interface' then - contextinterface = value - end - end - when /<[a-z]+/io then # beware of order, first pi test - break - end - end - xml.close - end + # tmp << checkxmlfile(rawname) tmp << "\\processXMLfilegrouped{#{rawname}}\n" else tmp << "\\processfile{#{rawname}}\n" @@ -812,16 +896,113 @@ class TEX end end + # def checkxmlfile(rawname) + # tmp = '' + # if FileTest.file?(rawname) && (xml = File.open(rawname)) then + # xml.each do |line| + # case line + # when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then + # category, key, value, rest = $1, $2, $3, $4 + # case category + # when 'job' then + # case key + # when 'control' then + # setvariable(value,if rest.empty? then true else rest end) + # when 'mode', 'modes' then + # tmp << "\\enablemode[#{value}]\n" + # when 'stylefile', 'environment' then + # tmp << "\\environment #{value}\n" + # when 'module' then + # tmp << "\\usemodule[#{value}]\n" + # when 'interface' then + # contextinterface = value + # when 'ctxfile' then + # setvariable('ctxfile', value) + # report("using source driven ctxfile #{value}") + # end + # end + # when /<[a-z]+/io then # beware of order, first pi test + # break + # end + # end + # xml.close + # end + # return tmp + # end + + def extendvariable(name,value) + set = getvariable(name).split(',') + set << value + str = set.uniq.join(',') + setvariable(name,str) + end + + def checkxmlfile(rawname) + if FileTest.file?(rawname) && (xml = File.open(rawname)) then + xml.each do |line| + case line + when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then + category, key, value, rest = $1, $2, $3, $4 + case category + when 'job' then + case key + when 'control' then + setvariable(value,if rest.empty? then true else rest end) + when /^(mode)(s|)$/ then + extendvariable('modes',value) + when /^(stylefile|environment)(s|)$/ then + extendvariable('environments',value) + when /^(use|)(module)(s|)$/ then + extendvariable('usemodules',value) + when /^(filter)(s|)$/ then + extendvariable('filters',value) + when 'interface' then + contextinterface = value + when 'ctxfile' then + setvariable('ctxfile', value) + report("using source driven ctxfile #{value}") + end + end + when /<[a-z]+/io then # beware of order, first pi test + break + end + end + xml.close + end + end + end class TEX + def timedrun(delay, &block) + delay = delay.to_i rescue 0 + if delay > 0 then + begin + report("job started with timeout '#{delay}'") + timeout(delay) do + yield block + end + rescue TimeoutError + report("job aborted due to timeout '#{delay}'") + rescue + report("job aborted due to error") + else + report("job finished within timeout '#{delay}'") + end + else + yield block + end + end + def processtex # much to do: mp, xml, runs etc setvariable('texformats',[getvariable('interface')]) unless getvariable('interface').empty? getarrayvariable('files').each do |filename| setvariable('filename',filename) report("processing document '#{filename}'") - processfile + timedrun(getvariable('timeout')) do + processfile + end end reportruntime end @@ -835,152 +1016,284 @@ class TEX reportruntime end + private + + def load_map_files(filename) # tui basename + # c \usedmapfile{=}{lm-texnansi} + begin + str = "" + IO.read(filename).scan(/^c\s+\\usedmapfile\{(.*?)\}\{(.*?)\}\s*$/o) do + str << "\\loadmapfile[#{$2}.map]\n" + end + rescue + return "" + else + return str + end + end + + public + + def processmpgraphic + getarrayvariable('files').each do |filename| + setvariable('filename',filename) + report("processing graphic '#{filename}'") + runtexmp(filename,'',false) # no purge + mapspecs = load_map_files(File.suffixed(filename,'temp','tui')) + unless getvariable('keep') then + # not enough: purge_mpx_files(filename) + Dir.glob(File.suffixed(filename,'temp*','*')).each do |fname| + File.delete(fname) unless File.basename(filename) == File.basename(fname) + end + end + begin + data = IO.read(File.suffixed(filename,'log')) + basename = filename.sub(/\.mp$/, '') + if data =~ /output files* written\:\s*(.*)$/mois then + files, number, range, list = $1.split(/\s+/), 0, false, [] + files.each do |fname| + if fname =~ /^.*\.(\d+)$/ then + if range then + (number+1 .. $1.to_i).each do |i| + list << i + end + range = false + else + number = $1.to_i + list << number + end + elsif fname =~ /\.\./ then + range = true + else + range = false + next + end + end + begin + if getvariable('combine') then + fullname = "#{basename}.#{number}" + File.open("texexec.tex",'w') do |f| + f << "\\setupoutput[pdftex]\n" + f << "\\setupcolors[state=start]\n" + f << mapspecs + f << "\\starttext\n" + list.each do |number| + f << "\\startTEXpage\n" + f << "\\convertMPtoPDF{#{fullname}}{1}{1}" + f << "\\stopTEXpage\n" + end + f << "\\stoptext\n" + end + report("converting graphic '#{fullname}'") + runtex("texexec.tex") + pdffile = File.suffixed(basename,'pdf') + File.silentrename("texexec.pdf",pdffile) + report ("#{basename}.* converted to #{pdffile}") + else + list.each do |number| + begin + fullname = "#{basename}.#{number}" + File.open("texexec.tex",'w') do |f| + f << "\\setupoutput[pdftex]\n" + f << "\\setupcolors[state=start]\n" + f << mapspecs + f << "\\starttext\n" + f << "\\startTEXpage\n" + f << "\\convertMPtoPDF{#{fullname}}{1}{1}" + f << "\\stopTEXpage\n" + f << "\\stoptext\n" + end + report("converting graphic '#{fullname}'") + runtex("texexec.tex") + if files.length>1 then + pdffile = File.suffixed(basename,number.to_s,'pdf') + else + pdffile = File.suffixed(basename,'pdf') + end + File.silentrename("texexec.pdf",pdffile) + report ("#{fullname} converted to #{pdffile}") + end + end + end + rescue + report ("error when converting #{fullname} (#{$!})") + end + end + rescue + report("error in converting #{filename}") + end + end + reportruntime + end + def processmpxtex getarrayvariable('files').each do |filename| setvariable('filename',filename) report("processing text of graphic '#{filename}'") - processmpx(filename,true) + processmpx(filename,false,true,true) end reportruntime end def deleteoptionfile(rawname) - begin - File.delete(File.suffixed(rawname,'top')) - rescue + ['top','top.keep'].each do |suffix| + begin + File.delete(File.suffixed(rawname,suffix)) + rescue + end end end - def makeoptionfile(rawname, jobname, jobsuffix, finalrun, fastdisabled, kindofrun) - # jobsuffix = orisuffix - if topname = File.suffixed(rawname,'top') and opt = File.open(topname,'w') then - # local handies - opt << "\% #{topname}\n" - opt << "\\unprotect\n" - opt << "\\setupsystem[\\c!n=#{kindofrun}]\n" - opt << "\\def\\MPOSTformatswitch\{#{prognameflag('metafun')} #{formatflag('mpost')}=\}\n" - if getvariable('batchmode') then - opt << "\\batchmode\n" - end - if getvariable('nonstopmode') then - opt << "\\nonstopmode\n" - end - if getvariable('paranoid') then - opt << "\\def\\maxreadlevel{1}\n" - end - if (str = File.unixfied(getvariable('modefile'))) && ! str.empty? then - opt << "\\readlocfile{#{str}}{}{}\n" - end - if (str = File.unixfied(getvariable('result'))) && ! str.empty? then - opt << "\\setupsystem[file=#{str}]\n" - elsif (str = getvariable('suffix')) && ! str.empty? then - opt << "\\setupsystem[file=#{jobname}.#{str}]\n" - end - if (str = File.unixfied(getvariable('path'))) && ! str.empty? then - opt << "\\usepath[#{str}]\n" unless str.empty? - end - if (str = getvariable('mainlanguage').downcase) && ! str.empty? && ! str.standard? then - opt << "\\setuplanguage[#{str}]\n" - end - if str = validbackend(getvariable('backend')) then - opt << "\\setupoutput[#{str}]\n" - end - if getvariable('color') then - opt << "\\setupcolors[\\c!state=\\v!start]\n" - end - if getvariable('nompmode') || getvariable('nomprun') || getvariable('automprun') then - opt << "\\runMPgraphicsfalse\n" - end - if getvariable('fast') && ! getvariable('fastdisabled') then - opt << "\\fastmode\n" - end - if getvariable('silentmode') then - opt << "\\silentmode\n" - end - if (str = getvariable('separation')) && ! str.empty? then - opt << "\\setupcolors[\\c!split=#{str}]\n" - end - if (str = getvariable('setuppath')) && ! str.empty? then - opt << "\\setupsystem[\\c!directory=\{#{str}\}]\n" - end - if (str = getvariable('paperformat')) && ! str.empty? && ! str.standard? then - if str =~ /^([a-z]+\d+)([a-z]+\d+)$/io then # A5A4 A4A3 A2A1 ... - opt << "\\setuppapersize[#{$1.upcase}][#{$2.upcase}]\n" - else # ...*... - pf = str.upcase.split(/[x\*]/o) - pf << pf[0] if pd.size == 1 - opt << "\\setuppapersize[#{pf[0]}][#{pf[1]}]\n" + def makeoptionfile(rawname, jobname, jobsuffix, finalrun, fastdisabled, kindofrun, currentrun=1) + begin + # jobsuffix = orisuffix + if topname = File.suffixed(rawname,'top') and opt = File.open(topname,'w') then + report("writing option file #{topname}") + # local handies + opt << "\% #{topname}\n" + opt << "\\unprotect\n" + if getvariable('utfbom') then + opt << "\\enableregime[utf]" end - end - if (str = getvariable('background')) && ! str.empty? then - opt << "\\defineoverlay[whatever][{\\externalfigure[#{str}][\\c!factor=\\v!max]}]\n" - opt << "\\setupbackgrounds[\\v!page][\\c!background=whatever]\n" - end - if getvariable('centerpage') then - opt << "\\setuplayout[\\c!location=\\v!middle,\\c!marking=\\v!on]\n" - end - if getvariable('nomapfiles') then - opt << "\\disablemapfiles\n" - end - if getvariable('noarrange') then - opt << "\\setuparranging[\\v!disable]\n" - elsif getvariable('arrange') then - arrangement = Array.new - if finalrun then - arrangement << "\\v!doublesided" unless getvariable('noduplex') - case printformat - when '' then arrangement << "\\v!normal" - when /.*up/oi then arrangement << "\\v!rotated" - when /.*down/oi then arrangement << ["2DOWN","\\v!rotated"] - when /.*side/oi then arrangement << ["2SIDE","\\v!rotated"] + opt << "\\setupsystem[\\c!n=#{kindofrun},\\c!m=#{currentrun}]\n" + opt << "\\def\\MPOSTformatswitch\{#{prognameflag('metafun')} #{formatflag('mpost')}=\}\n" + if getvariable('batchmode') then + opt << "\\batchmode\n" + end + if getvariable('nonstopmode') then + opt << "\\nonstopmode\n" + end + if getvariable('paranoid') then + opt << "\\def\\maxreadlevel{1}\n" + end + if (str = File.unixfied(getvariable('modefile'))) && ! str.empty? then + opt << "\\readlocfile{#{str}}{}{}\n" + end + if (str = File.unixfied(getvariable('result'))) && ! str.empty? then + opt << "\\setupsystem[file=#{str}]\n" + elsif (str = getvariable('suffix')) && ! str.empty? then + opt << "\\setupsystem[file=#{jobname}.#{str}]\n" + end + opt << "\\setupsystem[\\c!method=2]\n" # 1=oldtexexec 2=newtexexec (obsolete) + opt << "\\setupsystem[\\c!type=#{Tool.ruby_platform()}]\n" + if (str = File.unixfied(getvariable('path'))) && ! str.empty? then + opt << "\\usepath[#{str}]\n" unless str.empty? + end + if (str = getvariable('mainlanguage').downcase) && ! str.empty? && ! str.standard? then + opt << "\\setuplanguage[#{str}]\n" + end + if str = validbackend(getvariable('backend')) then + opt << "\\setupoutput[#{str}]\n" + elsif str = validbackend(getvariable('output')) then + opt << "\\setupoutput[#{str}]\n" + end + if getvariable('color') then + opt << "\\setupcolors[\\c!state=\\v!start]\n" + end + if getvariable('nompmode') || getvariable('nomprun') || getvariable('automprun') then + opt << "\\runMPgraphicsfalse\n" + end + if getvariable('fast') && ! getvariable('fastdisabled') then + opt << "\\fastmode\n" + end + if getvariable('silentmode') then + opt << "\\silentmode\n" + end + if (str = getvariable('separation')) && ! str.empty? then + opt << "\\setupcolors[\\c!split=#{str}]\n" + end + if (str = getvariable('setuppath')) && ! str.empty? then + opt << "\\setupsystem[\\c!directory=\{#{str}\}]\n" + end + if (str = getvariable('paperformat')) && ! str.empty? && ! str.standard? then + if str =~ /^([a-z]+\d+)([a-z]+\d+)$/io then # A5A4 A4A3 A2A1 ... + opt << "\\setuppapersize[#{$1.upcase}][#{$2.upcase}]\n" + else # ...*... + pf = str.upcase.split(/[x\*]/o) + pf << pf[0] if pf.size == 1 + opt << "\\setuppapersize[#{pf[0]}][#{pf[1]}]\n" end - else - arrangement << "\\v!disable" end - opt << "\\setuparranging[#{arrangement.flatten.join(',')}]\n" if arrangement.size > 0 - end - if (str = getvariable('modes')) && ! str.empty? then - opt << "\\enablemode[#{modes}]\n" - end - if (str = getvariable('arguments')) && ! str.empty? then - opt << "\\setupenv[#{str}]\n" - end - if (str = getvariable('randomseed')) && ! str.empty? then - opt << "\\setupsystem[\\c!random=#{str}]\n" - end - if (str = getvariable('input')) && ! str.empty? then - opt << "\\setupsystem[inputfile=#{str}]\n" - else - opt << "\\setupsystem[inputfile=#{rawname}]\n" - end - if (str = getvariable('pages')) && ! str.empty? then - if str.downcase == 'odd' then - opt << "\\chardef\\whichpagetoshipout=1\n" - elsif str.downcase == 'even' then - opt << "\\chardef\\whichpagetoshipout=2\n" + if (str = getvariable('background')) && ! str.empty? then + opt << "\\defineoverlay[whatever][{\\externalfigure[#{str}][\\c!factor=\\v!max]}]\n" + opt << "\\setupbackgrounds[\\v!page][\\c!background=whatever]\n" + end + if getvariable('centerpage') then + opt << "\\setuplayout[\\c!location=\\v!middle,\\c!marking=\\v!on]\n" + end + if getvariable('nomapfiles') then + opt << "\\disablemapfiles\n" + end + if getvariable('noarrange') then + opt << "\\setuparranging[\\v!disable]\n" + elsif getvariable('arrange') then + arrangement = Array.new + if finalrun then + arrangement << "\\v!doublesided" unless getvariable('noduplex') + case getvariable('printformat') + when '' then arrangement << "\\v!normal" + when /.*up/oi then arrangement << ["2UP","\\v!rotated"] + when /.*down/oi then arrangement << ["2DOWN","\\v!rotated"] + when /.*side/oi then arrangement << ["2SIDE","\\v!rotated"] + end + else + arrangement << "\\v!disable" + end + opt << "\\setuparranging[#{arrangement.flatten.join(',')}]\n" if arrangement.size > 0 + end + # we handle both "--mode" and "--modes", else "--mode" is + # mapped onto "--modefile" + if (str = getvariable('modes')) && ! str.empty? then + opt << "\\enablemode[#{str}]\n" + end + if (str = getvariable('mode')) && ! str.empty? then + opt << "\\enablemode[#{str}]\n" + end + if (str = getvariable('arguments')) && ! str.empty? then + opt << "\\setupenv[#{str}]\n" + end + if (str = getvariable('randomseed')) && ! str.empty? then + report("using randomseed #{str}") + opt << "\\setupsystem[\\c!random=#{str}]\n" + end + if (str = getvariable('input')) && ! str.empty? then + opt << "\\setupsystem[inputfile=#{str}]\n" else - pagelist = Array.new - str.split(/\,/).each do |page| - pagerange = page.split(/(\:|\.\.)/o ) - if pagerange.size > 1 then - pagerange.first.to_i.upto(pagerange.last.to_i) do |p| - pagelist << p.to_s + opt << "\\setupsystem[inputfile=#{rawname}]\n" + end + if (str = getvariable('pages')) && ! str.empty? then + if str.downcase == 'odd' then + opt << "\\chardef\\whichpagetoshipout=1\n" + elsif str.downcase == 'even' then + opt << "\\chardef\\whichpagetoshipout=2\n" + else + pagelist = Array.new + str.split(/\,/).each do |page| + pagerange = page.split(/\D+/o) + if pagerange.size > 1 then + pagerange.first.to_i.upto(pagerange.last.to_i) do |p| + pagelist << p.to_s + end + else + pagelist << page end - else - pagelist << page end + opt << "\\def\\pagestoshipout\{#{pagelist.join(',')}\}\n"; end - opt << "\\def\\pagestoshipout\{pagelist.join(',')\}\n"; end + opt << "\\protect\n"; + begin getvariable('filters' ).split(',').uniq.each do |f| opt << "\\useXMLfilter[#{f}]\n" end ; rescue ; end + begin getvariable('usemodules' ).split(',').uniq.each do |m| opt << "\\usemodule[#{m}]\n" end ; rescue ; end + begin getvariable('environments').split(',').uniq.each do |e| opt << "\\environment #{e}\n" end ; rescue ; end + # this will become: + # begin getvariable('environments').split(',').uniq.each do |e| opt << "\\useenvironment[#{e}]\n" end ; rescue ; end + opt << "\\endinput\n" + opt.close + else + report("unable to write option file #{topname}") end - opt << "\\protect\n"; - begin getvariable('filters' ).split(',').uniq.each do |f| opt << "\\useXMLfilter[#{f}]\n" end ; rescue ; end - begin getvariable('usemodules' ).split(',').uniq.each do |m| opt << "\\usemodule[#{m}]\n" end ; rescue ; end - begin getvariable('environments').split(',').uniq.each do |e| opt << "\\environment #{e}\n" end ; rescue ; end - # this will become: - # begin getvariable('environments').split(',').uniq.each do |e| opt << "\\useenvironment[#{e}]\n" end ; rescue ; end - opt << "\\endinput\n" - opt.close + rescue + report("fatal error in writing option file #{topname} (#{$!})") end end @@ -1001,6 +1314,7 @@ class TEX if ENV.key?('SHELL_ESCAPE') && (ENV['SHELL_ESCAPE'] == 'f') then setvariable('automprun',true) end + done = false ['TXRESOURCES','MPRESOURCES','MFRESOURCES'].each do |res| [getvariable('runpath'),getvariable('path')].each do |pat| unless pat.empty? then @@ -1009,12 +1323,47 @@ class TEX else ENV[res] = pat end + report("setting #{res} to #{ENV[res]}") unless done end end + done = true end end + def checktestversion + # + # one can set TEXMFALPHA and TEXMFBETA for test versions + # but keep in mind that the format as well as the test files + # then need the --alpha or --beta flag + # + done, tree = false, '' + ['alpha', 'beta'].each do |what| + if getvariable(what) then + if ENV["TEXMF#{what.upcase}"] then + done, tree = true, ENV["TEXMF#{what.upcase}"] + elsif ENV["TEXMFLOCAL"] then + done, tree = true, File.join(File.dirname(ENV['TEXMFLOCAL']), "texmf-#{what}") + end + end + break if done + end + if done then + tree = tree.strip + ENV['TEXMFPROJECT'] = tree + report("using test tree '#{tree}'") + ['MP', 'MF', 'TX'].each do |ctx| + ENV['CTXDEV#{ctx}PATH'] = '' + end + unless (FileTest.file?(File.join(tree,'ls-r')) || FileTest.file?(File.join(tree,'ls-R'))) then + report("no ls-r/ls-R file for tree '#{tree}' (run: mktexlsr #{tree})") + end + end + # puts `kpsewhich --expand-path=$TEXMF` + # exit + end + def runtex(filename) + checktestversion texengine = validtexengine(getvariable('texengine')) texformat = validtexformat(getarrayvariable('texformats').first) progname = validprogname(getvariable('progname')) @@ -1022,45 +1371,51 @@ class TEX report("tex format: #{texformat}") report("progname: #{progname}") if texengine && texformat && progname then - command = [quoted(texengine),prognameflag(progname),formatflag(texengine,texformat),runoptions(texengine),filename,texprocextras(texformat)].join(' ') - report(command) if getvariable('verbose') - system(command) + fixbackendvars(@@mappaths[texengine]) + runcommand([quoted(texengine),prognameflag(progname),formatflag(texengine,texformat),tcxflag,runoptions(texengine),filename,texprocextras(texformat)]) + # true else false end end - def runmp(filename) + def runmp(mpname,mpx=false) + checktestversion mpsengine = validmpsengine(getvariable('mpsengine')) mpsformat = validmpsformat(getarrayvariable('mpsformats').first) progname = validprogname(getvariable('progname')) if mpsengine && mpsformat && progname then - command = [quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),runoptions(mpsengine),filename,mpsprocextras(mpsformat)].join(' ') - report(command) if getvariable('verbose') - system(command) + ENV["MPXCOMMAND"] = "0" unless mpx + # runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),tcxflag,runoptions(mpsengine),filename,mpsprocextras(mpsformat)]) + runcommand([quoted(mpsengine),formatflag(mpsengine,mpsformat),tcxflag,runoptions(mpsengine),mpname,mpsprocextras(mpsformat)]) + true else false end end - def runtexmp(filename,filetype='') - mpfile = File.suffixed(filename,filetype,'mp') - if File.atleast?(mpfile,25) then + def runtexmp(filename,filetype='',purge=true) + checktestversion + mpname = File.suffixed(filename,filetype,'mp') + if File.atleast?(mpname,25) then # first run needed - File.silentdelete(File.suffixed(mpfile,'mpt')) - doruntexmp(mpfile,false) - mpgraphics = checkmpgraphics(mpfile) - mplabels = checkmplabels(mpfile) + File.silentdelete(File.suffixed(mpname,'mpt')) + doruntexmp(mpname,nil,true,purge) + mpgraphics = checkmpgraphics(mpname) + mplabels = checkmplabels(mpname) if mpgraphics || mplabels then # second run needed - doruntexmp(mpfile,mplabels) + doruntexmp(mpname,mplabels,true,purge) + else + # no labels end end end def runtexmpjob(filename,filetype='') - mpfile = File.suffixed(filename,filetype,'mp') - if File.atleast?(mpfile,25) && (data = File.silentread(mpfile)) then + checktestversion + mpname = File.suffixed(filename,filetype,'mp') + if File.atleast?(mpname,25) && (data = File.silentread(mpname)) then textranslation = if data =~ /^\%\s+translate.*?\=([\w\d\-]+)/io then $1 else '' end mpjobname = if data =~ /collected graphics of job \"(.+?)\"/io then $1 else '' end if ! mpjobname.empty? and File.unsuffixed(filename) =~ /#{mpjobname}/ then # don't optimize @@ -1071,8 +1426,9 @@ class TEX options.push("--translate=#{textranslation}") unless textranslation.empty? options.push("--batch") if getvariable('batchmode') options.push("--nonstop") if getvariable('nonstopmode') - options.push("--output=ps") - return runtexexec(mpfile,options,2) + options.push("--output=ps") # options.push("--dvi") + options.push("--nobackend") + return runtexexec(mpname,options,2) end end return false @@ -1086,7 +1442,7 @@ class TEX begin logger = Logger.new('TeXUtil') if tu = TeXUtil::Converter.new(logger) and tu.loaded(fname) then - tu.saved if tu.processed + ok = tu.processed && tu.saved && tu.finalized end rescue Kpse.runscript('texutil',fname,options) @@ -1095,16 +1451,17 @@ class TEX end end - # 1=tex 2=mptex 3=mpxtex + # 1=tex 2=mptex 3=mpxtex 4=mpgraphic def runtexexec(filename=[], options=[], mode=nil) begin if mode and job = TEX.new(@logger) then options.each do |option| - if option=~ /^\-*(.*?)\=(.*)$/o then - job.setvariable($1,$2) - else - job.setvariable(option,true) + case option + when /^\-*(.*?)\=(.*)$/o then + job.setvariable($1,$2) + when /^\-*(.*?)$/o then + job.setvariable($1,true) end end job.setvariable("files",filename) @@ -1112,6 +1469,7 @@ class TEX when 1 then job.processtex when 2 then job.processmptex when 3 then job.processmpxtex + when 4 then job.processmpgraphic end job.inspect && Kpse.inspect if getvariable('verbose') return true @@ -1124,49 +1482,65 @@ class TEX end def fixbackendvars(backend) - ENV['backend'] = backend ; - ENV['progname'] = backend unless validtexengine(backend) - ENV['TEXFONTMAPS'] = ".;\$TEXMF/fonts/map/{#{backend},pdftex,dvips,}//" + if backend then + 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,}//",'./fonts//'].join_path + else + report("unable to fix backend map path") if getvariable('verbose') + end end def runbackend(rawname) - case validbackend(getvariable('backend')) - when 'dvipdfmx' then - fixbackendvars('dvipdfm') - system("dvipdfmx -d 4 #{File.unsuffixed(rawname)}") - when 'xetex' then - fixbackendvars('xetex') - system("xdv2pdf #{File.suffixed(jrawname,'xdv')}") - when 'dvips' then - fixbackendvars('dvips') - mapfiles = '' - begin - if tuifile = File.suffixed(rawname,'tui') and FileTest.file?(tuifile) then - IO.read(tuifile).scan(/^c \\usedmapfile\{.\}\{(.*?)\}\s*$/o) do - mapfiles += "-u +#{$1} " ; - end + unless getvariable('nobackend') then + case validbackend(getvariable('backend')) + when 'dvipdfmx' then + fixbackendvars('dvipdfm') + runcommand("dvipdfmx -d 4 -V 5 #{File.unsuffixed(rawname)}") + when 'xetex' then + # xetex now runs its own backend + xdvfile = File.suffixed(rawname,'xdv') + if FileTest.file?(xdvfile) then + fixbackendvars('dvipdfm') + runcommand("xdvipdfmx -d 4 -V 5 #{xdvfile}") end - rescue + when 'xdv2pdf' then + xdvfile = File.suffixed(rawname,'xdv') + if FileTest.file?(xdvfile) then + fixbackendvars('xdv2pdf') + runcommand("xdv2pdf #{xdvfile}") + end + when 'dvips' then + fixbackendvars('dvips') mapfiles = '' - end - system("dvips #{mapfiles} #{File.unsuffixed(rawname)}") - when 'pdftex' then - # no need for postprocessing - else - report("no postprocessing needed") + begin + if tuifile = File.suffixed(rawname,'tui') and FileTest.file?(tuifile) then + IO.read(tuifile).scan(/^c \\usedmapfile\{.\}\{(.*?)\}\s*$/o) do + mapfiles += "-u +#{$1} " ; + end + end + rescue + mapfiles = '' + end + runcommand("dvips #{mapfiles} #{File.unsuffixed(rawname)}") + when 'pdftex' then + # no need for postprocessing + else + report("no postprocessing needed") + end end end def processfile takeprecautions - - rawname = getvariable('filename') - - jobname = getvariable('filename') - suffix = getvariable('suffix') - result = getvariable('result') - + report("using search method '#{Kpse.searchmethod}'") if getvariable('verbose') + rawname = getvariable('filename') + jobname = getvariable('filename') + suffix = getvariable('suffix') + result = getvariable('result') + forcexml = getvariable('forcexml') runonce = getvariable('once') finalrun = getvariable('final') || (getvariable('arrange') && ! getvariable('noarrange')) globalfile = getvariable('globalfile') @@ -1188,9 +1562,13 @@ class TEX setvariable('nomprun',true) if orisuffix == 'mpx' # else cylic run + PDFview.setmethod('xpdf') if getvariable('xpdf') + PDFview.closeall if getvariable('autopdf') - forcexml = jobsuffix.match(/^(xml|fo|fox|rlg|exa)$/io) # nil or match + if jobsuffix =~ /^(htm|html|xhtml|xml|fo|fox|rlg|exa)$/io then + forcexml = true + end dummyfile = false @@ -1207,53 +1585,66 @@ class TEX rawname = jobname + '.' + jobsuffix + rawpath = File.dirname(rawname) + rawbase = File.basename(rawname) + unless FileTest.file?(rawname) then inppath.split(',').each do |ip| break if dummyfile = FileTest.file?(File.join(ip,rawname)) end end + if dummyfile || forcexml then + jobsuffix = makestubfile(rawname,rawbase,forcexml) + checkxmlfile(rawname) + end + + # preprocess files - ctx = CtxRunner.new(rawname,@logger) - if getvariable('ctxfile').empty? then - ctx.manipulate(File.suffixed(rawname,'ctx'),'jobname.ctx') - else - ctx.manipulate(File.suffixed(getvariable('ctxfile'),'ctx')) - end - ctx.savelog(File.suffixed(rawname,'ctl')) + unless getvariable('noctx') then + ctx = CtxRunner.new(rawname,@logger) + if getvariable('ctxfile').empty? then + if rawname == rawbase then + ctx.manipulate(File.suffixed(rawname,'ctx'),'jobname.ctx') + else + ctx.manipulate(File.suffixed(rawname,'ctx'),File.join(rawpath,'jobname.ctx')) + end + else + ctx.manipulate(File.suffixed(getvariable('ctxfile'),'ctx')) + end + ctx.savelog(File.suffixed(rawbase,'ctl')) - envs = ctx.environments - mods = ctx.modules + envs = ctx.environments + mods = ctx.modules - # merge environment and module specs + # merge environment and module specs - envs << getvariable('environments') unless getvariable('environments').empty? - mods << getvariable('modules') unless getvariable('modules') .empty? + envs << getvariable('environments') unless getvariable('environments').empty? + mods << getvariable('modules') unless getvariable('modules') .empty? - envs = envs.uniq.join(',') - mods = mods.uniq.join(',') + envs = envs.uniq.join(',') + mods = mods.uniq.join(',') - report("using environments #{envs}") if envs.length > 0 - report("using modules #{mods}") if mods.length > 0 + report("using search method '#{Kpse.searchmethod}'") if getvariable('verbose') + report("using environments #{envs}") if envs.length > 0 + report("using modules #{mods}") if mods.length > 0 - setvariable('environments', envs) - setvariable('modules', mods) + setvariable('environments', envs) + setvariable('modules', mods) + end # end of preprocessing and merging - jobsuffix = makestubfile(rawname,forcexml) if dummyfile || forcexml - if globalfile || FileTest.file?(rawname) then if not dummyfile and not globalfile then scantexpreamble(rawname) scantexcontent(rawname) if getvariable('texformats').standard? end - result = File.suffixed(rawname,suffix) unless suffix.empty? - pushresult(rawname,result) + pushresult(rawbase,result) method = validtexmethod(validtexformat(getvariable('texformats'))) @@ -1263,64 +1654,114 @@ class TEX when 'context' then if getvariable('simplerun') || runonce then - makeoptionfile(rawname,jobname,orisuffix,true,true,3) unless getvariable('nooptionfile') - ok = runtex(rawname) + makeoptionfile(rawbase,jobname,orisuffix,true,true,3,1) unless getvariable('nooptionfile') + ok = runtex(if dummyfile || forcexml then rawbase else rawname end) if ok then - ok = runtexutil(rawname) if getvariable('texutil') || getvariable('forcetexutil') - runbackend(rawname) - popresult(rawname,result) + ok = runtexutil(rawbase) if getvariable('texutil') || getvariable('forcetexutil') + runbackend(rawbase) + popresult(rawbase,result) + end + if getvariable('keep') then + ['top','log','run'].each do |suffix| + File.silentrename(File.suffixed(rawbase,suffix),File.suffixed(rawbase,suffix+'.keep')) + end end - File.silentdelete(File.suffixed(rawname,'tmp')) - File.silentrename(File.suffixed(rawname,'top'),File.suffixed(rawname,'tmp')) else mprundone, ok, stoprunning = false, true, false texruns, nofruns = 0, getvariable('runs').to_i state = FileState.new ['tub','tuo'].each do |s| - state.register(File.suffixed(rawname,s)) + state.register(File.suffixed(rawbase,s)) end if getvariable('automprun') then # check this ['mprun','mpgraph'].each do |s| - state.register(File.suffixed(rawname,s,'mp'),'randomseed') + state.register(File.suffixed(rawbase,s,'mp'),'randomseed') end end while ! stoprunning && (texruns < nofruns) && ok do texruns += 1 report("TeX run #{texruns}") - if texruns == 1 then - makeoptionfile(rawname,jobname,orisuffix,false,false,1) unless getvariable('nooptionfile') - else - makeoptionfile(rawname,jobname,orisuffix,false,false,2) unless getvariable('nooptionfile') + unless getvariable('nooptionfile') then + if texruns == nofruns then + makeoptionfile(rawbase,jobname,orisuffix,false,false,4,texruns) # last + elsif texruns == 1 then + makeoptionfile(rawbase,jobname,orisuffix,false,false,1,texruns) # first + else + makeoptionfile(rawbase,jobname,orisuffix,false,false,2,texruns) # unknown + end end - ok = runtex(File.suffixed(rawname,jobsuffix)) + ok = runtex(File.suffixed(if dummyfile || forcexml then rawbase else rawname end,jobsuffix)) if ok && (nofruns > 1) then unless getvariable('nompmode') then - mprundone = runtexmpjob(rawname, "mpgraph") - mprundone = runtexmpjob(rawname, "mprun") + mprundone = runtexmpjob(rawbase, "mpgraph") + mprundone = runtexmpjob(rawbase, "mprun") end - ok = runtexutil(rawname) + ok = runtexutil(rawbase) state.update stoprunning = state.stable? end end - ok = runtexutil(rawname) if (nofruns == 1) && getvariable('texutil') + if not ok then + setvariable('error','error in tex file') + end + ok = runtexutil(rawbase) if (nofruns == 1) && getvariable('texutil') if ok && finalrun && (nofruns > 1) then - makeoptionfile(rawname,jobname,orisuffix,true,finalrun,4) unless getvariable('nooptionfile') + makeoptionfile(rawbase,jobname,orisuffix,true,finalrun,4,texruns) unless getvariable('nooptionfile') report("final TeX run #{texruns}") - ok = runtex(File.suffixed(rawname,jobsuffix)) + ok = runtex(File.suffixed(if dummyfile || forcexml then rawbase else rawname end,jobsuffix)) end - ['tmp','top'].each do |s| # previous tuo file / runtime option file - File.silentdelete(File.suffixed(rawname,s)) + if getvariable('keep') then + ['top','log','run'].each do |suffix| + File.silentrename(File.suffixed(rawbase,suffix),File.suffixed(rawbase,suffix+'.keep')) + end + else + File.silentrename(File.suffixed(rawbase,'top'),File.suffixed(rawbase,'tmp')) end - File.silentrename(File.suffixed(rawname,'top'),File.suffixed(rawname,'tmp')) + # ['tmp','top','log'].each do |s| # previous tuo file / runtime option file / log file + # File.silentdelete(File.suffixed(rawbase,s)) + # end if ok then - runbackend(rawname) - popresult(rawname,result) + runbackend(rawbase) + popresult(rawbase,result) + end + if true then # autopurge + begin + File.open(File.suffixed(rawbase, 'tuo')) do |f| + ok = 0 + f.each do |line| + case ok + when 1 then + # next line is empty + ok = 2 + when 2 then + if line =~ /^\%\s+\>\s+(.*?)\s+(\d+)/mois then + filename, n = $1, $2 + done = File.delete(filename) rescue false + if done && getvariable('verbose') then + report("deleting #{filename} (#{n} times used)") + end + else + break + end + else + if line =~ /^\%\s+temporary files\:\s+(\d+)/mois then + if $1.to_i == 0 then + break + else + ok = 1 + end + end + end + end + end + rescue + report("fatal error #{$!}") + end end end - Kpse.runscript('ctxtools',rawname,'--purge') if getvariable('purge') - Kpse.runscript('ctxtools',rawname,'--purgeall') if getvariable('purgeall') + Kpse.runscript('ctxtools',rawbase,'--purge') if getvariable('purge') + Kpse.runscript('ctxtools',rawbase,'--purge --all') if getvariable('purgeall') when 'latex' then @@ -1332,63 +1773,61 @@ class TEX end - if (dummyfile or forcexml) and FileTest.file?(rawname) then + if (dummyfile or forcexml) and FileTest.file?(rawbase) then begin - File.delete(File.suffixed(rawname,'run')) + File.delete(File.suffixed(rawbase,'run')) rescue report("unable to delete stub file") end end if ok and getvariable('autopdf') then - PDFview.open(File.suffixed(if result.empty? then rawname else result end,'pdf')) + PDFview.open(File.suffixed(if result.empty? then rawbase else result end,'pdf')) end + else + report("nothing to process") end end - # mp specific + # The labels are collected in the mergebe hash. Here we merge the relevant labels + # into beginfig/endfig. We could as well do this in metafun itself. Maybe some + # day ... (it may cost a bit of string space but that is cheap nowadays). - def doruntexmp(mpname,mergebe=true,context=true) + def doruntexmp(mpname,mergebe=nil,context=true,purge=true) texfound = false - mpbetex = Hash.new - mpfile = File.suffixed(mpname,'mp') - mpcopy = File.suffixed(mpname,'copy','mp') - setvariable('mp.file',mpfile) + mpname = File.suffixed(mpname,'mp') + mpcopy = File.suffixed(mpname,'mp.copy') + mpkeep = File.suffixed(mpname,'mp.keep') + setvariable('mp.file',mpname) setvariable('mp.line','') setvariable('mp.error','') - if mpdata = File.silentread(mpfile) then + if mpdata = File.silentread(mpname) then mpdata.gsub!(/^\#.*\n/o,'') - File.silentrename(mpfile,mpcopy) - texfound = mergebe || mpdata =~ /btex .*? etex/o - if mp = File.silentopen(mpfile,'w') then - mpdata.gsub!(/(btex.*?)\;(.*?etex)/o) do "#{$1}@@@#{$2}" end - mpdata.gsub!(/(\".*?)\;(.*?\")/o) do "#{$1}@@@#{$2}" end - mpdata.gsub!(/\;/o, "\;\n") - mpdata.gsub!(/\n+/o, "\n") - mpdata.gsub!(/(btex.*?)@@@(.*?etex)/o) do "#{$1}\;#{$2}" end - mpdata.gsub!(/(\".*?)@@@(.*?\")/o) do "#{$1};#{$2}" end + File.silentrename(mpname,mpcopy) + texfound = mergebe || (mpdata =~ /btex .*? etex/mo) + if mp = openedfile(mpname) then if mergebe then - mpdata.gsub!(/beginfig\s*\((\d+)\)\s*\;(.*?)endfig\s*\;/o) do + mpdata.gsub!(/beginfig\s*\((\d+)\)\s*\;(.+?)endfig\s*\;/mo) do n, str = $1, $2 - if str =~ /(.*?)(verbatimtex.*?etex)\s*\;(.*)/o then - "beginfig(#{n})\;\n$1$2\;\n#{mpbetex(n)}\n$3\;endfig\;\n" + if str =~ /^(.*?)(verbatimtex.*?etex)\s*\;(.*)$/mo then + "beginfig(#{n})\;\n#{$1}#{$2}\;\n#{mergebe[n]}\n#{$3}\;endfig\;\n" else - "beginfig(#{n})\;\n#{mpbetex(n)}\n#{str}\;endfig\;\n" + "beginfig(#{n})\;\n#{mergebe[n]}\n#{str}\;endfig\;\n" end end + unless mpdata =~ /beginfig\s*\(\s*0\s*\)/o then + mp << mergebe['0'] if mergebe.key?('0') + end end - unless mpdata =~ /beginfig\s*\(\s*0\s*\)/o then - mp << mpbetex[0] if mpbetex.key?(0) - end - mp << mpdata # ?? + mp << MPTools::splitmplines(mpdata) mp << "\n" mp << "end" mp << "\n" mp.close end - processmpx(mpname) if texfound + processmpx(mpname,true,true,purge) if texfound if getvariable('batchmode') then options = ' --interaction=batch' elsif getvariable('nonstopmode') then @@ -1398,7 +1837,7 @@ class TEX end # todo plain|mpost|metafun ok = runmp(mpname) - if f = File.silentopen(File.suffixed(mpfile,'log')) then + if f = File.silentopen(File.suffixed(mpname,'log')) then while str = f.gets do if str =~ /^l\.(\d+)\s(.*?)\n/o then setvariable('mp.line',$1) @@ -1408,32 +1847,63 @@ class TEX end f.close end - File.silentrename(mpfile,"mptrace.tmp") - File.silentrename(mpcopy, mpfile) + File.silentrename(mpname, mpkeep) + File.silentrename(mpcopy, mpname) end end - def processmpx(mpname,context=true) - mpname = File.suffixed(mpname,'mp') - if File.atleast?(mpname,10) && (data = File.silentread(mpname)) then + # todo: use internal mptotext function and/or turn all btex/etex into textexts + + def processmpx(mpname,force=false,context=true,purge=true) + unless force then + mpname = File.suffixed(mpname,'mp') + if File.atleast?(mpname,10) && (data = File.silentread(mpname)) then + if data =~ /(btex|etex|verbatimtex|textext)/o then + force = true + end + end + end + if force then begin - if data =~ /(btex|etex|verbatimtex)/o then - mptex = File.suffixed(mpname,'temp','tex') - mpdvi = File.suffixed(mpname,'temp','dvi') - mplog = File.suffixed(mpname,'temp','log') - mpmpx = File.suffixed(mpname,'temp','mpx') - ok = system("mpto #{mpname} > #{mptex}") - if ok && File.appended(mptex, "\\end\n") then + mptex = File.suffixed(mpname,'temp','tex') + mpdvi = File.suffixed(mpname,'temp','dvi') + mplog = File.suffixed(mpname,'temp','log') + mpmpx = File.suffixed(mpname,'mpx') + File.silentdelete(mptex) + if true then + report("using internal mptotex converter") + ok = MPTools::mptotex(mpname,mptex,'context') + else + command = "mpto #{mpname} > #{mptex}" + report(command) if getvariable('verbose') + ok = system(command) + end + # not "ok && ..." because of potential problem with return code and redirect (>) + if FileTest.file?(mptex) && File.appended(mptex, "\\end\n") then + # to be replaced by runtexexec([filenames],options,1) + if localjob = TEX.new(@logger) then + localjob.setvariable('files',mptex) + localjob.setvariable('backend','dvips') + localjob.setvariable('engine',getvariable('engine')) unless getvariable('engine').empty? + localjob.setvariable('once',true) + localjob.setvariable('nobackend',true) if context then - ok = RunConTeXtFile(mptex) + localjob.setvariable('texformats',[getvariable('interface')]) unless getvariable('interface').empty? + elsif getvariable('interface').empty? then + localjob.setvariable('texformats',['plain']) else - ok = RunSomeTeXFile(mptex) - end - ok = ok && FileTest.file?(mpdvi) && system("dvitomp #{mpdvi} #{mpmpx}") - [mptex,mpdvi,mplog].each do |mpfil| - File.silentdelete(mpfil) + localjob.setvariable('texformats',[getvariable('interface')]) end + localjob.processtex + ok = true # todo + else + ok = false end + # so far + command = "dvitomp #{mpdvi} #{mpmpx}" + report(command) if getvariable('verbose') + ok = ok && FileTest.file?(mpdvi) && system(command) + purge_mpx_files(mpname) if purge end rescue # error in processing mpx file @@ -1441,6 +1911,14 @@ class TEX end end + def purge_mpx_files(mpname) + unless getvariable('keep') then + ['tex', 'log', 'tui', 'tuo', 'top'].each do |suffix| + File.silentdelete(File.suffixed(mpname,'temp',suffix)) + end + end + end + def checkmpgraphics(mpname) mpoptions = '' if getvariable('makempy') then @@ -1452,12 +1930,12 @@ class TEX mponame = File.suffixed(mpname,'mpo') mpyname = File.suffixed(mpname,'mpy') return false unless File.atleast?(mponame,32) - mpochecksum = State.new.checksum(mponame) + mpochecksum = FileState.new.checksum(mponame) return false if mpochecksum.empty? # where does the checksum get into the file? # maybe let texexec do it? # solution: add one if not present or update when different - if f = File.open(mpyname) then + if f = File.silentopen(mpyname) then str = f.gets.chomp f.close if str =~ /^\%\s*mpochecksum\s*\:\s*(\d+)/o then @@ -1470,24 +1948,25 @@ class TEX def checkmplabels(mpname) mpname = File.suffixed(mpname,'mpt') - if File.atleast?(mpname,10) && (mp = File.open(mpname)) then + if File.atleast?(mpname,10) && (mp = File.silentopen(mpname)) then labels = Hash.new while str = mp.gets do - if str =~ /%\s*setup\s*:\s*(.*)/o then - t = $1 - else - t = '' - end - if str =~ /%\s*figure\s*(\d+)\s*:\s*(.*)/o then + t = if str =~ /^%\s*setup\s*:\s*(.*)$/o then $1 else '' end + if str =~ /^%\s*figure\s*(\d+)\s*:\s*(.*)$/o then + labels[$1] = labels[$1] || '' unless t.empty? then labels[$1] += "#{t}\n" t = '' end - labels[$1] += "$2\n" + labels[$1] += "#{$2}\n" end end mp.close - return labels if labels.size>0 + if labels.size>0 then + return labels + else + return nil + end end return nil end diff --git a/Master/texmf-dist/scripts/context/ruby/base/texutil.rb b/Master/texmf-dist/scripts/context/ruby/base/texutil.rb index c279bcc97a6..2bca831675e 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/texutil.rb +++ b/Master/texmf-dist/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,17 @@ class TeXUtil begin eval("#{p}").processor(@logger) rescue Exception - @logger.report("fatal error in plugin processor #{p} (#{$!})") + report("processing") + end + end + end + + def finalizers + @plugins.each do |p| + begin + eval("#{p}").finalizer(@logger) + rescue Exception + report("finalizing") end end end @@ -126,6 +145,8 @@ class TeXUtil class Sorter + @@downcase = true + def initialize(max=12) @rep, @map, @exp, @div = Hash.new, Hash.new, Hash.new, Hash.new @max = max @@ -149,7 +170,7 @@ class TeXUtil # sorter.expander('ijligature', 'y') def expander(from,to=nil) - from, to = converted(from), converted(to) + to = converted(to) # not from !!! @max = [@max,to.length+1].max if to @exp[from] = to || from || '' end @@ -179,16 +200,50 @@ class TeXUtil end if @map.size > 0 then # watch out, order of match matters - @rexb = /(\\[a-zA-Z]+|#{@map.keys.join('|')}|.)\s*/ # o + if @@downcase then + @rexb = /(\\[a-zA-Z]+|#{@map.keys.join('|')}|.)\s*/i # o + else + @rexb = /(\\[a-zA-Z]+|#{@map.keys.join('|')}|.)\s*/ # o + end else - @rexb = /(\\[a-zA-Z]+|.)\s*/o + if @@downcase then + @rexb = /(\\[a-zA-Z]+|.)\s*/io + else + @rexb = /(\\[a-zA-Z]+|.)\s*/o + end + end + if false then + @exp.keys.each do |e| + @exp[e].downcase! + end end end + def replace(str) + if @rexa then + str.gsub(@rexa) do + @rep[$1.escaped] + end + else + str + end + end + + def normalize(str) + # replace(str).gsub(/ +/,' ') + replace(str).gsub(/\s\s+/," \\space") + end + + def tokenize(str) + str.gsub(/\\strchr\{(.*?)\}/o) do "\\#{$1}" end + end + def remap(str) s = str.dup - s.gsub!(/(\d+)/o) do - $1.rjust(10,'a') # rest is b .. k + 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 @@ -198,6 +253,9 @@ class TeXUtil if @rexb then s.gsub!(@rexb) do token = $1.sub(/\\/o, '') + if @@downcase then + token.downcase! + end if @exp.key?(token) then @exp[token].ljust(@max,' ') elsif @map.key?(token) then @@ -210,17 +268,16 @@ class TeXUtil s end - def preset(shortcuts=[],expansions=[],reductions=[],divisions=[]) - # maybe we should move this to sort-def.tex + def preset(shortcuts=[],expansions=[],reductions=[],divisions=[],language='') 'a'.upto('z') do |c| expander(c) ; division(c) end + 'A'.upto('Z') do |c| expander(c) ; division(c) end expander('1','b') ; expander('2','c') ; expander('3','e') ; expander('4','f') expander('5','g') ; expander('6','h') ; expander('7','i') ; expander('8','i') expander('9','j') ; expander('0','a') ; expander('-','-') ; - # end potential move - shortcuts.each do |s| shortcut(s[0],s[1]) end - expansions.each do |e| expander(e[0],e[1]) end - reductions.each do |r| reducer(r[0],r[1]) end - divisions.each do |d| division(d[0],d[1]) end + shortcuts.each do |s| shortcut(s[1],s[2]) if s[0] == '' || s[0] == language end + expansions.each do |e| expander(e[1],e[2]) if e[0] == '' || e[0] == language end + reductions.each do |r| reducer(r[1],r[2]) if r[0] == '' || r[0] == language end + divisions.each do |d| division(d[1],d[2]) if d[0] == '' || d[0] == language end end def simplify(str) @@ -240,7 +297,7 @@ class TeXUtil # what to do with xml and utf-8 # \"e etc # unknown \cs - s.gsub!(/\\[a-z][a-z]+\s*\{(.*?)\}/o) do $1 end + s.gsub!(/\\[a-zA-Z][a-zA-Z]+\s*\{(.*?)\}/o) do $1 end return s end @@ -256,6 +313,7 @@ class TeXUtil def converted(str) if str then + # puts str str.gsub(/([\+\-]*\d+)/o) do n = $1.to_i if n > 0 then @@ -277,20 +335,18 @@ class TeXUtil module MyFiles - @@files = Hash.new + @@files, @@temps = Hash.new, Hash.new def MyFiles::reset(logger) - @@files = Hash.new + @@files, @@temps = Hash.new, Hash.new end def MyFiles::reader(logger,data) case data[0] when 'b', 'e' then - if @@files.key?(data[1]) then - @@files[data[1]] += 1 - else - @@files[data[1]] = 1 - end + @@files[data[1]] = (@@files[data[1]] ||0) + 1 + when 't' then # temporary file + @@temps[data[1]] = (@@temps[data[1]] ||0) + 1 end end @@ -299,14 +355,24 @@ class TeXUtil @@files.keys.sort.each do |k| handle << "% > #{k} #{@@files[k]/2}\n" end + handle << logger.banner("temporary files: #{@@temps.size}") + @@temps.keys.sort.each do |k| + handle << "% > #{k} #{@@temps[k]}\n" + end end def MyFiles::processor(logger) @@files.keys.sort.each do |k| unless (@@files[k] % 2) == 0 then - logger.report("check loading of file #{k}, begin/end problem") + logger.report("check loading of file '#{k}', begin/end problem") end end + @@temps.keys.sort.each do |k| + # logger.report("temporary file '#{k}' can be deleted") + end + end + + def MyFiles::finalizer(logger) end end @@ -337,6 +403,9 @@ class TeXUtil def MyCommands::processor(logger) end + def MyCommands::finalizer(logger) + end + end end @@ -367,7 +436,15 @@ class TeXUtil def MyExtras::processor(logger) @@programs.each do |p| - cmd = "texmfstart #{@@programs[p.to_i]}" + # cmd = @@programs[p.to_i] + # logger.report("running #{cmd}") + # system(cmd) + end + end + + def MyExtras::finalizer(logger) + @@programs.each do |p| + cmd = @@programs[p.to_i] logger.report("running #{cmd}") system(cmd) end @@ -395,7 +472,8 @@ class TeXUtil attr_writer :sortkey def build(sorter) - @sortkey = sorter.remap(sorter.simplify(@key.downcase)) + @sortkey = sorter.normalize(sorter.tokenize(@sortkey)) + @sortkey = sorter.remap(sorter.simplify(@key.downcase)) # ?? if @sortkey.empty? then @sortkey = sorter.remap(@command.downcase) end @@ -418,16 +496,23 @@ class TeXUtil end - @@synonyms = Hash.new + @@synonyms = Hash.new + @@sorter = Hash.new + @@languages = Hash.new def MySynonyms::reset(logger) - @@synonyms = Hash.new + @@synonyms = Hash.new + @@sorter = Hash.new + @@languages = Hash.new end def MySynonyms::reader(logger,data) - if data[0] == 'e' then - @@synonyms[data[1]] = Array.new unless @@synonyms.key?(data[1]) - @@synonyms[data[1]].push(Synonym.new(data[1],data[2],data[3],data[4])) + case data[0] + when 'e' then + @@synonyms[data[1]] = Array.new unless @@synonyms.key?(data[1]) + @@synonyms[data[1]].push(Synonym.new(data[1],data[2],data[3],data[4])) + when 'l' then + @@languages[data[1]] = data[2] || '' end end @@ -441,17 +526,25 @@ class TeXUtil end def MySynonyms::processor(logger) - sorter = Sorter.new - sorter.preset(eval("MyKeys").shortcuts,eval("MyKeys").expansions,eval("MyKeys").reductions,eval("MyKeys").divisions) - sorter.prepare @@synonyms.keys.each do |s| + @@sorter[s] = Sorter.new + @@sorter[s].preset( + eval("MyKeys").shortcuts, + eval("MyKeys").expansions, + eval("MyKeys").reductions, + eval("MyKeys").divisions, + @@languages[s] || '') + @@sorter[s].prepare @@synonyms[s].each_index do |i| - @@synonyms[s][i].build(sorter) + @@synonyms[s][i].build(@@sorter[s]) end @@synonyms[s] = @@synonyms[s].sort end end + def MySynonyms::finalizer(logger) + end + end end @@ -462,6 +555,9 @@ class TeXUtil class Register + @@specialsymbol = "\000" + @@specialbanner = "" # \\relax" + @@debug = false @@debug = true @@ -475,6 +571,7 @@ class TeXUtil @key = @entry.dup if @key.empty? @sortkey = @key.dup @nofentries, @nofpages = 0, 0 + @normalizeentry = false end attr_reader :state, :type, :location, :key, :entry, :seetoo, :page, :realpage, :texthowto, :pagehowto @@ -482,34 +579,55 @@ class TeXUtil attr_writer :sortkey def build(sorter) - @entry, @key = [@entry, @key].collect do |target| - # +a+b+c &a&b&c a+b+c a&b&c - case target[0,1] - when '&' then target = target.sub(/^./o,'').gsub(/([^\\])\&/o) do "#{$1}#{@@split}" end - when '+' then target = target.sub(/^./o,'').gsub(/([^\\])\+/o) do "#{$1}#{@@split}" end - else target = target .gsub(/([^\\])[\&\+]/o) do "#{$1}#{@@split}" end + # @entry, @key = sorter.normalize(@entry), sorter.normalize(sorter.tokenize(@key)) + @entry = sorter.normalize(sorter.tokenize(@entry)) if @normalizeentry + @key = sorter.normalize(sorter.tokenize(@key)) + if false then + @entry, @key = [@entry, @key].collect do |target| + # +a+b+c &a&b&c a+b+c a&b&c + case target[0,1] + when '&' then target = target.sub(/^./o,'').gsub(/([^\\])\&/o) do "#{$1}#{@@split}" end + when '+' then target = target.sub(/^./o,'').gsub(/([^\\])\+/o) do "#{$1}#{@@split}" end + else target = target .gsub(/([^\\])[\&\+]/o) do "#{$1}#{@@split}" end + end + # {a}{b}{c} + # if target =~ /^\{(.*)\}$/o then + # $1.split(/\} \{/o).join(@@split) # space between } { is mandate + # else + target + # end end - # {a}{b}{c} - # if target =~ /^\{(.*)\}$/o then - # $1.split(/\} \{/o).join(@@split) # space between } { is mandate - # else - target - # end + else + @entry, @key = cleanupsplit(@entry), cleanupsplit(@key) end @sortkey = sorter.simplify(@key) + # special = @sortkey =~ /^([^a-zA-Z\\])/o + special = @sortkey =~ /^([\`\~\!\@\#\$\%\^\&\*\(\)\_\-\+\=\{\}\[\]\:\;\"\'\|\<\,\>\.\?\/\d])/o @sortkey = @sortkey.split(@@split).collect do |c| sorter.remap(c) end.join(@@split) - # if ($Key eq "") { $Key = SanitizedString($Entry) } - # if ($ProcessHigh){ $Key = HighConverted($Key) } + if special then + @sortkey = "#{@@specialsymbol}#{@sortkey}" + end @sortkey = [ @sortkey.downcase, @sortkey, + @entry, @texthowto.ljust(10,' '), - @state, - @realpage.rjust(6,' '), + # @state, # no, messes up things + (@realpage ||'').rjust(6,' ').gsub(/0/,' '), + # (@realpage ||'').rjust(6,' '), @pagehowto ].join(@@split) end + def cleanupsplit(target) + # +a+b+c &a&b&c a+b+c a&b&c + case target[0,1] + when '&' then target.sub(/^./o,'').gsub(/([^\\])\&/o) do "#{$1}#{@@split}" end + when '+' then target.sub(/^./o,'').gsub(/([^\\])\+/o) do "#{$1}#{@@split}" end + else target .gsub(/([^\\])[\&\+]/o) do "#{$1}#{@@split}" end + end + end + def <=> (other) @sortkey <=> other.sortkey end @@ -555,18 +673,20 @@ class TeXUtil else testalpha = entry.sortkey[0,1].downcase end - if testalpha != alpha.downcase or alphaclass != entry.class then + if (testalpha != alpha.downcase) || (alphaclass != entry.class) then alpha = testalpha alphaclass = entry.class if alpha != ' ' then flushsavedline(handle) if alpha =~ /^[a-zA-Z]$/o then character = alpha.dup + elsif alpha == @@specialsymbol then + character = @@specialbanner elsif alpha.length > 1 then # character = "\\getvalue\{#{alpha}\}" character = "\\#{alpha}" else - character = "\\#{alpha}" + character = "\\unknown" end handle << "\\registerentry{#{entry.type}}{#{character}}\n" end @@ -614,6 +734,7 @@ class TeXUtil Register.flushsavedline(handle) handle << "\\registersee{#{entry.type}}{#{entry.pagehowto},#{entry.texthowto}}{#{entry.seetoo}}{#{entry.page}}\n" ; lastpage, lastrealpage = entry.page, entry.realpage + copied = false # no page ! elsif @@savedhowto != entry.pagehowto and ! entry.pagehowto.empty? then @@savedhowto = entry.pagehowto end @@ -648,11 +769,13 @@ class TeXUtil end @@registers = Hash.new - @@sorter = Sorter.new + @@sorter = Hash.new + @@languages = Hash.new def MyRegisters::reset(logger) @@registers = Hash.new - @@sorter = Sorter.new + @@sorter = Hash.new + @@languages = Hash.new end def MyRegisters::reader(logger,data) @@ -669,6 +792,8 @@ class TeXUtil when 's' then @@registers[data[1]] = Array.new unless @@registers.key?(data[1]) @@registers[data[1]].push(Register.new(4,data[1],data[2],data[3],data[4],data[5],data[6],nil)) + when 'l' then + @@languages[data[1]] = data[2] || '' end end @@ -676,23 +801,32 @@ class TeXUtil if @@registers.size > 0 then @@registers.keys.sort.each do |s| handle << logger.banner("registers: #{s} #{@@registers[s].size}") - Register.flush(@@registers[s],handle,@@sorter) + Register.flush(@@registers[s],handle,@@sorter[s]) # report("register #{@@registers[s].class}: #{@@registers[s].@nofentries} entries and #{@@registers[s].@nofpages} pages") end end end def MyRegisters::processor(logger) - @@sorter.preset(eval("MyKeys").shortcuts,eval("MyKeys").expansions,eval("MyKeys").reductions,eval("MyKeys").divisions) - @@sorter.prepare @@registers.keys.each do |s| + @@sorter[s] = Sorter.new + @@sorter[s].preset( + eval("MyKeys").shortcuts, + eval("MyKeys").expansions, + eval("MyKeys").reductions, + eval("MyKeys").divisions, + @@languages[s] || '') + @@sorter[s].prepare @@registers[s].each_index do |i| - @@registers[s][i].build(@@sorter) + @@registers[s][i].build(@@sorter[s]) end @@registers[s] = @@registers[s].sort end end + def MyRegisters::finalizer(logger) + end + end end @@ -730,6 +864,10 @@ class TeXUtil @@plugins.processors if @@plugins end + def MyPlugins::finalizer(logger) + @@plugins.finalizers if @@plugins + end + end end @@ -764,7 +902,7 @@ class TeXUtil def MyKeys::reader(logger,data) key = data.shift - grp = data.shift # language code, todo + # grp = data.shift # language code, todo case key when 's' then @@shortcuts.push(data) when 'e' then @@expansions.push(data) @@ -783,6 +921,9 @@ class TeXUtil logger.report("divisions : #{@@divisions.size}") # logger.report(@@divisions.inspect) end + def MyKeys::finalizer(logger) + end + end end @@ -816,24 +957,31 @@ class TeXUtil def loaded(filename) begin - report("parsing file #{filename}") - if f = open(File.suffixed(filename,'tui')) then - f.each do |line| - case line.chomp - when /^f (.*)$/o then @plugins.reader('MyFiles', $1.splitdata) - when /^c (.*)$/o then @plugins.reader('MyCommands', [$1]) - when /^e (.*)$/o then @plugins.reader('MyExtras', $1.splitdata) - when /^s (.*)$/o then @plugins.reader('MySynonyms', $1.splitdata) - when /^r (.*)$/o then @plugins.reader('MyRegisters',$1.splitdata) - when /^p (.*)$/o then @plugins.reader('MyPlugins', $1.splitdata) - when /^x (.*)$/o then @plugins.reader('MyKeys', $1.splitdata) - else report("unknown entry #{line[0,1]} in line #{line.chomp}") + tuifile = File.suffixed(filename,'tui') + if FileTest.file?(tuifile) then + report("parsing file #{tuifile}") + if f = open(tuifile) then + f.each do |line| + case line.chomp + when /^f (.*)$/o then @plugins.reader('MyFiles', $1.splitdata) + when /^c (.*)$/o then @plugins.reader('MyCommands', [$1]) + when /^e (.*)$/o then @plugins.reader('MyExtras', $1.splitdata) + when /^s (.*)$/o then @plugins.reader('MySynonyms', $1.splitdata) + when /^r (.*)$/o then @plugins.reader('MyRegisters',$1.splitdata) + when /^p (.*)$/o then @plugins.reader('MyPlugins', $1.splitdata) + when /^x (.*)$/o then @plugins.reader('MyKeys', $1.splitdata) + when /^r (.*)$/o then # nothing, not handled here + else + # report("unknown entry #{line[0,1]} in line #{line.chomp}") + end end + f.close end - f.close + else + report("unable to locate #{tuifile}") end rescue - report("fatal error in parsing #{filename}") + report("fatal error in parsing #{tuifile}") @filename = 'texutil' else @filename = filename @@ -848,19 +996,27 @@ class TeXUtil def saved(filename=@filename) if @fatalerror then report("fatal error, no tuo file saved") + return false else - begin + begin if f = File.open(File.suffixed(filename,'tuo'),'w') then @plugins.writers(f) f.close end rescue report("fatal error when saving file (#{$!})") + return false else report("tuo file saved") + return true end end + end + + def finalized + @plugins.finalizers @plugins.resets + return true # for the moment end def reset diff --git a/Master/texmf-dist/scripts/context/ruby/base/tool.rb b/Master/texmf-dist/scripts/context/ruby/base/tool.rb index 7f40e773d0c..9f120ca117a 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/tool.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/tool.rb @@ -132,7 +132,7 @@ module Tool return old unless test(?f,old) new = old.downcase - new.gsub!(/[^A-Za-z0-9\-\.\\\/]/o) do # funny chars + new.gsub!(/[^A-Za-z0-9\_\-\.\\\/]/o) do # funny chars '-' end if old =~ /[a-zA-Z]\:/o diff --git a/Master/texmf-dist/scripts/context/ruby/base/variables.rb b/Master/texmf-dist/scripts/context/ruby/base/variables.rb index 5cbc5ba538e..403b57716f4 100644 --- a/Master/texmf-dist/scripts/context/ruby/base/variables.rb +++ b/Master/texmf-dist/scripts/context/ruby/base/variables.rb @@ -12,6 +12,89 @@ require 'base/tool' +class Hash + + def nothing?(id) + ! self[id] || self[id].empty? + end + + def subset(pattern) + h = Hash.new + p = pattern.gsub(/([\.\:\-])/) do "\\#{$1}" end + r = /^#{p}/ + self.keys.each do |k| + h[k] = self[k].dup if k =~ r + end + return h + end + +end + +class ExtendedHash < Hash + + @@re_var_a = /\%(.*?)\%/ + @@re_var_b = /\$\((.*?)\)/ + + def set(key,value='',resolve=true) + if value then + self[key] = if resolve then resolved(value.to_s) else value.to_s end + else + self[key] = '' + end + end + + def replace(key,value='') + self[key] = value if self?(key) + end + + def get(key,default='') + if self.key?(key) then self[key] else default end + end + + def true?(key) + self[key] =~ /^(yes|on|true|enable|enabled|y|start)$/io rescue false + end + + def resolved(str) + begin + str.to_s.gsub(@@re_var_a) do + self[$1] || '' + end.gsub(@@re_var_b) do + self[$1] || '' + end + rescue + str.to_s rescue '' + end + end + + def check(key,default='') + if self.key?(key) then + if self[key].empty? then self[key] = (default || '') end + else + self[key] = (default || '') + end + end + + def checked(key,default='') + if self.key?(key) then + if self[key].empty? then default else self[key] end + else + default + end + end + + def empty?(key) + self[key].empty? + end + + # def downcase(key) + # self[key].downcase! + # end + +end + +# the next one is obsolete so we need to replace things + module Variables def setvariable(key,value='') @@ -26,6 +109,10 @@ module Variables if @variables.key?(key) then @variables[key] else default end end + def truevariable(key) + @variables[key] =~ /^(yes|on|true)$/io rescue false + end + def checkedvariable(str,default='') if @variables.key?(key) then if @variables[key].empty? then default else @variables[key] end diff --git a/Master/texmf-dist/scripts/context/ruby/ctxtools.rb b/Master/texmf-dist/scripts/context/ruby/ctxtools.rb index 94e6e735a20..0cf96e10d70 100644 --- a/Master/texmf-dist/scripts/context/ruby/ctxtools.rb +++ b/Master/texmf-dist/scripts/context/ruby/ctxtools.rb @@ -15,9 +15,38 @@ # todo: move scite here # -# todo: move kpse call to kpse class/module +# todo: move kpse call to kpse class/module, faster and better -banner = ['CtxTools', 'version 1.2.2', '2004/2005', 'PRAGMA ADE/POD'] +# Taco Hoekwater on patterns and lig building (see 'agr'): +# +# Any direct use of a ligature (as accessed by \char or through active +# characters) is wrong and will create faulty hyphenation. Normally, +# when TeX sees "office", it has six tokens, and it knows from the +# patterns that it can hyphenate between the "ff". It will build an +# internal list of four nodes, like this: +# +# [char, o , ffi ] +# [lig , ffi, c ,[f,f,i]] +# [char, c , e ] +# [char, e , NULL] +# +# as you can see from the ffi line, it has remembered the original +# characters. While hyphenating, it temporarily changes back to +# that, then re-instates the ligature afterwards. +# +# If you feed it the ligature directly, like so: +# +# [char, o , ffi ] +# [char, ffi , c ] +# [char, c , e ] +# [char, e , NULL] +# +# it cannot do that (it tries to hyphenate as if the "ffi" was a +# character), and the result is wrong hyphenation. + +banner = ['CtxTools', 'version 1.3.3', '2004/2006', 'PRAGMA ADE/POD'] + +# todo dirname unless defined? ownpath ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') @@ -29,6 +58,7 @@ require 'base/logger' require 'base/system' require 'rexml/document' +require 'net/http' require 'ftools' require 'kconv' @@ -84,24 +114,32 @@ class Commands end end unless maincontextfile.empty? then + nextcontextfile = maincontextfile.sub(/context\.tex$/,"cont-new.tex") case action - when 1 then touchfile(maincontextfile) - when 2 then reportversion(maincontextfile) + when 1 then + touchfile(maincontextfile) + touchfile(nextcontextfile,@@newcontextversion) + when 2 then + reportversion(maincontextfile) + reportversion(nextcontextfile,@@newcontextversion) end end end - def touchfile(filename) + @@contextversion = "\\\\contextversion" + @@newcontextversion = "\\\\newcontextversion" + + def touchfile(filename,command=@@contextversion) if FileTest.file?(filename) then if data = IO.read(filename) then - timestamp = Time.now.strftime('%Y.%m.%d') + timestamp = Time.now.strftime('%Y.%m.%d %H:%M') prevstamp = '' begin - data.gsub!(/\\contextversion\{(\d+\.\d+\.\d+)\}/) do + data.gsub!(/#{command}\{(\d+\.\d+\.\d+.*?)\}/) do prevstamp = $1 - "\\contextversion{#{timestamp}}" + "#{command.sub(/(\\)+/,"\\")}{#{timestamp}}" end rescue else @@ -133,11 +171,11 @@ class Commands end - def reportversion(filename) + def reportversion(filename,command=@@contextversion) version = 'unknown' begin - if FileTest.file?(filename) && IO.read(filename).match(/\\contextversion\{(\d+\.\d+\.\d+)\}/) then + if FileTest.file?(filename) && IO.read(filename).match(/#{command}\{(\d+\.\d+\.\d+.*?)\}/) then version = $1 end rescue @@ -145,7 +183,7 @@ class Commands if @commandline.option("pipe") then print version else - report("context version: #{version}") + report("context version: #{version} (#{filename})") end end @@ -183,7 +221,7 @@ class Commands interfaces = @commandline.arguments if interfaces.empty? then - interfaces = ['en', 'cz','de','it','nl','ro'] + interfaces = ['en','cz','de','it','nl','ro','fr'] end interfaces.each do |interface| @@ -281,7 +319,7 @@ class Commands interfaces = @commandline.arguments if interfaces.empty? then - interfaces = ['cz','de','it','nl','ro'] + interfaces = ['cz','de','it','nl','ro','fr'] else interfaces.delete('en') end @@ -353,6 +391,10 @@ class Commands data.i_translate('cd:inherit' , 'name' , strings) # data.i_translate('cd:command' , 'name' , strings) + data.gsub!(/(\<cd\:interface[^\>]*?language=")en(")/) do + $1 + interface + $2 + end + report("saving #{outfile}") begin @@ -375,13 +417,19 @@ class Commands public - def purgefiles(all=false) + def purgefiles pattern = @commandline.arguments - purgeall = @commandline.option("all") || all + purgeall = @commandline.option("all") recurse = @commandline.option("recurse") $dontaskprefixes.push(Dir.glob("mpx-*")) + + if purgeall then + $dontaskprefixes.push(Dir.glob("*.tex.prep")) + $dontaskprefixes.push(Dir.glob("*.xml.prep")) + end + $dontaskprefixes.flatten! $dontaskprefixes.sort! @@ -394,7 +442,7 @@ class Commands if ! pattern || pattern.empty? then globbed = if recurse then "**/*.*" else "*.*" end files = Dir.glob(globbed) - report("purging files : #{globbed}") + report("purging#{if purgeall then ' all' end} temporary files : #{globbed}") else pattern.each do |pat| globbed = if recurse then "**/#{pat}-*.*" else "#{pat}-*.*" end @@ -402,7 +450,7 @@ class Commands globbed = if recurse then "**/#{pat}.*" else "#{pat}.*" end files.push(Dir.glob(globbed)) end - report("purging files : #{pattern.join(' ')}") + report("purging#{if purgeall then ' all' end} temporary files : #{pattern.join(' ')}") end files.flatten! files.sort! @@ -428,6 +476,9 @@ class Commands end end end + $dummyfiles.each do |file| + (File.delete(file) if (FileTest.size?(file) rescue 10) < 10) rescue false + end $texnonesuffixes.each do |suffix| files.each do |file| if file =~ /(.*)\.#{suffix}$/i then @@ -461,10 +512,6 @@ class Commands end - def purgeallfiles - purgefiles(true) # for old times sake - end - private $removedfiles = 0 @@ -486,7 +533,9 @@ class Commands $forsuresuffixes = [ "tui", "tup", "ted", "tes", "top", "log", "tmp", "run", "bck", "rlg", - "mpt", "mpx", "mpd", "mpo" + "mpt", "mpx", "mpd", "mpo", "mpb", + "ctl", + "tmp.md5", "tmp.out" ] $texonlysuffixes = [ "dvi", "ps", "pdf" @@ -494,6 +543,9 @@ class Commands $texnonesuffixes = [ "tuo", "tub", "top" ] + $dummyfiles = [ + "mpgraph" + ] def removecontextfile (filename) if filename && FileTest.file?(filename) then @@ -607,7 +659,7 @@ class Commands str.chomp! str.sub!(/\s*$/o, '') case str - when /^[%\#]D/io then + when /^[%\#]D($| )/io then if skiplevel == 0 then someline = if str.length < 3 then "" else str[3,str.length-1] end if indocument then @@ -625,7 +677,7 @@ class Commands nofdocuments += 1 end end - when /^[%\#]M/io then + when /^[%\#]M($| )/io then if skiplevel == 0 then someline = if str.length < 3 then "" else str[3,str.length-1] end ted.puts("#{someline}\n") @@ -642,20 +694,25 @@ class Commands else if skiplevel == 0 then inlocaldocument = indocument + inlocaldocument = false # else first line skipped when not empty someline = str if indocument then ted.puts("\\stopdocumentation\n") indocument = false end - if someline.empty? && indefinition then - ted.puts("\\stopdefinition\n") - indefinition = false - elsif indefinition then - ted.puts("#{someline}\n") + if indefinition then + if someline.empty? then + ted.puts("\\stopdefinition\n") + indefinition = false + else + ted.puts("#{someline}\n") + end elsif ! someline.empty? then ted.puts("\n\\startdefinition\n") indefinition = true - unless inlocaldocument then + if inlocaldocument then + # nothing + else nofdefinitions += 1 ted.puts("#{someline}\n") end @@ -742,6 +799,56 @@ end # # Maybe I'll make this hyptools.tex +class String + + def markbraces + level = 0 + self.gsub(/([\{\}])/o) do |chr| + if chr == '{' then + level = level + 1 + chr = "((+#{level}))" + elsif chr == '}' then + chr = "((-#{level}))" + level = level - 1 + end + chr + end + end + + def unmarkbraces + self.gsub(/\(\(\+\d+?\)\)/o) do + "{" + end .gsub(/\(\(\-\d+?\)\)/o) do + "}" + end + end + + def getargument(pattern) + if self =~ /(#{pattern})\s*\(\(\+(\d+)\)\)(.*?)\(\(\-\2\)\)/m then # no /o + return $3 + else + return "" + end + end + + def withargument(pattern, &block) + if self.markbraces =~ /^(.*)(#{pattern}\s*)\(\(\+(\d+)\)\)(.*?)\(\(\-\3\)\)(.*)$/m then # no /o + "#{$1.unmarkbraces}#{$2}{#{yield($4.unmarkbraces)}}#{$5.unmarkbraces}" + else + self + end + end + + def filterargument(pattern, &block) + if self.markbraces =~ /^(.*)(#{pattern}\s*)\(\(\+(\d+)\)\)(.*?)\(\(\-\3\)\)(.*)$/m then # no /o + yield($4.unmarkbraces) + else + self + end + end + +end + class Language include CommandBase @@ -751,6 +858,7 @@ class Language @language = language @filenames = filenames @remapping = Array.new + @demapping = Array.new @unicode = Hash.new @encoding = encoding @data = '' @@ -758,8 +866,11 @@ class Language preload_accents() preload_unicode() if @commandline.option('utf8') case @encoding.downcase - when 't1', 'ec', 'cork' then preload_vector('ec') - when 'y', 'texnansi' then preload_vector('texnansi') + when 't1', 'ec', 'cork' then preload_vector('ec', 'enco-ec.tex') + when 'y', 'texnansi' then preload_vector('texnansi', 'enco-ans.tex') + when 'agr', 'agreek' then preload_vector('agr', 'enco-agr.tex') + when 't2a' then preload_vector('t2a', 'enco-cyr.tex') + when 'cyr' then preload_vector() # somehow loading t2a does not work out well end end @@ -774,6 +885,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 @@ -783,10 +897,11 @@ class Language begin if fname = located(filename) then data = IO.read(fname) - @data += data.gsub(/\%.*$/, '') + @data += data.gsub(/\%.*$/, '').gsub(/\\message\{.*?\}/, '') data.gsub!(/(\\patterns|\\hyphenation)\s*\{.*/mo) do '' end @read += "\n% preamble of file #{fname}\n\n#{data}\n" report("file #{fname} is loaded") + @data.gsub!(/^[\s\n]+$/mois, '') break # next fileset end rescue @@ -806,11 +921,45 @@ class Language def convert if @data then n = 0 - @remapping.each do |k| - @data.gsub!(k[0]) do - # report("#{k[0]} => #{k[1]}") - n += 1 - k[1] + if true then + report("") + ["\\patterns","\\hyphenation"].each do |what| + @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 + content.gsub!(from) do + done = true + m += 1 + "[#{i}]" + end + report("#{m.to_s.rjust(5)} entries remapped to #{to}") unless m == 0 + n += m + end + content.gsub!(/\[(\d+)\]/o) do + @remapping[$1.to_i][1] + end + report(" nothing remapped") unless done + report("") + content.to_s + end + end + else + @remapping.each do |k| + from, to, m = k[0], k[1], 0 + @data.gsub!(from) do + m += 1 + to + end + report("#{m.to_s.rjust(5)} entries remapped to #{to}") unless m == 0 + n += m end end report("#{n} changes in patterns and exceptions") @@ -865,6 +1014,7 @@ class Language end def triggerunicode + return if @commandline.option('utf8') then "% xetex needs utf8 encoded patterns and for patterns\n" + "% coded as such we need to enable this regime when\n" + @@ -963,9 +1113,9 @@ class Language begin if f = File.open(patname,'w') then data = '' - @data.scan(/\\patterns\s*\{\s*(.*?)\s*\}/m) do + @data.filterargument('\\patterns') do |content| report("merging patterns") - data += $1 + "\n" + data += content.strip end data.gsub!(/(\s*\n\s*)+/mo, "\n") @@ -989,9 +1139,9 @@ class Language begin if f = File.open(hypname,'w') then data = '' - @data.scan(/\\hyphenation\s*\{\s*(.*?)\s*\}/m) do + @data.filterargument('\\hyphenation') do |content| report("merging exceptions") - data += $1 + "\n" + data += content.strip end data.gsub!(/(\s*\n\s*)+/mo, "\n") f << banner @@ -1094,7 +1244,7 @@ class Language end - def preload_vector(encoding='') + def preload_vector(encoding='', filename='') # funny polish @@ -1117,41 +1267,285 @@ class Language remap(/X/, "[aeligature]") 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 + 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 + remap(/\<\'a\|/, "[greekalphaiotasubdasiatonos]") + # remap(/\<\'a\|/, "[greekdasiatonos][greekAlpha][greekiota]") + remap(/\>\'a\|/, "[greekalphaiotasubpsilitonos]") + remap(/\<\`a\|/, "[greekalphaiotasubdasiavaria]") + remap(/\>\`a\|/, "[greekalphaiotasubpsilivaria]") + remap(/\<\~a\|/, "[greekalphaiotasubdasiaperispomeni]") + remap(/\>\~a\|/, "[greekalphaiotasubpsiliperispomeni]") + remap(/\'a\|/, "[greekalphaiotasubtonos]") + remap(/\`a\|/, "[greekalphaiotasubvaria]") + remap(/\~a\|/, "[greekalphaiotasubperispomeni]") + remap(/\<a\|/, "[greekalphaiotasubdasia]") + remap(/\>a\|/, "[greekalphaiotasubpsili]") + remap(/a\|/, "[greekalphaiotasub]") + remap(/\<\'h\|/, "[greeketaiotasubdasiatonos]") + remap(/\>\'h\|/, "[greeketaiotasubpsilitonos]") + remap(/\<\`h\|/, "[greeketaiotasubdasiavaria]") + remap(/\>\`h\|/, "[greeketaiotasubpsilivaria]") + remap(/\<\~h\|/, "[greeketaiotasubdasiaperispomeni]") + remap(/\>\~h\|/, "[greeketaiotasubpsiliperispomeni]") + remap(/\'h\|/, "[greeketaiotasubtonos]") + remap(/\`h\|/, "[greeketaiotasubvaria]") + remap(/\~h\|/, "[greeketaiotasubperispomeni]") + remap(/\<h\|/, "[greeketaiotasubdasia]") + remap(/\>h\|/, "[greeketaiotasubpsili]") + remap(/h\|/, "[greeketaiotasub]") + remap(/\<'w\|/, "[greekomegaiotasubdasiatonos]") + remap(/\>'w\|/, "[greekomegaiotasubpsilitonos]") + remap(/\<`w\|/, "[greekomegaiotasubdasiavaria]") + remap(/\>`w\|/, "[greekomegaiotasubpsilivaria]") + remap(/\<~w\|/, "[greekomegaiotasubdasiaperispomeni]") + remap(/\>~w\|/, "[greekomegaiotasubpsiliperispomeni]") + remap(/\<w\|/, "[greekomegaiotasubdasia]") + remap(/\>w\|/, "[greekomegaiotasubpsili]") + remap(/\'w\|/, "[greekomegaiotasubtonos]") + remap(/\`w\|/, "[greekomegaiotasubvaria]") + remap(/\~w\|/, "[greekomegaiotasubperispomeni]") + remap(/w\|/, "[greekomegaiotasub]") + remap(/\<\'i/, "[greekiotadasiatonos]") + remap(/\>\'i/, "[greekiotapsilitonos]") + remap(/\<\`i/, "[greekiotadasiavaria]") + remap(/\>\`i/, "[greekiotapsilivaria]") + remap(/\<\~i/, "[greekiotadasiaperispomeni]") + remap(/\>\~i/, "[greekiotapsiliperispomeni]") + remap(/\"\'i/, "[greekiotadialytikatonos]") + remap(/\"\`i/, "[greekiotadialytikavaria]") + remap(/\"\~i/, "[greekiotadialytikaperispomeni]") + remap(/\<i/, "[greekiotadasia]") + remap(/\>i/, "[greekiotapsili]") + remap(/\'i/, "[greekiotaoxia]") + remap(/\`i/, "[greekiotavaria]") + remap(/\~i/, "[greekiotaperispomeni]") + remap(/\"i/, "[greekiotadialytika]") + remap(/\>\~e/, "[greekepsilonpsiliperispomeni]") + remap(/\<\~e/, "[greekepsilondasiaperispomeni]") + remap(/\<\'e/, "[greekepsilondasiatonos]") + remap(/\>\'e/, "[greekepsilonpsilitonos]") + remap(/\<\`e/, "[greekepsilondasiavaria]") + remap(/\>\`e/, "[greekepsilonpsilivaria]") + remap(/\<e/, "[greekepsilondasia]") + remap(/\>e/, "[greekepsilonpsili]") + remap(/\'e/, "[greekepsilonoxia]") + remap(/\`e/, "[greekepsilonvaria]") + remap(/\~e/, "[greekepsilonperispomeni]") + remap(/\<\'a/, "[greekalphadasiatonos]") + remap(/\>\'a/, "[greekalphapsilitonos]") + remap(/\<\`a/, "[greekalphadasiavaria]") + remap(/\>\`a/, "[greekalphapsilivaria]") + remap(/\<\~a/, "[greekalphadasiaperispomeni]") + remap(/\>\~a/, "[greekalphapsiliperispomeni]") + remap(/\<a/, "[greekalphadasia]") + remap(/\>a/, "[greekalphapsili]") + remap(/\'a/, "[greekalphaoxia]") + remap(/\`a/, "[greekalphavaria]") + remap(/\~a/, "[greekalphaperispomeni]") + remap(/\<\'h/, "[greeketadasiatonos]") + remap(/\>\'h/, "[greeketapsilitonos]") + remap(/\<\`h/, "[greeketadasiavaria]") + remap(/\>\`h/, "[greeketapsilivaria]") + remap(/\<\~h/, "[greeketadasiaperispomeni]") + remap(/\>\~h/, "[greeketapsiliperispomeni]") + remap(/\<h/, "[greeketadasia]") + remap(/\>h/, "[greeketapsili]") + remap(/\'h/, "[greeketaoxia]") + remap(/\`h/, "[greeketavaria]") + remap(/\~h/, "[greeketaperispomeni]") + remap(/\<\~o/, "[greekomicrondasiaperispomeni]") + remap(/\>\~o/, "[greekomicronpsiliperispomeni]") + remap(/\<\'o/, "[greekomicrondasiatonos]") + remap(/\>\'o/, "[greekomicronpsilitonos]") + remap(/\<\`o/, "[greekomicrondasiavaria]") + remap(/\>\`o/, "[greekomicronpsilivaria]") + remap(/\<o/, "[greekomicrondasia]") + remap(/\>o/, "[greekomicronpsili]") + remap(/\'o/, "[greekomicronoxia]") + remap(/\`o/, "[greekomicronvaria]") + remap(/\~o/, "[greekomicronperispomeni]") + remap(/\<\'u/, "[greekupsilondasiatonos]") + remap(/\>\'u/, "[greekupsilonpsilitonos]") + remap(/\<\`u/, "[greekupsilondasiavaria]") + remap(/\>\'u/, "[greekupsilonpsilivaria]") + remap(/\<\~u/, "[greekupsilondasiaperispomeni]") + remap(/\>\~u/, "[greekupsilonpsiliperispomeni]") + remap(/\"\'u/, "[greekupsilondialytikatonos]") + remap(/\"\`u/, "[greekupsilondialytikavaria]") + remap(/\"\~u/, "[greekupsilondialytikaperispomeni]") + remap(/\<u/, "[greekupsilondasia]") + remap(/\>u/, "[greekupsilonpsili]") + remap(/\'u/, "[greekupsilonoxia]") + remap(/\`u/, "[greekupsilonvaria]") + remap(/\~u/, "[greekupsilonperispomeni]") + remap(/\"u/, "[greekupsilondiaeresis]") + remap(/\<\'w/, "[greekomegadasiatonos]") + remap(/\>\'w/, "[greekomegapsilitonos]") + remap(/\<\`w/, "[greekomegadasiavaria]") + remap(/\>\`w/, "[greekomegapsilivaria]") + remap(/\<\~w/, "[greekomegadasiaperispomeni]") + remap(/\>\~w/, "[greekomegapsiliperispomeni]") + remap(/\<w/, "[greekomegadasia]") + remap(/\>w/, "[greekomegapsili]") + remap(/\'w/, "[greekomegaoxia]") + remap(/\`w/, "[greekomegavaria]") + remap(/\~w/, "[greekomegaperispomeni]") + remap(/\<r/, "[greekrhodasia]") + remap(/\>r/, "[greekrhopsili]") + remap(/\<\~/, "[greekdasiaperispomeni]") + remap(/\>\~/, "[greekpsiliperispomeni]") + remap(/\<\'/, "[greekdasiatonos]") + remap(/\>\'/, "[greekpsilitonos]") + remap(/\<\`/, "[greekdasiavaria]") + remap(/\>\`/, "[greekpsilivaria]") + remap(/\"\'/, "[greekdialytikatonos]") + remap(/\"\`/, "[greekdialytikavaria]") + remap(/\"\~/, "[greekdialytikaperispomeni]") + remap(/\</, "[dasia]") + remap(/\>/, "[psili]") + remap(/\'/, "[oxia]") + remap(/\`/, "[greekvaria]") + remap(/\~/, "[perispomeni]") + remap(/\"/, "[dialytika]") + # unknown + remap(/\|/, "[greekIotadialytika]") + # next + remap(/A/, "[greekAlpha]") + remap(/B/, "[greekBeta]") + remap(/D/, "[greekDelta]") + remap(/E/, "[greekEpsilon]") + remap(/F/, "[greekPhi]") + remap(/G/, "[greekGamma]") + remap(/H/, "[greekEta]") + remap(/I/, "[greekIota]") + remap(/J/, "[greekTheta]") + remap(/K/, "[greekKappa]") + remap(/L/, "[greekLambda]") + remap(/M/, "[greekMu]") + remap(/N/, "[greekNu]") + remap(/O/, "[greekOmicron]") + remap(/P/, "[greekPi]") + remap(/Q/, "[greekChi]") + remap(/R/, "[greekRho]") + remap(/S/, "[greekSigma]") + remap(/T/, "[greekTau]") + remap(/U/, "[greekUpsilon]") + remap(/W/, "[greekOmega]") + remap(/X/, "[greekXi]") + remap(/Y/, "[greekPsi]") + remap(/Z/, "[greekZeta]") + remap(/a/, "[greekalpha]") + remap(/b/, "[greekbeta]") + remap(/c/, "[greekfinalsigma]") + remap(/d/, "[greekdelta]") + remap(/e/, "[greekepsilon]") + remap(/f/, "[greekphi]") + remap(/g/, "[greekgamma]") + remap(/h/, "[greeketa]") + remap(/i/, "[greekiota]") + remap(/j/, "[greektheta]") + remap(/k/, "[greekkappa]") + remap(/l/, "[greeklambda]") + remap(/m/, "[greekmu]") + remap(/n/, "[greeknu]") + remap(/o/, "[greekomicron]") + remap(/p/, "[greekpi]") + remap(/q/, "[greekchi]") + remap(/r/, "[greekrho]") + remap(/s/, "[greeksigma]") + remap(/t/, "[greektau]") + remap(/u/, "[greekupsilon]") + remap(/w/, "[greekomega]") + remap(/x/, "[greekxi]") + remap(/y/, "[greekpsi]") + remap(/z/, "[greekzeta]") + when 'ru' then + remap(/\xC1/, "[cyrillica]") + remap(/\xC2/, "[cyrillicb]") + remap(/\xD7/, "[cyrillicv]") + remap(/\xC7/, "[cyrillicg]") + remap(/\xC4/, "[cyrillicd]") + remap(/\xC5/, "[cyrillice]") + remap(/\xD6/, "[cyrilliczh]") + remap(/\xDA/, "[cyrillicz]") + remap(/\xC9/, "[cyrillici]") + remap(/\xCA/, "[cyrillicishrt]") + remap(/\xCB/, "[cyrillick]") + remap(/\xCC/, "[cyrillicl]") + remap(/\xCD/, "[cyrillicm]") + remap(/\xCE/, "[cyrillicn]") + remap(/\xCF/, "[cyrillico]") + remap(/\xD0/, "[cyrillicp]") + remap(/\xD2/, "[cyrillicr]") + remap(/\xD3/, "[cyrillics]") + remap(/\xD4/, "[cyrillict]") + remap(/\xD5/, "[cyrillicu]") + remap(/\xC6/, "[cyrillicf]") + remap(/\xC8/, "[cyrillich]") + remap(/\xC3/, "[cyrillicc]") + remap(/\xDE/, "[cyrillicch]") + remap(/\xDB/, "[cyrillicsh]") + remap(/\xDD/, "[cyrillicshch]") + remap(/\xDF/, "[cyrillichrdsn]") + remap(/\xD9/, "[cyrillicery]") + remap(/\xD8/, "[cyrillicsftsn]") + remap(/\xDC/, "[cyrillicerev]") + remap(/\xC0/, "[cyrillicyu]") + remap(/\xD1/, "[cyrillicya]") + remap(/\xA3/, "[cyrillicyo]") else end if ! encoding.empty? then begin - filename = `kpsewhich -progname=context enco-#{encoding}.tex` - if data = IO.read(filename.chomp) then + filename = `kpsewhich -progname=context #{filename}` + if data = IO.readlines(filename.chomp) then report("preloading #{encoding} character mappings") - data.scan(/\\definecharacter\s*([a-zA-Z]+)\s*(\d+)\s*/o) do - name, number = $1, $2 - remap(/\^\^#{sprintf("%02x",number)}/, "[#{name}]") + accept = false + data.each do |line| + case line.chomp + when /\\start(en|)coding\s*\[(.*?)\]/io then + enc = $2 + if accept = (enc == encoding) then + report("accepting vector #{enc}") + else + report("skipping vector #{enc}") + end + when /\\stop(en|)coding/io then + accept = false + when accept && /\\definecharacter\s*([a-zA-Z]+)\s*(\d+)\s*/o then + name, number = $1, $2 + remap(/\^\^#{sprintf("%02x",number)}/, "[#{name}]") + if number.to_i > 127 then + remap(/#{sprintf("\\%03o",number)}/, "[#{name}]") + end + end end end rescue end end - end end @@ -1185,7 +1579,7 @@ class Commands # todo: filter the fallback list from context # The first entry in the array is the encoding which will be used - # when interpreting th eraw patterns. The second entry is a list of + # when interpreting the raw patterns. The second entry is a list of # filesets (string|aray), each first match of a set is taken. @@languagedata['ba' ] = [ 'ec' , ['bahyph.tex'] ] @@ -1204,6 +1598,7 @@ class Commands @@languagedata['hr' ] = [ 'ec' , ['hrhyph.tex'] ] @@languagedata['hu' ] = [ 'ec' , ['huhyphn.tex'] ] @@languagedata['en' ] = [ 'default' , [['ushyphmax.tex','ushyph.tex','hyphen.tex']] ] + @@languagedata['us' ] = [ 'default' , [['ushyphmax.tex','ushyph.tex','hyphen.tex']] ] # inhyph.tex @@languagedata['is' ] = [ 'ec' , ['ishyph.tex'] ] @@languagedata['it' ] = [ 'ec' , ['ithyph.tex'] ] @@ -1211,7 +1606,7 @@ class Commands # mnhyph @@languagedata['nl' ] = [ 'ec' , ['nehyph96.tex'] ] @@languagedata['no' ] = [ 'ec' , ['nohyph.tex'] ] - # oldgrhyph.tex + @@languagedata['agr'] = [ 'agr' , [['grahyph4.tex','oldgrhyph.tex']] ] # new, todo @@languagedata['pl' ] = [ 'ec' , ['plhyph.tex'] ] @@languagedata['pt' ] = [ 'ec' , ['pthyph.tex'] ] @@languagedata['ro' ] = [ 'ec' , ['rohyph.tex'] ] @@ -1222,7 +1617,8 @@ class Commands @@languagedata['sv' ] = [ 'ec' , ['svhyph.tex'] ] @@languagedata['tr' ] = [ 'ec' , ['tkhyph.tex'] ] @@languagedata['uk' ] = [ 'default' , [['ukhyphen.tex','ukhyph.tex']] ] - + # @@languagedata['ru' ] = [ 't2a' , ['ruhyphal.tex'] ] # t2a does not work + @@languagedata['ru' ] = [ 'cyr' , ['ruhyphal.tex'] ] end class Commands @@ -1235,10 +1631,11 @@ class Commands texmfroot = @commandline.argument('first') texmfroot = '.' if texmfroot.empty? - maproot = "#{texmfroot}/fonts/map/pdftex/context" + maproot = "#{texmfroot.gsub(/\\/,'/')}/fonts/map/pdftex/context" if File.directory?(maproot) then - if files = Dir.glob("#{maproot}/*.map") and files.size > 0 then + files = Dir.glob("#{maproot}/*.map") + if files.size > 0 then files.each do |pdffile| next if File.basename(pdffile) == 'pdftex.map' pdffile = File.expand_path(pdffile) @@ -1267,6 +1664,12 @@ class Commands data.delete_if do |line| line.gsub(/\s+/,'').empty? end + data.collect! do |line| + # remove line with "name name" lines + line.gsub(/^(\S+)\s+\1\s*$/) do + $1 + end + end begin if force then if n > 0 then @@ -1279,8 +1682,12 @@ class Commands report("? #{File.basename(dpxfile)} - dvipdfm(x)") end else - report("- #{File.basename(dpxfile)} - dvipdfm(x)") - begin File.delete(dpxname) ; rescue ; end + report("- #{File.basename(dpxfile)} - dvipdfm(x) - no entries") + # begin File.delete(dpxname) ; rescue ; end + if f = File.open(dpxfile,'w') then + f.puts("% no map entries") + f.close + end end else report(". #{File.basename(dpxfile)} - dvipdfm(x) - #{n}") @@ -1314,6 +1721,118 @@ class Commands end +class Array + + def add_shebang(filename,program) + unless self[0] =~ /^\#/ then + self.insert(0,"\#!/usr/env #{program}") + end + unless self[2] =~ /^\#.*?copyright\=/ then + self.insert(1,"\#") + self.insert(2,"\# copyright=pragma-ade readme=readme.pdf licence=cc-gpl") + self.insert(3,"") unless self[3].chomp.strip.empty? + self[2].gsub!(/ +/, ' ') + return true + else + return false + end + end + + def add_directive(filename,program) + unless self[0] =~ /^\%/ then + self.insert(0,"\% content=#{program}") + end + unless self[2] =~ /^\%.*?copyright\=/ then + self.insert(1,"\%") + if File.expand_path(filename) =~ /[\\\/](doc|manuals)[\\\/]/ then + self.insert(2,"\% copyright=pragma-ade readme=readme.pdf licence=cc-by-nc-sa") + else + self.insert(2,"\% copyright=pragma-ade readme=readme.pdf licence=cc-gpl") + end + self.insert(3,"") unless self[3].chomp.strip.empty? + self[0].gsub!(/ +/, ' ') + return true + else + return false + end + end + + def add_comment(filename) + if self[0] =~ /<\?xml.*?\?>/ && self[2] !~ /^<\!\-\-.*?copyright\=.*?\-\->/ then + self.insert(1,"") + if File.expand_path(filename) =~ /[\\\/](doc|manuals)[\\\/]/ then + self.insert(2,"<!-- copyright='pragma-ade' readme='readme.pdf' licence='cc-by-nc-sa' -->") + else + self.insert(2,"<!-- copyright='pragma-ade' readme='readme.pdf' licence='cc-gpl' -->") + end + self.insert(3,"") unless self[3].chomp.strip.empty? + return true + else + return false + end + end + +end + +class Commands + + include CommandBase + + def brandfiles + + force = @commandline.option("force") + files = @commandline.arguments # Dir.glob("**/*.*") + done = false + + files.each do |filename| + if FileTest.file?(filename) then + ok = false + begin + data = IO.readlines(filename) + case filename + when /\.rb$/ then + ok = data.add_shebang(filename,'ruby') + when /\.pl$/ then + ok = data.add_shebang(filename,'perl') + when /\.py$/ then + ok = data.add_shebang(filename,'python') + when /\.lua$/ then + ok = data.add_shebang(filename,'lua') + when /\.tex$/ then + ok = data.add_directive(filename,'tex') + when /\.mp$/ then + ok = data.add_directive(filename,'metapost') + when /\.mf$/ then + ok = data.add_directive(filename,'metafont') + when /\.(xml|xsl|fo|fx|rlx|rng|exa)$/ then + ok = data.add_comment(filename) + end + rescue + report("fatal error in processing #{filename}") # maybe this catches the mac problem taco reported + else + if ok then + report() + report(filename) + report() + for i in 0..4 do + report(' ' + data[i].chomp) + end + if force && f = File.open(filename,'w') then + f.puts data + f.close + end + done = true + end + end + else + report("no file named #{filename}") + end + end + report() if done + end + +end + class Commands include CommandBase @@ -1322,68 +1841,609 @@ class Commands # document: <!DOCTYPE something SYSTEM "entities.xml"> def flushentities(handle,entities,doctype=nil) # 'stylesheet' - tab = if doctype then "\t" else "" end - handle.puts("<!DOCTYPE #{doctype} [") if doctype + if doctype then + tab = "\t" + handle << "<?xml version='1.0' encoding='utf-8'?>\n\n" + handle << "<!-- !DOCTYPE entities SYSTEM 'entities.xml' -->\n\n" + handle << "<!DOCTYPE #{doctype} [\n" + else + tab = "" + end entities.keys.sort.each do |k| - handle.puts("#{tab}<!ENTITY #{k} \"\&\##{entities[k]};\">") + handle << "#{tab}<!ENTITY #{k} \"\&\#x#{entities[k]};\">\n" + end + if doctype then + handle << "]>\n" end - handle.puts("]>") if doctype end def listentities - # filename = `texmfstart tmftools.rb --progname=context enco-uc.tex`.chomp - filename = `kpsewhich --progname=context enco-uc.tex`.chomp + filenames = ['enco-uc.tex','contextnames.txt'] outputname = @commandline.argument('first') + doctype = @commandline.option('doctype') + entities = Hash.new + + filenames.each do |filename| + # filename = `texmfstart tmftools.rb --progname=context #{filename}`.chomp + filename = `kpsewhich --progname=context #{filename}`.chomp + if filename and not filename.empty? and FileTest.file?(filename) then + report("loading #{filename.gsub(/\\/,'/')}") unless outputname.empty? + IO.readlines(filename).each do |line| + case line + when /^[\#\%]/io then + # skip comment line + when /\\definecharacter\s+([a-z]+)\s+\{\\uchar\{*(\d+)\}*\{(\d+)\}\}/io then + name, code = $1, ($2.to_i*256 + $3.to_i).to_s + entities[name] = code.rjust(4,'0') unless entities.key?(name) + when /^([A-F0-9]+)\;([a-z][a-z]+)\;(.*?)\;(.*?)\s*$/io then + code, name, adobe, comment = $1, $2, $3, $4 + entities[name] = code.rjust(4,'0') unless entities.key?(name) + end + end + end + end + if outputname and not outputname.empty? then + if f = File.open(outputname,'w') then + report("saving #{entities.size} entities in #{outputname}") + flushentities(f,entities,doctype) + f.close + else + flushentities($stdout,entities,doctype) + end + else + flushentities($stdout,entities,doctype) + end + end + +end + +class Commands + + include CommandBase + + def platformize + + pattern = if @commandline.arguments.empty? then "*.{rb,pl,py}" else @commandline.arguments end + recurse = @commandline.option("recurse") + force = @commandline.option("force") + pattern = "#{if recurse then '**/' else '' end}#{pattern}" + Dir.glob(pattern).each do |file| + if File.file?(file) then + size = File.size(file) + data = IO.readlines(file) + if force then + if f = File.open(file,'w') + data.each do |line| + f.puts(line.chomp) + end + f.close + end + if File.size(file) == size then # not robust + report("file '#{file}' is unchanged") + else + report("file '#{file}' is platformized") + end + else + report("file '#{file}' is a candidate") + end + end + end + end - if filename and not filename.empty? and FileTest.file?(filename) then - entities = Hash.new - IO.readlines(filename).each do |line| - if line =~ /\\definecharacter\s+([a-zA-Z]+)\s+\{\\uchar\{*(\d+)\}*\{(\d+)\}\}/o then - name, low, high = $1, $2.to_i, $3.to_i - entities[name] = low*256 + high +end + +class TexDeps + + @@cs_tex = %q/ + above abovedisplayshortskip abovedisplayskip + abovewithdelims accent adjdemerits advance afterassignment + aftergroup atop atopwithdelims + badness baselineskip batchmode begingroup + belowdisplayshortskip belowdisplayskip binoppenalty botmark + box boxmaxdepth brokenpenalty + catcode char chardef cleaders closein closeout clubpenalty + copy count countdef cr crcr csname + day deadcycles def defaulthyphenchar defaultskewchar + delcode delimiter delimiterfactor delimeters + delimitershortfall delimeters dimen dimendef discretionary + displayindent displaylimits displaystyle + displaywidowpenalty displaywidth divide + doublehyphendemerits dp dump + edef else emergencystretch end endcsname endgroup endinput + endlinechar eqno errhelp errmessage errorcontextlines + errorstopmode escapechar everycr everydisplay everyhbox + everyjob everymath everypar everyvbox exhyphenpenalty + expandafter + fam fi finalhyphendemerits firstmark floatingpenalty font + fontdimen fontname futurelet + gdef global group globaldefs + halign hangafter hangindent hbadness hbox hfil horizontal + hfill horizontal hfilneg hfuzz hoffset holdinginserts hrule + hsize hskip hss horizontal ht hyphenation hyphenchar + hyphenpenalty hyphen + if ifcase ifcat ifdim ifeof iffalse ifhbox ifhmode ifinner + ifmmode ifnum ifodd iftrue ifvbox ifvmode ifvoid ifx + ignorespaces immediate indent input inputlineno input + insert insertpenalties interlinepenalty + jobname + kern + language lastbox lastkern lastpenalty lastskip lccode + leaders left lefthyphenmin leftskip leqno let limits + linepenalty line lineskip lineskiplimit long looseness + lower lowercase + mag mark mathaccent mathbin mathchar mathchardef mathchoice + mathclose mathcode mathinner mathop mathopen mathord + mathpunct mathrel mathsurround maxdeadcycles maxdepth + meaning medmuskip message mkern month moveleft moveright + mskip multiply muskip muskipdef + newlinechar noalign noboundary noexpand noindent nolimits + nonscript scriptscript nonstopmode nulldelimiterspace + nullfont number + omit openin openout or outer output outputpenalty over + overfullrule overline overwithdelims + pagedepth pagefilllstretch pagefillstretch pagefilstretch + pagegoal pageshrink pagestretch pagetotal par parfillskip + parindent parshape parskip patterns pausing penalty + postdisplaypenalty predisplaypenalty predisplaysize + pretolerance prevdepth prevgraf + radical raise read relax relpenalty right righthyphenmin + rightskip romannumeral + scriptfont scriptscriptfont scriptscriptstyle scriptspace + scriptstyle scrollmode setbox setlanguage sfcode shipout + show showbox showboxbreadth showboxdepth showlists showthe + skewchar skip skipdef spacefactor spaceskip span special + splitbotmark splitfirstmark splitmaxdepth splittopskip + string + tabskip textfont textstyle the thickmuskip thinmuskip time + toks toksdef tolerance topmark topskip tracingcommands + tracinglostchars tracingmacros tracingonline tracingoutput + tracingpages tracingparagraphs tracingrestores tracingstats + uccode uchyph underline unhbox unhcopy unkern unpenalty + unskip unvbox unvcopy uppercase + vadjust valign vbadness vbox vcenter vfil vfill vfilneg + vfuzz voffset vrule vsize vskip vsplit vss vtop + wd widowpenalty write + xdef xleaders xspaceskip + year + /.split + + @@cs_etex = %q/ + beginL beginR botmarks + clubpenalties currentgrouplevel currentgrouptype + currentifbranch currentiflevel currentiftype + detokenize dimexpr displaywidowpenalties + endL endR eTeXrevision eTeXversion everyeof + firstmarks fontchardp fontcharht fontcharic fontcharwd + glueexpr glueshrink glueshrinkorder gluestretch + gluestretchorder gluetomu + ifcsname ifdefined iffontchar interactionmode + interactionmode interlinepenalties + lastlinefit lastnodetype + marks topmarks middle muexpr mutoglue + numexpr + pagediscards parshapedimen parshapeindent parshapelength + predisplaydirection + savinghyphcodes savingvdiscards scantokens showgroups + showifs showtokens splitdiscards splitfirstmarks + TeXXeTstate tracingassigns tracinggroups tracingifs + tracingnesting tracingscantokens + unexpanded unless + widowpenalties + /.split + + @@cs_pdftex = %q/ + pdfadjustspacing pdfannot pdfavoidoverfull + pdfcatalog pdfcompresslevel + pdfdecimaldigits pdfdest pdfdestmargin + pdfendlink pdfendthread + pdffontattr pdffontexpand pdffontname pdffontobjnum pdffontsize + pdfhorigin + pdfimageresolution pdfincludechars pdfinfo + pdflastannot pdflastdemerits pdflastobj + pdflastvbreakpenalty pdflastxform pdflastximage + pdflastximagepages pdflastxpos pdflastypos + pdflinesnapx pdflinesnapy pdflinkmargin pdfliteral + pdfmapfile pdfmaxpenalty pdfminpenalty pdfmovechars + pdfnames + pdfobj pdfoptionpdfminorversion pdfoutline pdfoutput + pdfpageattr pdfpageheight pdfpageresources pdfpagesattr + pdfpagewidth pdfpkresolution pdfprotrudechars + pdfrefobj pdfrefxform pdfrefximage + pdfsavepos pdfsnaprefpoint pdfsnapx pdfsnapy pdfstartlink + pdfstartthread + pdftexrevision pdftexversion pdfthread pdfthreadmargin + pdfuniqueresname + pdfvorigin + pdfxform pdfximage + /.split + + @@cs_omega = %q/ + odelimiter omathaccent omathchar oradical omathchardef omathcode odelcode + leftghost rightghost + charwd charht chardp charit + localleftbox localrightbox + localinterlinepenalty localbrokenpenalty + pagedir bodydir pardir textdir mathdir + boxdir nextfakemath + pagewidth pageheight pagerightoffset pagebottomoffset + nullocp nullocplist ocp externalocp ocplist pushocplist popocplist clearocplists ocptracelevel + addbeforeocplist addafterocplist removebeforeocplist removeafterocplist + OmegaVersion + InputTranslation OutputTranslation DefaultInputTranslation DefaultOutputTranslation + noInputTranslation noOutputTranslation + InputMode OutputMode DefaultInputMode DefaultOutputMode + noInputMode noOutputMode noDefaultInputMode noDefaultOutputMode + /.split + + @@cs_plain = %q/ + TeX + bgroup egroup endgraf space empty null + newcount newdimen newskip newmuskip newbox newtoks newhelp newread newwrite newfam newlanguage newinsert newif + maxdimen magstephalf magstep + frenchspacing nonfrenchspacing normalbaselines obeylines obeyspaces raggedright ttraggedright + thinspace negthinspace enspace enskip quad qquad + smallskip medskip bigskip removelastskip topglue vglue hglue + break nobreak allowbreak filbreak goodbreak smallbreak medbreak bigbreak + line leftline rightline centerline rlap llap underbar strutbox strut + cases matrix pmatrix bordermatrix eqalign displaylines eqalignno leqalignno + pageno folio tracingall showhyphens fmtname fmtversion + hphantom vphantom phantom smash + /.split + + @@cs_eplain = %q/ + eTeX + newmarks grouptype interactionmode nodetype iftype + tracingall loggingall tracingnone + /.split + + # let's ignore \dimendef etc + + @@primitives_def = "def|edef|xdef|gdef|let|newcount|newdimen|newskip|newbox|newtoks|newmarks|chardef|mathchardef|newconditional" + + @@cs_global = [@@cs_tex,@@cs_etex,@@cs_pdftex,@@cs_omega].sort.flatten + @@types = [['invalid','*'],['okay','='],['forward','>'],['backward','<'],['unknown','?']] + + def initialize(logger=nil) + @cs_local = Hash.new + @cs_new = Hash.new + @cs_defd = Hash.new + @cs_used = Hash.new + @filename = 'context.tex' + @files = Array.new # keep load order ! + @compact = false + @logger = logger + end + + def load(filename='context.tex',omitlist=['mult-com.tex']) + begin + @filename = filename + File.open(filename) do |f| + f.each do |line| + if line =~ /^\\input\s+(\S+)\s*/o then + @files.push($1) unless omitlist.include?(File.basename($1)) + end end end - if outputname and not outputname.empty? then - if f = File.open(outputname,'w') then - flushentities(f,entities) - f.close + rescue + @files = Array.new + end + end + + def analyze + @files.each do |filename| + if f = File.open(filename) then + @logger.report("loading #{filename}") if @logger + defs, uses, n = 0, 0, 0 + f.each do |line| + n += 1 + case line + when /^%/ + # skip + when /\\newif\s*\\if([a-zA-Z@\?\!]+)/ then + pushdef(filename,n,"if:#{$1}") + when /\\([a-zA-Z@\?\!]+)(true|false)/ then + pushuse(filename,n,"if:#{$1}") + when /^\s*\\(#{@primitives_def})\\([a-zA-Z@\?\!]{3,})/o + pushdef(filename,n,$2) + when /\\([a-zA-Z@\?\!]{3,})/o + pushuse(filename,n,$1) + end + end + f.close + end + end + end + + def feedback(compact=false) + begin + outputfile = File.basename(@filename).sub(/\.tex$/,'')+'.dep' + File.open(outputfile,'w') do |f| + @compact = compact + @logger.report("saving analysis in #{outputfile}") if @logger + list, len = @cs_local.keys.sort, 0 + if @compact then + list.each do |cs| + if cs.length > len then len = cs.length end + end + len += 1 else - flushentities($stdout,entities) + f.puts "<?xml version='1.0'?>\n" + f.puts "<dependencies xmlns='http://www.pragma-ade.com/schemas/texdeps.rng' rootfile='#{@filename}'>\n" + end + list.each do |cs| + if @cs_new.key?(cs) then + if @cs_new[cs] == @cs_local[cs] then + f.puts some_struc(cs,len,1,some_str(@cs_new,@cs_defd,cs)) + elsif @cs_new[cs].first == @cs_local[cs].first then + f.puts some_struc(cs,len,2,some_str(@cs_new,@cs_defd,cs),some_str(@cs_local,@cs_used,cs)) + else + f.puts some_struc(cs,len,3,some_str(@cs_new,@cs_defd,cs),some_str(@cs_local,@cs_used,cs)) + end + else + f.puts some_struc(cs,len,4,some_str(@cs_local,@cs_used,cs)) + end + end + if @compact then + # nothing + else + "</dependencies>\n" unless @compact end - else - flushentities($stdout,entities) end + rescue + end + end + + private + + def some_struc(cs,len,type=1,defstr='',usestr='') + if @compact then + "#{cs.ljust(len)} #{@@types[type][1]} #{defstr} #{usestr}" + else + "<macro name='#{cs}' type='#{type}'>\n" + + if defstr.empty? then " <defined/>\n" else " <defined>\n#{defstr} <\defined>\n" end + + if usestr.empty? then " <used/>\n" else " <used>#{usestr}\n <\used>\n" end + + "</macro>\n" end + end + def some_str(files, lines, cs) + return '' unless files[cs] + if @compact then + str = '[ ' + files[cs].each do |c| + str += c + str += " (#{lines[cs][c].join(' ')}) " if lines[cs][c] + str += ' ' + end + str += ']' + str.gsub(/ +/, ' ') + else + str = '' + files[cs].each do |c| + if lines[cs][c] then + str += " <file name='#{c}'>\n" + str += " " + lines[cs][c].each do |l| + # str += " <line n='#{l}'/>\n" + str += "<line n='#{l}'/>" + end + str += "\n" + str += " </file>\n" + else + str += " <file name='#{c}'/>\n" + end + end + str + end + end + + def pushdef(filename,n,cs) + unless @cs_new.key?(cs) then + @cs_new[cs] = Array.new + @cs_defd[cs] = Hash.new unless @cs_defd.key?(cs) + end + @cs_defd[cs][filename] = Array.new unless @cs_defd[cs][filename] + @cs_new[cs].push(filename) unless @cs_new[cs].include?(filename) + @cs_defd[cs][filename] << n + end + + def pushuse(filename,n,cs) + unless @@cs_global.include?(cs.to_s) then + unless @cs_local[cs] then + @cs_local[cs] = Array.new + @cs_used[cs] = Hash.new unless @cs_used.key?(cs) + end + @cs_used[cs][filename] = Array.new unless @cs_used[cs][filename] + @cs_local[cs].push(filename) unless @cs_local[cs].include?(filename) + @cs_used[cs][filename] << n + end end end +class Commands -logger = Logger.new(banner.shift) -commandline = CommandLine.new + include CommandBase -commandline.registeraction('touchcontextfile', 'update context version') -commandline.registeraction('contextversion', 'report context version') + def dependencies -commandline.registeraction('jeditinterface', 'generate jedit syntax files [--pipe]') -commandline.registeraction('bbeditinterface', 'generate bbedit syntax files [--pipe]') -commandline.registeraction('sciteinterface', 'generate scite syntax files [--pipe]') -commandline.registeraction('rawinterface', 'generate raw syntax files [--pipe]') + filename = if @commandline.arguments.empty? then 'context.tex' else @commandline.arguments.first end + compact = @commandline.option('compact') -commandline.registeraction('translateinterface', 'generate interface files (xml) [nl de ..]') -commandline.registeraction('purgefiles', 'remove temporary files [--all --recurse] [basename]') + ['progname=context',''].each do |progname| + unless FileTest.file?(filename) then + name = `kpsewhich #{progname} #{filename}`.chomp + if FileTest.file?(name) then + filename = name + break + end + end + end -commandline.registeraction('documentation', 'generate documentation [--type=] [filename]') + if FileTest.file?(filename) && deps = TexDeps.new(logger) then + deps.load + deps.analyze + deps.feedback(compact) + else + report("unknown file #{filename}") + end -commandline.registeraction('filterpages') # no help, hidden temporary feature -commandline.registeraction('purgeallfiles') # no help, compatibility feature + end -commandline.registeraction('patternfiles', 'generate pattern files [--all --xml --utf8] [languagecode]') +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 + + def fetchfile(site, name, target=nil) + begin + http = Net::HTTP.new(site) + resp, data = http.get(name.gsub(/^\/*/, '/')) + rescue + return false + else + begin + if data then + name = File.basename(name) + File.open(target || name, 'wb') do |f| + f << data + end + else + return false + end + rescue + return false + else + return true + end + end + end + + def locatedlocaltree + tree = `kpsewhich --expand-path $TEXMFLOCAL`.chomp rescue nil + unless tree && FileTest.directory?(tree) then + tree = `kpsewhich --expand-path $TEXMF`.chomp rescue nil + end + return tree + end -commandline.registeraction('dpxmapfiles', 'convert pdftex mapfiles to dvipdfmx [--force] [texmfroot]') -commandline.registeraction('listentities', 'create doctype entity definition from enco-uc.tex') + def extractarchive(archive) + unless FileTest.file?(archive) then + report("fatal error, '#{archive}' has not been downloaded") + return false + end + unless system("unzip -uo #{archive}") then + report("fatal error, make sure that you have 'unzip' in your path") + return false + end + stubs = "scripts/context/stubs/unix/*" + if System.unix? and not system("chmod +x #{stubs}") then + report("change x-permissions of '#{stubs}' manually") + end + return true + end + + def remakeformats + return system("texmfstart texexec --make --all") + end + + if localtree = locatedlocaltree then + report("updating #{localtree}") + begin + Dir.chdir(localtree) + rescue + report("unable to change to #{localtree}") + else + archive = 'cont-tmf.zip' + report("fetching #{archive}") + unless fetchfile("www.pragma-ade.com","/context/latest/#{archive}") then + report("unable to fetch #{archive}") + return + end + report("extracting #{archive}") + unless extractarchive(archive) then + report("unable to extract #{archive}") + return + end + report("remaking formats") + unless remakeformats then + report("unable to remak formats") + end + end + else + report("unable to locate local tree") + end + + end + +end + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registeraction('touchcontextfile' , 'update context version') +commandline.registeraction('contextversion' , 'report context version') +commandline.registeraction('jeditinterface' , 'generate jedit syntax files [--pipe]') +commandline.registeraction('bbeditinterface' , 'generate bbedit syntax files [--pipe]') +commandline.registeraction('sciteinterface' , 'generate scite syntax files [--pipe]') +commandline.registeraction('rawinterface' , 'generate raw syntax files [--pipe]') +commandline.registeraction('translateinterface', 'generate interface files (xml) [nl de ..]') +commandline.registeraction('purgefiles' , 'remove temporary files [--all --recurse] [basename]') +commandline.registeraction('documentation' , 'generate documentation [--type=] [filename]') +commandline.registeraction('filterpages' ) # no help, hidden temporary feature +commandline.registeraction('patternfiles' , 'generate pattern files [--all --xml --utf8] [languagecode]') +commandline.registeraction('dpxmapfiles' , 'convert pdftex mapfiles to dvipdfmx [--force] [texmfroot]') +commandline.registeraction('listentities' , 'create doctype entity definition from enco-uc.tex') +commandline.registeraction('brandfiles' , 'add context copyright notice [--force]') +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','') @@ -1394,6 +2454,7 @@ commandline.registerflag('all') commandline.registerflag('xml') commandline.registerflag('log') commandline.registerflag('utf8') +commandline.registerflag('doctype') # general diff --git a/Master/texmf-dist/scripts/context/ruby/fcd_start.rb b/Master/texmf-dist/scripts/context/ruby/fcd_start.rb index 8ac48f79e8c..348ac75ba83 100644 --- a/Master/texmf-dist/scripts/context/ruby/fcd_start.rb +++ b/Master/texmf-dist/scripts/context/ruby/fcd_start.rb @@ -256,6 +256,8 @@ class FastCD @result = @list.grep(/\/#{@pattern}[^\/]*$/i) end end + else + puts(Dir.pwd.gsub(/\\/o, '/')) end rescue end @@ -403,31 +405,37 @@ class FastCD end +$stdout.sync = true + verbose, action, args = false, :find, Array.new -usage = "fcd [--make|add|show|find] [--verbose] [pattern]" +usage = "fcd [--add|clear|find|list|make|show|stub] [--verbose] [pattern]" +version = "1.0.2" + +def quit(message) + puts(message) + exit +end ARGV.each do |a| case a - when '-v', '--verbose' then verbose = true - when '-m', '--make' then action = :make - when '-c', '--clear' then action = :clear when '-a', '--add' then action = :add - when '-s', '--show' then action = :show - when '-l', '--list' then action = :show + when '-c', '--clear' then action = :clear when '-f', '--find' then action = :find + when '-l', '--list' then action = :show + when '-m', '--make' then action = :make + when '-s', '--show' then action = :show when '--stub' then action = :stub - when '-h', '--help' then puts "usage: #{usage}" ; exit - when /^\-\-.*/ then puts "unknown switch: #{a}" + "\n" + "usage: #{usage}" ; exit + when '-v', '--verbose' then verbose = true + when '--version' then quit("version: #{version}") + when '-h', '--help' then quit("usage: #{usage}") + when /^\-\-.*/ then quit("error: unknown switch #{a}, try --help") else args << a end end -$stdout.sync = true - fcd = FastCD.new(verbose) - -fcd.report("Fast Change Dir / version 1.0") +fcd.report("Fast Change Dir / version #{version}") case action when :make then diff --git a/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb b/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb index 807cad10ccd..2c1079d1a6b 100644 --- a/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb +++ b/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb @@ -13,11 +13,14 @@ require 'base/variables' require 'base/system' +require 'ftools' class GhostScript include Variables + @@pdftrimwhite = 'pdftrimwhite.pl' + @@pstopdfoptions = [ 'AntiAliasColorImages', 'AntiAliasGrayImages', @@ -213,7 +216,7 @@ class GhostScript else report("invalid conversion method #{gsmethod}") end rescue - report("job aborted due to some error #{$!}") + report("job aborted due to some error: #{$!}") begin File.delete(resultfile) if test(?e,resultfile) rescue @@ -338,9 +341,12 @@ class GhostScript end - def convertbounded (inpfile, outfile) - + def convertbounded(inpfile, outfile) report("converting #{inpfile} bounded") + do_convertbounded(inpfile, outfile) + end + + def do_convertbounded(inpfile, outfile) begin return false if FileTest.file?(outfile) && (! File.delete(outfile)) @@ -350,7 +356,7 @@ class GhostScript arguments = '' arguments << "\@gsprofile.ini " - arguments << "-q -sDEVICE=pdfwrite -dNOPAUSE -dNOCACHE -dBATCH -dSAFER" + arguments << "-q -sDEVICE=pdfwrite -dNOPAUSE -dNOCACHE -dBATCH -dSAFER " arguments << "#{gscolorswitch} " arguments << "#{gsdefaults} " arguments << "-sOutputFile=#{outfile} #{gsstream} -c quit " @@ -426,7 +432,7 @@ class GhostScript # -dEPSFitPage and -dEPSCrop behave weird (don't work) - arguments = "-sDEVICE=bbox -dSAFER -dNOPAUSE -dBATCH #{inpfile}" + arguments = "-sDEVICE=bbox -dSAFER -dNOPAUSE -dBATCH #{inpfile} " debug("ghostscript: #{arguments}") @@ -456,13 +462,14 @@ class GhostScript report("converting #{inpfile} cropped") - convertbounded(inpfile, @@pdftempfile) + do_convertbounded(inpfile, @@pdftempfile) return unless test(?e,@@pdftempfile) arguments = " --offset=#{@offset} #{@@pdftempfile} #{outfile}" - unless ok = System.run('cropcrap',arguments) then + report("calling #{@@pdftrimwhite}") + unless ok = System.run(@@pdftrimwhite,arguments) then report('cropping failed') begin File.delete(outfile) diff --git a/Master/texmf-dist/scripts/context/ruby/imgtopdf.rb b/Master/texmf-dist/scripts/context/ruby/imgtopdf.rb new file mode 100644 index 00000000000..98a36c17fd9 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/imgtopdf.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +# program : newimgtopdf +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +unless defined? ownpath + ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'') + $: << ownpath +end + +require 'base/switch' +require 'base/logger' + +require 'graphics/magick' + +banner = ['ImgToPdf', 'version 1.1.2', '2002-2006', 'PRAGMA ADE/POD'] + +class Commands + + include CommandBase + + # nowadays we would force a directive, but + # for old times sake we handle default usage + + def main + filename = @commandline.argument('first') + + if filename.empty? then + help + else + convert + end + end + + # actions + + def convert + + magick = Magick.new(session) + + ['compression','depth','colorspace','quality','inputpath','outputpath'].each do |v| + magick.setvariable(v,@commandline.option(v)) + end + + @commandline.arguments.each do |fullname| + magick.setvariable('inputfile',fullname) + magick.setvariable('outputfile',fullname.gsub(/(\..*?$)/io, '.pdf')) + if @commandline.option('auto') then + magick.autoconvert + else + magick.convert + end + end + end + +end + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registerflag('auto') + +commandline.registervalue('compression') +commandline.registervalue('depth') +commandline.registervalue('colorspace') +commandline.registervalue('quality') + +commandline.registervalue('inputpath') +commandline.registervalue('outputpath') + + +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.registeraction('convert', 'convert image into pdf') + +commandline.expand + +Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/mpstools.rb b/Master/texmf-dist/scripts/context/ruby/mpstools.rb index 6dc0c35ad0c..534bfb95bcf 100644 --- a/Master/texmf-dist/scripts/context/ruby/mpstools.rb +++ b/Master/texmf-dist/scripts/context/ruby/mpstools.rb @@ -1,4 +1,6 @@ # todo +# +# this script will replace mptopdf and makempy puts("This program is yet unfinished, for the moment it just calls 'mptopdf'.\n\n") diff --git a/Master/texmf-dist/scripts/context/ruby/newimgtopdf.rb b/Master/texmf-dist/scripts/context/ruby/newimgtopdf.rb index 3c1636cf2bc..563ae5b80a0 100644 --- a/Master/texmf-dist/scripts/context/ruby/newimgtopdf.rb +++ b/Master/texmf-dist/scripts/context/ruby/newimgtopdf.rb @@ -1,86 +1,8 @@ #!/usr/bin/env ruby -# program : newimgtopdf +# program : imgtopdf # copyright : PRAGMA Advanced Document Engineering # version : 2002-2005 # author : Hans Hagen -# -# project : ConTeXt / eXaMpLe -# concept : Hans Hagen -# info : j.hagen@xs4all.nl -# www : www.pragma-ade.com -unless defined? ownpath - ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'') - $: << ownpath -end - -require 'base/switch' -require 'base/logger' - -require 'graphics/magick' - -banner = ['ImgToPdf', 'version 1.1.1', '2002-2005', 'PRAGMA ADE/POD'] - -class Commands - - include CommandBase - - # nowadays we would force a directive, but - # for old times sake we handle default usage - - def main - filename = @commandline.argument('first') - - if filename.empty? then - help - else - convert - end - end - - # actions - - def convert - - magick = Magick.new(session) - - ['compression','depth','colorspace','quality','inputpath','outputpath'].each do |v| - magick.setvariable(v,@commandline.option(v)) - end - - @commandline.arguments.each do |fullname| - magick.setvariable('inputfile',fullname) - magick.setvariable('outputfile',fullname.gsub(/(\..*?$)/io, '.pdf')) - if @commandline.option('auto') then - magick.autoconvert - else - magick.convert - end - end - end - -end - -logger = Logger.new(banner.shift) -commandline = CommandLine.new - -commandline.registerflag('auto') - -commandline.registervalue('compression') -commandline.registervalue('depth') -commandline.registervalue('colorspace') -commandline.registervalue('quality') - -commandline.registervalue('inputpath') -commandline.registervalue('outputpath') - - -commandline.registeraction('help') -commandline.registeraction('version') - -commandline.registeraction('convert', 'convert image into pdf') - -commandline.expand - -Commands.new(commandline,logger,banner).send(commandline.action || 'main') +load(File.join(File.expand_path(File.dirname($0)),'imgtopdf.rb')) diff --git a/Master/texmf-dist/scripts/context/ruby/newpstopdf.rb b/Master/texmf-dist/scripts/context/ruby/newpstopdf.rb index 898f987cbce..a45b4cab896 100644 --- a/Master/texmf-dist/scripts/context/ruby/newpstopdf.rb +++ b/Master/texmf-dist/scripts/context/ruby/newpstopdf.rb @@ -4,527 +4,5 @@ # copyright : PRAGMA Advanced Document Engineering # version : 2002-2005 # author : Hans Hagen -# -# project : ConTeXt / eXaMpLe -# concept : Hans Hagen -# info : j.hagen@xs4all.nl -# www : www.pragma-ade.com -banner = ['PsToPdf', 'version 2.0.0', '2002-2005', 'PRAGMA ADE/POD'] - -unless defined? ownpath - ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'') - $: << ownpath -end - -# todo: paden/prefix in magick and inkscape -# todo: clean up method handling (pass strings, no numbers) -# --method=crop|bounded|raw|... -# --resolution=low|normal|medium|high|printer|print|screen|ebook|default -# + downward compatible flag handling - -require 'base/switch' -require 'base/tool' -require 'base/logger' - -require 'graphics/gs' -require 'graphics/magick' -require 'graphics/inkscape' - -require 'rexml/document' - -exit if defined?(REQUIRE2LIB) - -class Commands - - include CommandBase - - # nowadays we would force a directive, but - # for old times sake we handle default usage - - def main - filename = @commandline.argument('first') - pattern = @commandline.option('pattern') - globfiles(pattern) if filename.empty? && ! pattern.empty? - filename = @commandline.argument('first') - if filename.empty? then - help - elsif filename =~ /\.exa$/ then - request - else - convert - end - end - - # actions - - def convert - - ghostscript = GhostScript.new(logger) - magick = ImageMagick.new(logger) - inkscape = InkScape.new(logger) - - outpath = @commandline.option('outputpath') - unless outpath.empty? then - begin - File.expand_path(outpath) - outpath = File.makedirs(outpath) unless FileTest.directory?(outpath) - rescue - # sorry - end - end - - @commandline.arguments.each do |filename| - - filename = Tool.cleanfilename(filename,@commandline) - inppath = @commandline.option('inputpath') - if inppath.empty? then - inppath = '.' - fullname = filename # avoid duplicate './' - else - fullname = File.join(inppath,filename) - end - if FileTest.file?(fullname) then - handle_whatever(ghostscript,inkscape,magick,filename) - else - report("file #{fullname} does not exist") - end - - end - - end - - def request - - # <exa:request> - # <exa:application> - # <exa:command>pstopdf</exa:command> - # <exa:filename>E:/tmp/demo.ps</exa:filename> - # </exa:application> - # <exa:data> - # <exa:variable label='gs:DoThumbnails'>false</exa:variable> - # <exa:variable label='gs:ColorImageDepth'>-1</exa:variable> - # </exa:data> - # </exa:request> - - ghostscript = GhostScript.new(logger) - magick = ImageMagick.new(logger) - inkscape = InkScape.new(logger) - - dataname = @commandline.argument('first') || '' - filename = @commandline.argument('second') || '' - - if dataname.empty? || ! FileTest.file?(dataname) then - report('provide valid exa file') - return - else - begin - request = REXML::Document.new(File.new(dataname)) - rescue - report('provide valid exa file (xml error)') - return - end - end - if filename.empty? then - begin - if filename = REXML::XPath.first(request.root,"exa:request/exa:application/exa:filename/text()") then - filename = filename.to_s - else - report('no filename found in exa file') - return - end - rescue - filename = '' - end - end - if filename.empty? then - report('provide valid filename') - return - elsif ! FileTest.file?(filename) then - report("invalid filename #{filename}") - return - end - - [ghostscript,inkscape,magick].each do |i| - i.setvariable('inputfile',filename) - end - - # set ghostscript variables - REXML::XPath.each(request.root,"/exa:request/exa:data/exa:variable") do |v| - begin - if (key = v.attributes['label']) and (value = v.text.to_s) then - case key - when /gs[\:\.](var[\:\.])*(offset)/io then ghostscript.setoffset(value) - when /gs[\:\.](var[\:\.])*(method)/io then ghostscript.setvariable('method',value) - when /gs[\:\.](var[\:\.])*(.*)/io then ghostscript.setpsoption($2,value) - end - end - rescue - end - end - - # no inkscape and magick variables (yet) - - handle_whatever(ghostscript,inkscape,magick,filename) - - end - - def watch - - ghostscript = GhostScript.new(logger) - magick = ImageMagick.new(logger) - inkscape = InkScape.new(logger) - - pathname = commandline.option('watch') - - unless pathname and not pathname.empty? then - report('empty watchpath is not supported') - exit - end - - if pathname == '.' then - report("watchpath #{pathname} is not supported") - exit - end - - if FileTest.directory?(pathname) then - if Dir.chdir(pathname) then - report("watching path #{pathname}") - else - report("unable to change to path #{pathname}") - exit - end - else - report("invalid path #{pathname}") - exit - end - - waiting = false - - loop do - - if waiting then - report("waiting #{getvariable('delay')}") - waiting = false - sleep(getvariable('delay').to_i) - end - - files = Dir.glob("**/*.*") - - if files and files.length > 0 then - - files.each do |fullname| - - next unless fullname - - if FileTest.directory?(fullname) then - debug('skipping path', fullname) - next - end - - unless magick.supported(fullname) then - debug('not supported', fullname) - next - end - - if (! FileTest.file?(fullname)) || (FileTest.size(fullname) < 100) then - debug("skipping small crap file #{fullname}") - next - end - - debug("handling file #{fullname}") - - begin - next unless File.rename(fullname,fullname) # access trick - rescue - next # being written - end - - fullname = Tool.cleanfilename(fullname,@commandline) - - fullname.gsub!(/\\/io, '/') - - filename = File.basename(fullname) - filepath = File.dirname(fullname) - - next if filename =~ /gstemp.*/io - - if filepath !~ /(result|done|raw|crop|bound|bitmap)/io then - begin - File.makedirs(filepath+'/raw') - File.makedirs(filepath+'/bound') - File.makedirs(filepath+'/crop') - File.makedirs(filepath+'/bitmap') - debug("creating prefered input paths on #{filepath}") - rescue - debug("creating input paths on #{filepath} failed") - end - end - - if filepath =~ /^(.*\/|)(done|result)$/io then - debug("skipping file #{fullname}") - else - report("start processing file #{fullname}") - if filepath =~ /^(.*\/*)(raw|crop|bound)$/io then - donepath = $1 + 'done' - resultpath = $1 + 'result' - case $2 - when 'raw' then method = 1 - when 'bound' then method = 2 - when 'crop' then method = 3 - else method = 2 - end - report("forcing method #{method}") - else - method = 2 - donepath = filepath + '/done' - resultpath = filepath + '/result' - report("default method #{method}") - end - - begin - File.makedirs(donepath) - File.makedirs(resultpath) - rescue - report('result path creation fails') - end - - if FileTest.directory?(donepath) && FileTest.directory?(resultpath) then - - resultname = resultpath + '/' + filename.sub(/\..*$/,'') + '.pdf' - - @commandline.setoption('inputpath', filepath) - @commandline.setoption('outputpath', resultpath) - @commandline.setoption('method', method) - - if ghostscript.psfile?(fullname) then - handle_ghostscript(ghostscript,filename) - else - handle_magick(magick,filename) - end - - sleep(1) # calm down - - if FileTest.file?(fullname) then - begin - File.copy(fullname,donepath + '/' + filename) - File.delete(fullname) - rescue - report('cleanup fails') - end - end - - end - - end - - end - - end - - waiting = true - end - - end - - private - - def handle_whatever(ghostscript,inkscape,magick,filename) - if ghostscript.psfile?(filename) then - # report("processing ps file #{filename}") - ghostscript.setvariable('pipe',false) if @commandline.option('nopipe') - # ghostscript.setvariable('pipe',not @commandline.option('nopipe')) - ghostscript.setvariable('colormodel',@commandline.option('colormodel')) - ghostscript.setvariable('offset',@commandline.option('offset')) - handle_ghostscript(ghostscript,filename) - elsif ghostscript.pdffile?(filename) && ghostscript.pdfmethod?(@commandline.option('method')) then - # report("processing pdf file #{filename}") - handle_ghostscript(ghostscript,filename) - elsif inkscape.supported?(filename) then - # report("processing non ps/pdf file #{filename}") - handle_inkscape(inkscape,filename) - elsif magick.supported?(filename) then - # report("processing non ps/pdf file #{filename}") - handle_magick(magick,filename) - end - end - - def handle_magick(magick,filename) - - report("converting non-ps file #{filename} into pdf") - - inppath = @commandline.option('inputpath') - outpath = @commandline.option('outputpath') - - inppath = inppath + '/' if not inppath.empty? - outpath = outpath + '/' if not outpath.empty? - - prefix = @commandline.option('prefix') - suffix = @commandline.option('suffix') - - inpfilename = "#{inppath}#{filename}" - outfilename = "#{outpath}#{prefix}#{filename.sub(/\.(.*?)$/, '')}#{suffix}.pdf" - - magick.setvariable('inputfile' , inpfilename) - magick.setvariable('outputfile', outfilename) - - magick.autoconvert - - end - - def handle_inkscape(inkscape,filename) - - report("converting svg(z) file #{filename} into pdf") - - inppath = @commandline.option('inputpath') - outpath = @commandline.option('outputpath') - - inppath = inppath + '/' if not inppath.empty? - outpath = outpath + '/' if not outpath.empty? - - prefix = @commandline.option('prefix') - suffix = @commandline.option('suffix') - - inpfilename = "#{inppath}#{filename}" - outfilename = "#{outpath}#{prefix}#{filename.sub(/\.(.*?)$/, '')}#{suffix}.pdf" - - inkscape.setvariable('inputfile' , inpfilename) - inkscape.setvariable('outputfile', outfilename) - - if @commandline.option('verbose') || @commandline.option('debug') then - logname = filename.gsub(/\.[^\.]*?$/, '.log') - report("log info saved in #{logname}") - inkscape.convert(logname) # logname ook doorgeven - else - inkscape.convert - end - - end - - def handle_ghostscript(ghostscript,filename) - - ghostscript.reset - - method = ghostscript.method(@commandline.option('method')) - force = ghostscript.method(@commandline.option('force')) - - ghostscript.setvariable('method', method) - ghostscript.setvariable('force', force) - - # report("conversion method #{method}") - - inppath = @commandline.option('inputpath') - outpath = @commandline.option('outputpath') - - inppath = inppath + '/' if not inppath.empty? - outpath = outpath + '/' if not outpath.empty? - - prefix = @commandline.option('prefix') - suffix = @commandline.option('suffix') - - ok = false - - if ghostscript.pdfmethod?(method) then - - report("converting pdf file #{filename} into pdf") - - if prefix.empty? && suffix.empty? && inppath.empty? && outpath.empty? then - prefix = ghostscript.pdfprefix(method) - end - - if ghostscript.pdffile?(filename) then - - filename = filename.sub(/\.pdf$/, '') - - inpfilename = "#{inppath}#{filename}.pdf" - outfilename = "#{outpath}#{prefix}#{filename}#{suffix}.pdf" - - ghostscript.setvariable('inputfile' ,inpfilename) - ghostscript.setvariable('outputfile',outfilename) - - if FileTest.file?(inpfilename) then - ok = ghostscript.convert - else - report("no file found #{filename}") - end - - else - report("no pdf file #{filename}") - end - - elsif ghostscript.psfile?(filename) then - - if filename =~ /(.*)\.(.*?)$/io then - filename, filesuffix = $1, $2 - else - filesuffix = 'eps' - end - - report("converting #{filesuffix} (ps) into pdf") - - inpfilename = "#{inppath}#{filename}.#{filesuffix}" - outfilename = "#{outpath}#{prefix}#{filename}#{suffix}.pdf" - - ghostscript.setvariable('inputfile' , inpfilename) - ghostscript.setvariable('outputfile', outfilename) - - if FileTest.file?(inpfilename) then - ok = ghostscript.convert - if ! ok && FileTest.file?(outfilename) then - begin - File.delete(outfilename) - rescue - end - end - else - report("no file with name #{filename} found") - end - - else - report('file must be of type eps/ps/ai/pdf') - end - - return ok - - end - -end - -# ook pdf -> pdf onder optie 0, andere kleurruimte - -logger = Logger.new(banner.shift) -commandline = CommandLine.new - -commandline.registerflag('debug') -commandline.registerflag('verbose') -commandline.registerflag('nopipe') - -commandline.registervalue('method',2) -commandline.registervalue('offset',0) - -commandline.registervalue('prefix') -commandline.registervalue('suffix') - -commandline.registervalue('inputpath') -commandline.registervalue('outputpath') - -commandline.registerflag('watch') -commandline.registerflag('force') - -commandline.registervalue('delay',2) - -commandline.registervalue('colormodel','cmyk') -commandline.registervalue('pattern','') - -commandline.registeraction('help') -commandline.registeraction('version') - -commandline.registeraction('convert', 'convert ps into pdf') -commandline.registeraction('request', 'handles exa request file') -commandline.registeraction('watch', 'watch folders for conversions (untested)') - -commandline.expand - -logger.verbose if (commandline.option('verbose') || commandline.option('debug')) - -Commands.new(commandline,logger,banner).send(commandline.action || 'main') +load(File.join(File.expand_path(File.dirname($0)),'pstopdf.rb')) diff --git a/Master/texmf-dist/scripts/context/ruby/newtexexec.rb b/Master/texmf-dist/scripts/context/ruby/newtexexec.rb index 27f8eeed96f..6b4db52dc51 100644 --- a/Master/texmf-dist/scripts/context/ruby/newtexexec.rb +++ b/Master/texmf-dist/scripts/context/ruby/newtexexec.rb @@ -1,609 +1,8 @@ -banner = ['TeXExec', 'version 6.1.0', '1997-2005', 'PRAGMA ADE/POD'] +#!/usr/bin/env ruby -unless defined? ownpath - ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') - $: << ownpath -end +# program : texexec +# copyright : PRAGMA Advanced Document Engineering +# version : 1997-2006 +# author : Hans Hagen -require 'base/switch' -require 'base/logger' -require 'base/variables' -require 'base/system' - -require 'base/tex' -require 'base/texutil' - -require 'ftools' # needed ? - -require 'base/kpse' # needed ? -# require 'base/pdf' # needed ? -require 'base/state' # needed ? -require 'base/file' # needed ? - -class Commands - - include CommandBase - - def make - if job = TEX.new(logger) then - prepare(job) - # bonus, overloads language switch ! - job.setvariable('language','all') if @commandline.option('all') - if @commandline.arguments.length > 0 then - if @commandline.arguments.first == 'all' then - job.setvariable('texformats',job.defaulttexformats) - job.setvariable('mpsformats',job.defaultmpsformats) - else - job.setvariable('texformats',@commandline.arguments) - job.setvariable('mpsformats',@commandline.arguments) - end - end - job.makeformats - job.inspect && Kpse.inspect if @commandline.option('verbose') - end - end - - def check - if job = TEX.new(logger) then - job.checkcontext - job.inspect && Kpse.inspect if @commandline.option('verbose') - end - end - - def main - if @commandline.arguments.length>0 then - process - else - help - end - end - - def process - if job = TEX.new(logger) then - job.setvariable('files',@commandline.arguments) - prepare(job) - job.processtex - job.inspect && Kpse.inspect if @commandline.option('verbose') - end - end - - def mptex - if job = TEX.new(logger) then - job.setvariable('files',@commandline.arguments) - prepare(job) - job.processmptex - job.inspect && Kpse.inspect if @commandline.option('verbose') - end - end - - def mpxtex - if job = TEX.new(logger) then - job.setvariable('files',@commandline.arguments) - prepare(job) - job.processmpxtex - job.inspect && Kpse.inspect if @commandline.option('verbose') - end - end - - # hard coded goodies # to be redone as s-ctx-.. with vars passed as such - - def listing - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - backspace = @commandline.checkedoption('backspace', '1.5cm') - topspace = @commandline.checkedoption('topspace', '1.5cm') - pretty = @commandline.option('pretty') - f << "% interface=english\n" - f << "\\setupbodyfont[11pt,tt]\n" - f << "\\setuplayout\n" - f << " [topspace=#{topspace},backspace=#{backspace},\n" - f << " header=0cm,footer=1.5cm,\n" - f << " width=middle,height=middle]\n" - f << "\\setuptyping[lines=yes]\n" - f << "\\setuptyping[option=color]\n" if pretty - f << "\\starttext\n"; - files.each do |filename| - report("list file: #{filename}") - cleanname = cleantexfilename(filename).downcase - f << "\\page\n" - f << "\\setupfootertexts[\\tttf #{cleanname}][\\tttf \\pagenumber]\n" - f << "\\typefile{#{filename}}\n" - end - f << "\\stoptext\n" - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - else - report('no files to list') - end - else - report('no files to list') - end - job.cleanuptemprunfiles - end - end - - def figures - # this one will be redone using rlxtools - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - job.runtexutil(files,"--figures", true) - figures = @commandline.checkedoption('method', 'a').downcase - paperoffset = @commandline.checkedoption('paperoffset', '0pt') - backspace = @commandline.checkedoption('backspace', '1.5cm') - topspace = @commandline.checkedoption('topspace', '1.5cm') - boxtype = @commandline.checkedoption('boxtype','') - f << "% format=english\n"; - f << "\\setuplayout\n"; - f << " [topspace=#{topspace},backspace=#{backspace},\n" - f << " header=1.5cm,footer=0pt,\n"; - f << " width=middle,height=middle]\n"; - if @commandline.option('fullscreen') then - f << "\\setupinteraction\n"; - f << " [state=start]\n"; - f << "\\setupinteractionscreen\n"; - f << " [option=max]\n"; - end - boxtype += "box" unless boxtype.empty? || (boxtype =~ /box$/io) - f << "\\starttext\n"; - f << "\\showexternalfigures[alternative=#{figures},offset=#{paperoffset},size=#{boxtype}]\n"; - f << "\\stoptext\n"; - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - File.silentdelete('texutil.tuf') - else - report('no figures to show') - end - else - report('no figures to show') - end - job.cleanuptemprunfiles - end - end - - def modules - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - msuffixes = ['tex','mp','pl','pm','rb'] - if files.length > 0 then - files.each do |fname| - fnames = Array.new - if FileTest.file?(fname) then - fnames << fname - else - msuffixes.each do |fsuffix| - fnames << File.suffixed(fname,fsuffix) - end - end - fnames.each do |ffname| - if msuffixes.include?(File.splitname(ffname)[1]) && FileTest.file?(ffname) then - if mod = File.open(job.tempfilename('tex'),'w') then - # will become a call to ctxtools - job.runtexutil(ffname,"--documents", true) - if ted = File.silentopen(File.suffixed(ffname,'ted')) then - firstline = ted.gets - if firstline =~ /interface=/o then - mod << firstline - else - mod << "% interface=en\n" - end - ted.close - else - mod << "% interface=en\n" - end - mod << "\\usemodule[abr-01,mod-01]\n" - mod << "\\def\\ModuleNumber{1}\n" - mod << "\\starttext\n" - # todo: global file too - mod << "\\readlocfile{#{File.suffixed(ffname,'ted')}}{}{}\n" - mod << "\\stoptext\n" - mod.close - job.setvariable('interface','english') # redundant - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - ["dvi", "pdf","tuo"].each do |s| - File.silentrename(job.tempfilename(s),File.suffixed(ffname,s)); - end - end - end - end - end - else - report('no modules to process') - end - job.cleanuptemprunfiles - end - end - - def arrange - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - emptypages = @commandline.checkedoption('addempty', '') - paperoffset = @commandline.checkedoption('paperoffset', '0cm') - textwidth = @commandline.checkedoption('textwidth', '0cm') - backspace = @commandline.checkedoption('backspace', '0cm') - topspace = @commandline.checkedoption('topspace', '0cm') - f << "\\definepapersize\n" - f << " [offset=#{paperoffset}]\n" - f << "\\setuplayout\n" - f << " [backspace=#{backspace},\n" - f << " topspace=#{topspace},\n" - f << " marking=on,\n" if @commandline.option('marking') - f << " width=middle,\n" - f << " height=middle,\n" - f << " location=middle,\n" - f << " header=0pt,\n" - f << " footer=0pt]\n" - unless @commandline.option('noduplex') then - f << "\\setuppagenumbering\n" - f << " [alternative=doublesided]\n" - end - f << "\\starttext\n" - files.each do |filename| - report("arranging file #{filename}") - f << "\\insertpages\n" - f << " [#{filename}]\n" - f << " [#{addempty}]\n" unless addempty.empty? - f << " [width=#{textwidth}]\n" - end - f << "\\stoptext\n" - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - else - report('no files to arrange') - end - else - report('no files to arrange') - end - job.cleanuptemprunfiles - end - end - - def select - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - selection = @commandline.checkedoption('selection', '') - paperoffset = @commandline.checkedoption('paperoffset', '0cm') - textwidth = @commandline.checkedoption('textwidth', '0cm') - backspace = @commandline.checkedoption('backspace', '0cm') - topspace = @commandline.checkedoption('topspace', '0cm') - paperformat = @commandline.checkedoption('paperformat', 'A4*A4').split(/[\*x]/o) - from, to = paperformat[0] || 'A4', paperformat[1] || paperformat[0] || 'A4' - if from == 'fit' or to == 'fit' then - f << "\\getfiguredimensions[#{files.first}]\n" - if from == 'fit' then - f << "\\expanded{\\definepapersize[from-fit][width=\\figurewidth,height=\\figureheight]}\n" - from = 'from-fit' - end - if to == 'fit' then - f << "\\expanded{\\definepapersize[to-fit][width=\\figurewidth,height=\\figureheight]}\n" - to = 'to-fit' - end - end - job.setvariable('paperformat','') # else overloaded later on - f << "\\setuppapersize[#{from}][#{to}]\n" - f << "\\definepapersize\n"; - f << " [offset=#{paperoffset}]\n"; - f << "\\setuplayout\n"; - f << " [backspace=#{backspace},\n"; - f << " topspace=#{topspace},\n"; - f << " marking=on,\n" if @commandline.option('marking') - f << " width=middle,\n"; - f << " height=middle,\n"; - f << " location=middle,\n"; - f << " header=0pt,\n"; - f << " footer=0pt]\n"; - f << "\\setupexternalfigures\n"; - f << " [directory=]\n"; - f << "\\starttext\n"; - unless selection.empty? then - f << "\\filterpages\n" - f << " [#{files.first}][#{selection}][width=#{textwidth}]\n" - end - f << "\\stoptext\n" - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - else - report('no files to selectt') - end - else - report('no files to select') - end - job.cleanuptemprunfiles - end - end - - def copy - copyortrim(false,'copy') - end - - def trim - copyortrim(true,'trim') - end - - def copyortrim(trim=false) - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - scale = @commandline.checkedoption('scale') - scale = (scale * 1000).to_i if scale < 10 - paperoffset = @commandline.checkedoption('paperoffset', '0cm') - f << "\\starttext\n" - files.each do |filename| - result = @commandline.checkedoption('result','texexec') - if (filename !~ /^texexec/io) && (filename !~ /^#{result}/) then - report("copying file: #{filename}") - f << "\\getfiguredimensions\n" - f << " [#{filename}]\n" - f << " [page=1" - f << ",\n size=trimbox" if trim - f << "]\n" - f << "\\definepapersize\n" - f << " [copy]\n" - f << " [width=\\naturalfigurewidth,\n" - f << " height=\\naturalfigureheight]\n" - f << "\\setuppapersize\n" - f << " [copy][copy]\n" - f << "\\setuplayout\n" - f << " [page]\n" - f << "\\setupexternalfigures\n" - f << " [directory=]\n" - f << "\\copypages\n" - f << " [#[filename}]\n" - f << " [scale=#{scale},\n" - f << " marking=on,\n" if @commandline.option('markings') - f << " size=trimbox,\n" if trim - f << " offset=#{paperoffset}]\n" - end - end - f << "\\stoptext\n" - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - else - report("no files to #{what}") - end - else - report("no files to #{what}") - end - job.cleanuptemprunfiles - end - end - - def combine - if job = TEX.new(logger) then - prepare(job) - job.cleanuptemprunfiles - files = @commandline.arguments.sort - if files.length > 0 then - if f = File.open(job.tempfilename('tex'),'w') then - paperoffset = @commandline.checkedoption('paperoffset', '0cm') - combination = @commandline.checkedoption('combination','2*2').split(/[\*x]/o) - paperformat = @commandline.checkedoption('paperoffset', 'A4*A4').split(/[\*x]/o) - nx, ny = combination[0] || '2', combination[1] || combination[0] || '2' - from, to = paperformat[0] || 'A4', paperformat[1] || paperformat[0] || 'A4' - f << "\\setuppapersize[#{from}][#{to}]\n" - f << "\\setuplayout\n" - f << " [topspace=#{paperoffset},\n" - f << " backspace=#{paperoffset},\n" - f << " header=0pt,\n" - f << " footer=1cm,\n" - f << " width=middle,\n" - f << " height=middle]\n" - if @commandline.option('nobanner') then - f << "\\setuplayout\n" - f << " [footer=0cm]\n" - end - f << "\\setupexternalfigures\n" - f << " [directory=]\n" - f << "\\starttext\n" - files.each do |filename| - result = @commandline.checkedoption('result','texexec') - if (filename !~ /^texexec/io) && (filename !~ /^#{result}/) then - report("combination file: #{filename}") - cleanname = cleantexfilename(filename).downcase - f << "\\setupfootertexts\n" - f << " [\\tttf #{cleanname}\\quad\\quad\\currentdate\\quad\\quad\\pagenumber]\n" - f << "\\combinepages[#{filename}][nx=#{nx},ny=#{ny}]\n" - f << "\\page\n" - end - end - f << "\\stoptext\n" - f.close - job.setvariable('interface','english') - job.setvariable('simplerun',true) - # job.setvariable('nooptionfile',true) - job.setvariable('files',[job.tempfilename]) - job.processtex - else - report('no files to list') - end - else - report('no files to list') - end - job.cleanuptemprunfiles - end - end - - private - - def prepare(job) - - job.booleanvars.each do |k| - job.setvariable(k,@commandline.option(k)) - end - job.stringvars.each do |k| - job.setvariable(k,@commandline.option(k)) - end - job.standardvars.each do |k| - job.setvariable(k,@commandline.option(k)) - end - job.knownvars.each do |k| - job.setvariable(k,@commandline.option(k)) unless @commandline.option(k).empty? - end - - if (str = @commandline.option('engine')) && ! str.standard? && ! str.empty? then - job.setvariable('texengine',str) - elsif @commandline.oneof('pdfetex','pdftex','pdf') then - job.setvariable('texengine','pdfetex') - elsif @commandline.oneof('xetex','xtx') then - job.setvariable('texengine','xetex') - elsif @commandline.oneof('aleph') then - job.setvariable('texengine','aleph') - else - job.setvariable('texengine','standard') - end - - if (str = @commandline.option('backend')) && ! str.standard? && ! str.empty? then - job.setvariable('backend',str) - elsif @commandline.oneof('pdfetex','pdftex','pdf') then - job.setvariable('backend','pdftex') - elsif @commandline.oneof('dvipdfmx','dvipdfm','dpx','dpm') then - job.setvariable('backend','dvipdfmx') - elsif @commandline.oneof('xetex','xtx') then - job.setvariable('backend','xetex') - elsif @commandline.oneof('aleph') then - job.setvariable('backend','dvipdfmx') - elsif @commandline.oneof('dvips','ps') then - job.setvariable('backend','dvips') - else - job.setvariable('backend','standard') - end - - if (str = @commandline.option('engine')) && ! str.standard? && ! str.empty? then - job.setvariable('mpsengine',@commandline.option('engine')) - else - job.setvariable('mpsengine','standard') - end - - end - - def cleantexfilename(filename) - filename.gsub(/([\$\_\#])/) do "\\$1" end.gsub(/([\~])/) do "\\string$1" end - end - -end - -logger = Logger.new(banner.shift) -commandline = CommandLine.new - -commandline.registeraction('make', 'make formats') -commandline.registeraction('check', 'check versions') -commandline.registeraction('process', 'process file') -commandline.registeraction('mptex', 'process mp file') -commandline.registeraction('mpxtex', 'process mpx file') - -commandline.registeraction('listing', 'list of file content') -commandline.registeraction('figures', 'generate overview of figures') -commandline.registeraction('modules', 'generate module documentation') -commandline.registeraction('arrange', 'impose pages (booklets)') -commandline.registeraction('select', 'select pages from file(s)') -commandline.registeraction('copy', 'copy pages from file(s)') -commandline.registeraction('trim', 'trim pages from file(s)') -commandline.registeraction('combine', 'combine multiple pages') - -@@extrastringvars = [ - 'pages', 'background', 'backspace', 'topspace', 'boxtype', 'tempdir', - 'printformat', 'paperformat', 'method', 'scale', 'selection', - 'combination', 'paperoffset', 'textwidth', 'addempty', 'logfile', - 'startline', 'endline', 'startcolumn', 'endcolumn', 'scale' -] - -@@extrabooleanvars = [ - 'centerpage', 'noduplex', 'color', 'pretty', - 'fullscreen', 'screensaver', 'markings' -] - -if job = TEX.new(logger) then - - job.setextrastringvars(@@extrastringvars) - job.setextrabooleanvars(@@extrabooleanvars) - - job.booleanvars.each do |k| - commandline.registerflag(k) - end - job.stringvars.each do |k| - commandline.registervalue(k,'') - end - job.standardvars.each do |k| - commandline.registervalue(k,'standard') - end - job.knownvars.each do |k| - commandline.registervalue(k,'') - end - -end - -# todo: register flags -> first one true - -commandline.registerflag('pdf') -commandline.registerflag('pdftex') -commandline.registerflag('pdfetex') - -commandline.registerflag('dvipdfmx') -commandline.registerflag('dvipdfm') -commandline.registerflag('dpx') -commandline.registerflag('dpm') - -commandline.registerflag('dvips') -commandline.registerflag('ps') - -commandline.registerflag('xetex') -commandline.registerflag('xtx') - -commandline.registerflag('aleph') - -commandline.registerflag('all') -commandline.registerflag('fast') - -# generic - -commandline.registeraction('help') -commandline.registeraction('version') - -commandline.registerflag('verbose') - -commandline.expand - -Commands.new(commandline,logger,banner).send(commandline.action || 'main') +load(File.join(File.expand_path(File.dirname($0)),'texexec.rb')) diff --git a/Master/texmf-dist/scripts/context/ruby/newtexutil.rb b/Master/texmf-dist/scripts/context/ruby/newtexutil.rb index af003a8e7b2..d6dd06a71ee 100644 --- a/Master/texmf-dist/scripts/context/ruby/newtexutil.rb +++ b/Master/texmf-dist/scripts/context/ruby/newtexutil.rb @@ -1,96 +1,8 @@ -banner = ['TeXUtil ', 'version 9.1.0', '1997-2005', 'PRAGMA ADE/POD'] +#!/usr/bin/env ruby -unless defined? ownpath - ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') - $: << ownpath -end +# program : texexec +# copyright : PRAGMA Advanced Document Engineering +# version : 1997-2006 +# author : Hans Hagen -require 'base/switch' -require 'base/logger' -require 'base/file' -require 'base/texutil' - -class Commands - - include CommandBase - - def references - filename = @commandline.argument('first') - if not filename.empty? and FileTest.file?(File.suffixed(filename,'tuo')) then - if tu = TeXUtil::Converter.new(logger) and tu.loaded(filename) then - tu.saved if tu.processed - end - end - end - - def main - if @commandline.arguments.length>0 then - references - else - help - end - end - - def purgefiles - system("texmfstart ctxtools --purge #{@commandline.argument.join(' ')}") - end - - def purgeallfiles - system("texmfstart ctxtools --purgeall #{@commandline.argument.join(' ')}") - end - - def documentation - system("texmfstart ctxtools --document #{@commandline.argument.join(' ')}") - end - - def analyzefile - system("texmfstart pdftools --analyze #{@commandline.argument.join(' ')}") - end - - def filterpages # obsolete - system("texmfstart ctxtools --purge #{@commandline.argument.join(' ')}") - end - - def figures - report("this code is not yet converted from perl to ruby") - end - - def logfile - report("this code is not yet converted from perl to ruby") - end - -end - -logger = Logger.new(banner.shift) -commandline = CommandLine.new - -# main feature - -commandline.registeraction('references', 'convert tui file into tuo file') - -# todo features - -commandline.registeraction('figures', 'generate figure dimensions file') -commandline.registeraction('logfile', 'filter essential log messages') - -# backward compatibility features - -commandline.registeraction('purgefiles', 'remove most temporary files') -commandline.registeraction('purgeallfiles', 'remove all temporary files') -commandline.registeraction('documentation', 'generate documentation file from source') -commandline.registeraction('analyzefile', 'analyze pdf file') - -# old feature, not needed any longer due to extension of pdftex - -commandline.registeraction('filterpages') - -# generic features - -commandline.registeraction('help') -commandline.registeraction('version') - -commandline.registerflag('verbose') - -commandline.expand - -Commands.new(commandline,logger,banner).send(commandline.action || 'main') +load(File.join(File.expand_path(File.dirname($0)),'texutil.rb')) diff --git a/Master/texmf-dist/scripts/context/ruby/pdftools.rb b/Master/texmf-dist/scripts/context/ruby/pdftools.rb new file mode 100644 index 00000000000..8a62a048728 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/pdftools.rb @@ -0,0 +1,816 @@ +#!/usr/bin/env ruby + +# program : pdftools +# copyright : PRAGMA Advanced Document Engineering +# version : 2003-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +# This script will harbor some handy manipulations on tex +# related files. + +banner = ['PDFTools', 'version 1.2.1', '2003/2005', 'PRAGMA ADE/POD'] + +unless defined? ownpath + ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') + $: << ownpath +end + +require 'base/switch' +require 'base/logger' + +require 'ftools' + +class File + + def File.deletefiles(*filenames) + filenames.flatten.each do |filename| + begin + delete(filename) if FileTest.file?(filename) + rescue + end + end + end + + def File.needsupdate(oldname,newname) + begin + return File.stat(oldname).mtime != File.stat(newname).mtime + rescue + return true + end + end + + def File.syncmtimes(oldname,newname) + begin + t = Time.now # i'm not sure if the time is frozen, so we do it here + File.utime(0,t,oldname,newname) + rescue + end + end + + def File.replacesuffix(oldname,subpath='') + newname = File.expand_path(oldname.sub(/\.\w+?$/,'.pdf')) + File.join(File.dirname(newname),subpath,File.basename(newname)) + end + +end + +class ImageMagick + + def initialize + + begin + version = `convert -version` + rescue + @binary = nil + ensure + if (version) && (! version.empty?) && (version =~ /ImageMagick/mo) && (version =~ /version/mio) then + @binary = 'convert' + else + @binary = 'imagemagick' + end + end + + end + + def process(arguments) + begin + @binary && system("#{@binary} #{arguments}") + rescue + false + end + end + +end + +class TexExec + + def initialize + @binary = 'texmfstart texexec.pl --pdf --batch --silent --purge' + end + + def process(arguments,once=true) + begin + if once then + @binary && system("#{@binary} --once #{arguments}") + else + @binary && system("#{@binary} #{arguments}") + end + rescue + false + end + end + +end + +class PdfImages + + def initialize + @binary = "pdfimages" + end + + def process(arguments) + begin + @binary && system("#{@binary} #{arguments}") + rescue + false + end + end + +end + +class ConvertImage + + def initialize(command=nil) + @command = command + end + + def convertimage(filename) + + return if filename =~ /\.(pdf)$/io + + retain = @command.option('retain') + subpath = @command.option('subpath') + + if filename =~ /\s/ then + @command.report("skipping strange filename '#{filename}'") + else + newname = File.replacesuffix(filename,subpath) + # newname.gsub!(s/[^a-zA-Z0-9\_-\.]/o, '-') + begin + File.makedirs(File.dirname(newname)) + rescue + end + if ! retain || File.needsupdate(filename,newname) then + imagemagick = ImageMagick.new + if imagemagick then + ok = imagemagick.process("-compress zip -quality 99 #{filename} #{newname}") + File.syncmtimes(oldname,newname) if retain + end + end + end + end + +end + +class DownsampleImage + + def initialize(command=nil) + @command = command + end + + def convertimage(filename) + + return if filename =~ /\.(pdf)$/io + + retain = @command.option('retain') + subpath = @command.option('subpath') + + if @command.option('lowres') then + method = '4' + elsif @command.option('medres') || @command.option('normal') then + method = '5' + else + method = '4' + end + + if filename =~ /\s/ then + @command.report("skipping strange filename '#{filename}'") + else + newname = File.replacesuffix(filename,subpath) + begin + File.makedirs(File.dirname(newname)) + rescue + end + if ! retain || File.needsupdate(filename,newname) then + ok = system("texmfstart pstopdf.rb --method=#{method} #{filename} #{newname}") + File.syncmtimes(oldname,newname) if retain + end + end + end + +end + +class ColorImage + + def initialize(command=nil,tmpname='pdftools') + @command = command + @tmpname = tmpname + @colorname = nil + @colorspec = nil + @colorspace = nil + end + + def registercolor(spec='.5',name='c') + name = name || 'c' + spec = spec.split(',') + case spec.length + when 4 + @colorname, @colorspec, @colorspace = name, spec.join('/'), 'cmyk' + when 3 + @colorname, @colorspec, @colorspace = name, spec.join('/'), 'rgb' + when 1 + @colorname, @colorspec, @colorspace = name, spec.join('/'), 'gray' + else + @colorname, @colorspec, @colorspace = nil, nil, nil + end + end + + def convertimage(filename) + + invert = @command.option('invert') + retain = @command.option('retain') + subpath = @command.option('subpath') + + subpath += '/' unless subpath.empty? + + if @colorname && ! @colorname.empty? && @colorspec && ! @colorspec.empty? then + basename = filename.sub(/\.\w+?$/,'') + oldname = filename + ppmname = @tmpname + '-000.ppm' + jpgname = @tmpname + '-000.jpg' + newname = File.expand_path(oldname) + newname = File.dirname(newname) + '/' + subpath + @colorname + '-' + File.basename(newname) + newname.sub!(/\.\w+?$/, '.pdf') + begin + File.makedirs(File.dirname(newname)) + rescue + end + if ! retain || File.needsupdate(filename,newname) then + pdfimages = PdfImages.new + imagemagick = ImageMagick.new + if pdfimages && imagemagick then + File.deletefiles(ppmname,jpgname,newname) + if filename =~ /\.(pdf)$/io then + ok = pdfimages.process("-j -f 1 -l 1 #{filename} #{@tmpname}") + if ok then + if FileTest.file?(ppmname) then + inpname = ppmname + elsif FileTest.file?(jpgname) then + inpname = jpgname + else + ok = false + end + if ok then + switch = if ! invert then '-negate' else '' end + # make sure that we keep the format + tmpname = File.basename(inpname) + tmpname = tmpname.sub(/(.*)\..*?$/,@tmpname) # somehow sub! fails here + ok = imagemagick.process("-colorspace gray #{switch} #{inpname} #{tmpname}") + if ! ok || ! FileTest.file?(tmpname) then + # problems + else + ok = imagemagick.process("-colorspace #{switch} #{@colorspace} -colorize #{@colorspec} -compress zip #{tmpname} #{newname}") + if ! ok || ! FileTest.file?(newname) then + # unable to colorize image + else + # conversion done + end + end + end + end + else + # make sure that we keep the format + tmpname = File.basename(basename) + tmpname = tmpname.sub(/(.*)\..*?$/,@tmpname) # somehow sub! fails here + ok = imagemagick.process("-colorspace gray #{oldname} #{tmpname}") + if ! ok || ! FileTest.file?(tmpname) then + # unable to convert color to gray + else + ok = imagemagick.process("-colorspace #{@colorspace} -colorize #{@colorspec} -compress zip #{tmpname} #{newname}") + if ! ok || ! FileTest.file?(newname) then + # unable to colorize image + else + # conversion done + end + end + end + File.deletefiles(ppmname,jpgname,tmpname) + File.syncmtimes(filename,newname) if retain + end + end + end + end + +end + +class SpotColorImage + + def initialize(command=nil, tmpname='pdftools') + @command = command + @tmpname = tmpname + @colorname = nil + @colorspec = nil + @colorspace = nil + @colorfile = nil + end + + def registercolor(spec='.5',name='unknown') + name = name || 'unknown' + if spec =~ /^[\d\.\,]+$/ then + spec = spec.split(',') + case spec.length + when 4 + @colorname, @colorspec, @colorspace = name, ["c=#{spec[0]}","m=#{spec[1]}","y=#{spec[2]}","k=#{spec[3]}"].join(','), 'cmyk' + when 3 + @colorname, @colorspec, @colorspace = name, ["r=#{spec[0]}","g=#{spec[1]}","b=#{spec[2]}"].join(','), 'rgb' + when 1 + @colorname, @colorspec, @colorspace = name, ["s=#{spec[0]}"].join(','), 'gray' + else + @colorname, @colorspec, @colorspace = nil, nil, nil + end + else + @colorname, @colorfile = name, spec + end + end + + def convertgrayimage(filename) + + invert = @command.option('invert') + retain = @command.option('retain') + subpath = @command.option('subpath') + + subpath += '/' unless subpath.empty? + + if @colorname && ! @colorname.empty? && ((@colorspec && ! @colorspec.empty?) || (@colorfile && ! @colorfile.empty?)) then + basename = filename.sub(/\.\w+?$/,'') + oldname = filename # png jpg pdf + newname = File.expand_path(oldname) + ppmname = @tmpname + '-000.ppm' + jpgname = @tmpname + '-000.jpg' + outname = @tmpname + '-000.pdf' + texname = @tmpname + '-temp.tex' + pdfname = @tmpname + '-temp.pdf' + newname = File.dirname(newname) + '/' + subpath + @colorname + '-' + File.basename(newname) + newname.sub!(/\.\w+?$/, '.pdf') + begin + File.makedirs(File.dirname(newname)) + rescue + end + if ! retain || File.needsupdate(filename,newname) then + pdfimages = PdfImages.new + imagemagick = ImageMagick.new + texexec = TexExec.new + if pdfimages && imagemagick && texexec then + if filename =~ /\.(jpg|png|pdf)$/io then + @command.report("processing #{basename}") + File.deletefiles(ppmname,jpgname,newname) + switch = if ! invert then '-negate' else '' end + if filename =~ /\.(pdf)$/io then + ok = pdfimages.process("-j -f 1 -l 1 #{oldname} #{@tmpname}") + if ok then + if FileTest.file?(ppmname) then + inpname = ppmname + elsif FileTest.file?(jpgname) then + inpname = jpgname + else + ok = false + end + if ok then + ok = imagemagick.process("-colorspace gray #{switch} -compress zip #{inpname} #{outname}") + end + end + else + ok = imagemagick.process("-colorspace gray #{switch} -compress zip #{oldname} #{outname}") + end + if ok then + ok = false unless FileTest.file?(outname) + end + if ok then + if f = File.open(texname, 'w') then + f.puts(conversionfile(filename,outname,newname)) + f.close + ok = texexec.process(texname) + else + ok = false + end + @command.report("error in processing #{newname}") unless ok + if FileTest.file?(pdfname) then + if f = File.open(pdfname,'r') then + f.binmode + begin + if g = File.open(newname,'w') then + g.binmode + data = f.read + # pdftex (direct) & imagemagick (indirect) + if data =~ /(\d+)\s+0\s+obj\s+\[\/Separation\s+\/#{@colorname}/mos then + @command.report("replacing separation color") + object = $1 + data.gsub!(/(\/Type\s+\/XObject.*?)(\/ColorSpace\s*(\/DeviceGray|\/DeviceCMYK|\/DeviceRGB|\d+\s+\d+\s+R))/mois) do + $1 + "/ColorSpace #{object} 0 R".ljust($2.length) + end + elsif data =~ /(\d+)\s+0\s+obj\s+\[\/Indexed\s*\[/mos then + @command.report("replacing indexed color") + # todo: more precise check on color + object = $1 + data.gsub!(/(\/Type\s+\/XObject.*?)(\/ColorSpace\s*(\/DeviceGray|\/DeviceCMYK|\/DeviceRGB|\d+\s+\d+\s+R))/mois) do + $1 + "/ColorSpace #{object} 0 R".ljust($2.length) + end + elsif data =~ /(\d+)\s+0\s+obj\s+\[\/Separation/mos then + @command.report("replacing separation color") + object = $1 + data.gsub!(/(\/Type\s+\/XObject.*?)(\/ColorSpace\s*(\/DeviceGray|\/DeviceCMYK|\/DeviceRGB|\d+\s+\d+\s+R))/mois) do + $1 + "/ColorSpace #{object} 0 R".ljust($2.length) + end + end + g.write(data) + g.close + end + rescue + @command.report("error in converting #{newname}") + else + @command.report("#{newname} is converted") + end + f.close + end + else + @command.report("error in writing #{newname}") + end + else + @command.report("error in producing #{newname}") + end + File.deletefiles(ppmname,jpgname,outname) + # File.deletefiles(texname,pdfname) + File.syncmtimes(filename,newname) if retain + end + else + @command.report("error in locating binaries") + end + else + @command.report("#{newname} is not changed") + end + end + end + + private + + # % example colorfile: + # + # \definecolor [darkblue] [c=1,m=.38,y=0,k=.64] % pantone pms 2965 uncoated m + # \definecolor [darkyellow] [c=0,m=.28,y=1,k=.06] % pantone pms 124 uncoated m + # + # % \definecolor [darkblue-100] [darkblue] [p=1] + # % \definecolor [darkyellow-100] [darkyellow] [p=1] + # + # \definecolorcombination [pdftoolscolor] [darkblue=.12,darkyellow=.28] [c=.1,m=.1,y=.3,k=.1] + + def conversionfile(originalname,filename,finalname) + tex = "\\setupcolors[state=start]\n" + if @colorfile then + tex += "\\readfile{#{@colorfile}}{}{}\n" + tex += "\\starttext\n" + # tex += "\\predefineindexcolor[pdftoolscolor]\n" + tex += "\\startTEXpage\n" + tex += "\\pdfimage{#{filename}}\n" + tex += "\\stopTEXpage\n" + tex += "\\stoptext\n" + else + tex += "\\definecolor[#{@colorname}][#{@colorspec}]\n" + tex += "\\definecolor[pdftoolscolor][#{@colorname}][p=1]\n" + tex += "\\starttext\n" + tex += "\\startTEXpage\n" + tex += "\\hbox{\\color[pdftoolscolor]{\\pdfimage{#{filename}}}}\n" + tex += "\\stopTEXpage\n" + tex += "\\stoptext\n" + end + tex += "\n" + tex += "% old: #{originalname}\n" + tex += "% new: #{finalname}\n" + return tex + end + +end + +module XML + + def XML::version + "<?xml version='1.0'?>" + end + + def XML::start(element, attributes='') + if attributes.empty? then + "<#{element}>" + else + "<#{element} #{attributes}>" + end + end + + def XML::end(element) + "</#{element}>" + end + + def XML::empty(element, attributes='') + if attributes && attributes.empty? then + "<#{element}/>" + else + "<#{element} #{attributes}/>" + end + end + + def XML::element(element, attributes='', content='') + if content && ! content.empty? then + XML::start(element,attributes) + content + XML::end(element) + else + XML::empty(element,attributes) + end + end + + def XML::box(tag, rect, type=1) + case type + when 1 + if rect && ! rect.empty? then + rect = rect.split(' ') + XML::element("#{tag}box", '', + XML::element("llx", '', rect[0]) + + XML::element("lly", '', rect[1]) + + XML::element("ulx", '', rect[2]) + + XML::element("uly", '', rect[3]) ) + else + XML::empty("#{tag}box") + end + when 2 + if rect && ! rect.empty? then + rect = rect.split(' ') + XML::element("box", "type='#{tag}'", + XML::element("llx", '', rect[0]) + + XML::element("lly", '', rect[1]) + + XML::element("ulx", '', rect[2]) + + XML::element("uly", '', rect[3]) ) + else + XML::empty("box", "type='#{tag}'") + end + when 3 + if rect && ! rect.empty? then + rect = rect.split(' ') + XML::element("box", "type='#{tag}' llx='#{rect[0]}' lly='#{rect[1]}' ulx='#{rect[2]}' uly='#{rect[3]}'") + else + XML::empty("box", "type='#{tag}'") + end + else + '' + end + end + + def XML::crlf + "\n" + end + + def XML::skip(n=1) + ' '*n + end + +end + +class Commands + + include CommandBase + + # alias savedhelp :help + + # def help + # savedhelp + # report("under construction (still separate tools)") + # end + + # filename.pdf --spotimage --colorname=darkblue --colorspec=1,0.38,0,0.64 + + def spotimage + + if ! @commandline.argument('first').empty? && files = findfiles() then + colorname = @commandline.option('colorname') + colorspec = @commandline.option('colorspec') + if colorname && ! colorname.empty? && colorspec && ! colorspec.empty? then + files.each do |filename| + s = SpotColorImage.new(self) + s.registercolor(colorspec,colorname) + s.convertgrayimage(filename) + end + else + report("provide --colorname=somename --colorspec=c,m,y,k") + end + else + report("provide filename (png, jpg, pdf)") + end + + end + + def colorimage + + if ! @commandline.argument('first').empty? && files = findfiles() then + colorname = @commandline.option('colorname') + colorspec = @commandline.option('colorspec') + if colorspec && ! colorspec.empty? then + files.each do |filename| + s = ColorImage.new(self) + s.registercolor(colorspec,colorname) # name optional + s.convertimage(filename) + end + else + report("provide --colorspec=c,m,y,k") + end + else + report("provide filename") + end + + end + + def convertimage + + if ! @commandline.argument('first').empty? && files = findfiles() then + files.each do |filename| + s = ConvertImage.new(self) + s.convertimage(filename) + end + else + report("provide filename") + end + + end + + def downsampleimage + + if ! @commandline.argument('first').empty? && files = findfiles() then + files.each do |filename| + s = DownsampleImage.new(self) + s.convertimage(filename) + end + else + report("provide filename") + end + + end + + def info + + if files = findfiles() then + + print(XML.version + XML.crlf) + print(XML.start('pdfinfo', "xmlns='http://www.pragma-ade.com/schemas/pdfinfo.rng'") + XML.crlf) + + files.each do |filename| + + if filename =~ /\.pdf$/io then + + begin + data = `pdfinfo -box #{filename}`.chomp.split("\n") + rescue + data = nil + end + + if data then + + pairs = Hash.new + + data.each do |d| + if (d =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/mois) then + key, val = $1, $2 + pairs[key.downcase.sub(/ /,'')] = val + end + end + + print(XML.skip(1) + XML.start('pdffile', "filename='#{filename}'") + XML.crlf) + + print(XML.skip(2) + XML.element('path', '', File.expand_path(filename)) + XML.crlf) + + if pairs.key?('error') then + + print(XML.skip(2) + XML.element('comment', '', pairs['error']) + XML.crlf) + + else + + print(XML.skip(2) + XML.element('version', '', pairs['pdfversion']) + XML.crlf) + print(XML.skip(2) + XML.element('pages', '', pairs['pages' ]) + XML.crlf) + print(XML.skip(2) + XML.element('title', '', pairs['title' ]) + XML.crlf) + print(XML.skip(2) + XML.element('subject', '', pairs['subject' ]) + XML.crlf) + print(XML.skip(2) + XML.element('author', '', pairs['author' ]) + XML.crlf) + print(XML.skip(2) + XML.element('producer', '', pairs['producer' ]) + XML.crlf) + + if pairs.key?('creationdate') then + pairs['creationdate'].sub!(/(\d\d)\/(\d\d)\/(\d\d)/) do + '20' + $3 + '-' + $1 + '-' +$2 + end + pairs['creationdate'].sub!(/(\d\d)\/(\d\d)\/(\d\d\d\d)/) do + $3 + '-' + $1 + '-' + $2 + end + print(XML.skip(2) + XML.element('creationdate', '', pairs['creationdate']) + XML.crlf) + end + + if pairs.key?('moddate') then + if pairs['moddate'] =~ /(\d\d\d\d)(\d\d)(\d\d)/ then + pairs['moddate'] = "#{$1}-#{$2}-#{$3}" + end + print(XML.skip(2) + XML.element('modificationdate', '', pairs['moddate']) + XML.crlf) + end + + print(XML.skip(2) + XML.element('tagged', '', pairs['tagged' ]) + XML.crlf) + print(XML.skip(2) + XML.element('encrypted', '', pairs['encrypted']) + XML.crlf) + print(XML.skip(2) + XML.element('optimized', '', pairs['optimized']) + XML.crlf) + + if pairs.key?('PageSize') then + print(XML.skip(2) + XML.element('width', '', pairs['pagesize'].sub(/\s*(.*?)\s+(.*?)\s+.*/, $1)) + XML.crlf) + print(XML.skip(2) + XML.element('height', '', pairs['pagesize'].sub(/\s*(.*?)\s+(.*?)\s+.*/, $2)) + XML.crlf) + end + + if pairs.key?('FileSize') then + print(XML.skip(2) + XML.element('size', '', pairs['filesize'].sub(/\s*(.*?)\s+.*/, $1)) + XML.crlf) + end + + print(XML.skip(2) + XML.box('media', pairs['mediabox']) + XML.crlf) + print(XML.skip(2) + XML.box('crop' , pairs['cropbox' ]) + XML.crlf) + print(XML.skip(2) + XML.box('bleed', pairs['bleedbox']) + XML.crlf) + print(XML.skip(2) + XML.box('trim' , pairs['trimBox' ]) + XML.crlf) + print(XML.skip(2) + XML.box('art' , pairs['artbox' ]) + XML.crlf) + + end + + print(XML.skip(1) + XML.end('pdffile') + XML.crlf) + + end + + end + + end + + print(XML.end('pdfinfo') + XML.crlf) + + end + + end + + def countpages + filenames, n = findfiles('pdf'), 0 + filenames.each do |filename| + if `pdfinfo #{filename}`.chomp =~ /^pages\s*\:\s*(\d+)/mois then + report("#{$1.rjust(4)} pages found in #{filename}") + n += $1.to_i + end + end + report("#{n.to_s.rjust(4)} pages in total") + end + + def analyzefile + # needs an update + filenames = @commandline.arguments + filenames.each do |filename| + if filename && FileTest.file?(filename) && filename =~ /\.pdf/io then + filesize = FileTest.size(filename) + report("analyzing file : #{filename}") + report("file size : #{filesize}") + if pdf = File.open(filename) then + pdf.binmode + nofobject, nofxform, nofannot, noflink, nofwidget, nofnamed, nofscript, nofcross = 0, 0, 0, 0, 0, 0, 0, 0 + while data = pdf.gets do + data.scan(/\d+\s+\d+\s+obj/o) do nofobject += 1 end + data.scan(/\/Type\s*\/XObject/o) do nofxform += 1 end + data.scan(/\/Type\s*\/Annot/o) do nofannot += 1 end + data.scan(/\/GoToR\s*\/F/o) do nofcross += 1 end + data.scan(/\/Subtype\s*\/Link/o) do noflink += 1 end + data.scan(/\/Subtype\s*\/Widget/o) do nofwidget += 1 end + data.scan(/\/S\s*\/Named/o) do nofnamed += 1 end + data.scan(/\/S\s*\/JavaScript/o) do nofscript += 1 end + end + pdf.close + report("objects : #{nofobject}") + report("xforms : #{nofxform}") + report("annotations : #{nofannot}") + report("links : #{noflink} (#{nofnamed} named / #{nofscript} scripts / #{nofcross} files)") + report("widgets : #{nofwidget}") + end + end + end + end + +end + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registeraction('spotimage' , 'filename --colorspec= --colorname= [--retain --invert --subpath=]') +commandline.registeraction('colorimage', 'filename --colorspec= [--retain --invert --colorname= ]') +commandline.registeraction('convertimage', 'filename [--retain --subpath]') +commandline.registeraction('downsampleimage', 'filename [--retain --subpath --lowres --normal]') +commandline.registeraction('info', 'filename') +commandline.registeraction('countpages', 'pattern') + +commandline.registeraction('analyzefile' , 'filename') + +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.registervalue('colorname') +commandline.registervalue('colorspec') +commandline.registervalue('subpath') + +commandline.registerflag('lowres') +commandline.registerflag('medres') +commandline.registerflag('normal') +commandline.registerflag('invert') +commandline.registerflag('retain') + +commandline.expand + +Commands.new(commandline,logger,banner).send(commandline.action || 'help') diff --git a/Master/texmf-dist/scripts/context/ruby/pstopdf.rb b/Master/texmf-dist/scripts/context/ruby/pstopdf.rb new file mode 100644 index 00000000000..197f83961ff --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/pstopdf.rb @@ -0,0 +1,527 @@ +#!/usr/bin/env ruby + +# program : pstopdf +# copyright : PRAGMA Advanced Document Engineering +# version : 2002-2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl +# www : www.pragma-ade.com + +banner = ['PsToPdf', 'version 2.0.1', '2002-2006', 'PRAGMA ADE/POD'] + +$: << File.expand_path(File.dirname($0)) + +# todo: paden/prefix in magick and inkscape +# todo: clean up method handling (pass strings, no numbers) +# --method=crop|bounded|raw|... +# --resolution=low|normal|medium|high|printer|print|screen|ebook|default +# + downward compatible flag handling + +require 'base/switch' +require 'base/tool' +require 'base/logger' + +require 'graphics/gs' +require 'graphics/magick' +require 'graphics/inkscape' + +require 'rexml/document' + +exit if defined?(REQUIRE2LIB) + +class Commands + + include CommandBase + + # nowadays we would force a directive, but + # for old times sake we handle default usage + + def main + filename = @commandline.argument('first') + pattern = @commandline.option('pattern') + globfiles(pattern) if filename.empty? && ! pattern.empty? + filename = @commandline.argument('first') + if filename.empty? then + help + elsif filename =~ /\.exa$/ then + request + else + convert + end + end + + # actions + + def convert + + ghostscript = GhostScript.new(logger) + magick = ImageMagick.new(logger) + inkscape = InkScape.new(logger) + + outpath = @commandline.option('outputpath') + unless outpath.empty? then + begin + File.expand_path(outpath) + outpath = File.makedirs(outpath) unless FileTest.directory?(outpath) + rescue + # sorry + end + end + + @commandline.arguments.each do |filename| + + filename = Tool.cleanfilename(filename,@commandline) + inppath = @commandline.option('inputpath') + if inppath.empty? then + inppath = '.' + fullname = filename # avoid duplicate './' + else + fullname = File.join(inppath,filename) + end + if FileTest.file?(fullname) then + handle_whatever(ghostscript,inkscape,magick,filename) + else + report("file #{fullname} does not exist") + end + + end + + end + + def request + + # <exa:request> + # <exa:application> + # <exa:command>pstopdf</exa:command> + # <exa:filename>E:/tmp/demo.ps</exa:filename> + # </exa:application> + # <exa:data> + # <exa:variable label='gs:DoThumbnails'>false</exa:variable> + # <exa:variable label='gs:ColorImageDepth'>-1</exa:variable> + # </exa:data> + # </exa:request> + + ghostscript = GhostScript.new(logger) + magick = ImageMagick.new(logger) + inkscape = InkScape.new(logger) + + dataname = @commandline.argument('first') || '' + filename = @commandline.argument('second') || '' + + if dataname.empty? || ! FileTest.file?(dataname) then + report('provide valid exa file') + return + else + begin + request = REXML::Document.new(File.new(dataname)) + rescue + report('provide valid exa file (xml error)') + return + end + end + if filename.empty? then + begin + if filename = REXML::XPath.first(request.root,"exa:request/exa:application/exa:filename/text()") then + filename = filename.to_s + else + report('no filename found in exa file') + return + end + rescue + filename = '' + end + end + if filename.empty? then + report('provide valid filename') + return + elsif ! FileTest.file?(filename) then + report("invalid filename #{filename}") + return + end + + [ghostscript,inkscape,magick].each do |i| + i.setvariable('inputfile',filename) + end + + # set ghostscript variables + REXML::XPath.each(request.root,"/exa:request/exa:data/exa:variable") do |v| + begin + if (key = v.attributes['label']) and (value = v.text.to_s) then + case key + when /gs[\:\.](var[\:\.])*(offset)/io then ghostscript.setoffset(value) + when /gs[\:\.](var[\:\.])*(method)/io then ghostscript.setvariable('method',value) + when /gs[\:\.](var[\:\.])*(.*)/io then ghostscript.setpsoption($2,value) + end + end + rescue + end + end + + # no inkscape and magick variables (yet) + + handle_whatever(ghostscript,inkscape,magick,filename) + + end + + def watch + + ghostscript = GhostScript.new(logger) + magick = ImageMagick.new(logger) + inkscape = InkScape.new(logger) + + pathname = commandline.option('watch') + + unless pathname and not pathname.empty? then + report('empty watchpath is not supported') + exit + end + + if pathname == '.' then + report("watchpath #{pathname} is not supported") + exit + end + + if FileTest.directory?(pathname) then + if Dir.chdir(pathname) then + report("watching path #{pathname}") + else + report("unable to change to path #{pathname}") + exit + end + else + report("invalid path #{pathname}") + exit + end + + waiting = false + + loop do + + if waiting then + report("waiting #{getvariable('delay')}") + waiting = false + sleep(getvariable('delay').to_i) + end + + files = Dir.glob("**/*.*") + + if files and files.length > 0 then + + files.each do |fullname| + + next unless fullname + + if FileTest.directory?(fullname) then + debug('skipping path', fullname) + next + end + + unless magick.supported(fullname) then + debug('not supported', fullname) + next + end + + if (! FileTest.file?(fullname)) || (FileTest.size(fullname) < 100) then + debug("skipping small crap file #{fullname}") + next + end + + debug("handling file #{fullname}") + + begin + next unless File.rename(fullname,fullname) # access trick + rescue + next # being written + end + + fullname = Tool.cleanfilename(fullname,@commandline) + + fullname.gsub!(/\\/io, '/') + + filename = File.basename(fullname) + filepath = File.dirname(fullname) + + next if filename =~ /gstemp.*/io + + if filepath !~ /(result|done|raw|crop|bound|bitmap)/io then + begin + File.makedirs(filepath+'/raw') + File.makedirs(filepath+'/bound') + File.makedirs(filepath+'/crop') + File.makedirs(filepath+'/bitmap') + debug("creating prefered input paths on #{filepath}") + rescue + debug("creating input paths on #{filepath} failed") + end + end + + if filepath =~ /^(.*\/|)(done|result)$/io then + debug("skipping file #{fullname}") + else + report("start processing file #{fullname}") + if filepath =~ /^(.*\/*)(raw|crop|bound)$/io then + donepath = $1 + 'done' + resultpath = $1 + 'result' + case $2 + when 'raw' then method = 1 + when 'bound' then method = 2 + when 'crop' then method = 3 + else method = 2 + end + report("forcing method #{method}") + else + method = 2 + donepath = filepath + '/done' + resultpath = filepath + '/result' + report("default method #{method}") + end + + begin + File.makedirs(donepath) + File.makedirs(resultpath) + rescue + report('result path creation fails') + end + + if FileTest.directory?(donepath) && FileTest.directory?(resultpath) then + + resultname = resultpath + '/' + filename.sub(/\.[^\.]*$/,'') + '.pdf' + + @commandline.setoption('inputpath', filepath) + @commandline.setoption('outputpath', resultpath) + @commandline.setoption('method', method) + + if ghostscript.psfile?(fullname) then + handle_ghostscript(ghostscript,filename) + else + handle_magick(magick,filename) + end + + sleep(1) # calm down + + if FileTest.file?(fullname) then + begin + File.copy(fullname,donepath + '/' + filename) + File.delete(fullname) + rescue + report('cleanup fails') + end + end + + end + + end + + end + + end + + waiting = true + end + + end + + private + + def handle_whatever(ghostscript,inkscape,magick,filename) + if ghostscript.psfile?(filename) then + # report("processing ps file #{filename}") + ghostscript.setvariable('pipe',false) if @commandline.option('nopipe') + # ghostscript.setvariable('pipe',not @commandline.option('nopipe')) + ghostscript.setvariable('colormodel',@commandline.option('colormodel')) + ghostscript.setvariable('offset',@commandline.option('offset')) + handle_ghostscript(ghostscript,filename) + elsif ghostscript.pdffile?(filename) && ghostscript.pdfmethod?(@commandline.option('method')) then + # report("processing pdf file #{filename}") + handle_ghostscript(ghostscript,filename) + elsif inkscape.supported?(filename) then + # report("processing non ps/pdf file #{filename}") + handle_inkscape(inkscape,filename) + elsif magick.supported?(filename) then + # report("processing non ps/pdf file #{filename}") + handle_magick(magick,filename) + end + end + + def handle_magick(magick,filename) + + report("converting non-ps file #{filename} into pdf") + + inppath = @commandline.option('inputpath') + outpath = @commandline.option('outputpath') + + inppath = inppath + '/' if not inppath.empty? + outpath = outpath + '/' if not outpath.empty? + + prefix = @commandline.option('prefix') + suffix = @commandline.option('suffix') + + inpfilename = "#{inppath}#{filename}" + outfilename = "#{outpath}#{prefix}#{filename.sub(/\.([^\.]*?)$/, '')}#{suffix}.pdf" + + magick.setvariable('inputfile' , inpfilename) + magick.setvariable('outputfile', outfilename) + + magick.autoconvert + + end + + def handle_inkscape(inkscape,filename) + + report("converting svg(z) file #{filename} into pdf") + + inppath = @commandline.option('inputpath') + outpath = @commandline.option('outputpath') + + inppath = inppath + '/' if not inppath.empty? + outpath = outpath + '/' if not outpath.empty? + + prefix = @commandline.option('prefix') + suffix = @commandline.option('suffix') + + inpfilename = "#{inppath}#{filename}" + outfilename = "#{outpath}#{prefix}#{filename.sub(/\.([^\.]*?)$/, '')}#{suffix}.pdf" + + inkscape.setvariable('inputfile' , inpfilename) + inkscape.setvariable('outputfile', outfilename) + + if @commandline.option('verbose') || @commandline.option('debug') then + logname = filename.gsub(/\.[^\.]*?$/, '.log') + report("log info saved in #{logname}") + inkscape.convert(logname) # logname ook doorgeven + else + inkscape.convert + end + + end + + def handle_ghostscript(ghostscript,filename) + + ghostscript.reset + + method = ghostscript.method(@commandline.option('method')) + force = ghostscript.method(@commandline.option('force')) + + ghostscript.setvariable('method', method) + ghostscript.setvariable('force', force) + + # report("conversion method #{method}") + + inppath = @commandline.option('inputpath') + outpath = @commandline.option('outputpath') + + inppath = inppath + '/' if not inppath.empty? + outpath = outpath + '/' if not outpath.empty? + + prefix = @commandline.option('prefix') + suffix = @commandline.option('suffix') + + ok = false + + if ghostscript.pdfmethod?(method) then + + report("converting pdf file #{filename} into pdf") + + if prefix.empty? && suffix.empty? && inppath.empty? && outpath.empty? then + prefix = ghostscript.pdfprefix(method) + end + + if ghostscript.pdffile?(filename) then + + filename = filename.sub(/\.pdf$/, '') + + inpfilename = "#{inppath}#{filename}.pdf" + outfilename = "#{outpath}#{prefix}#{filename}#{suffix}.pdf" + + ghostscript.setvariable('inputfile' ,inpfilename) + ghostscript.setvariable('outputfile',outfilename) + + if FileTest.file?(inpfilename) then + ok = ghostscript.convert + else + report("no file found #{filename}") + end + + else + report("no pdf file #{filename}") + end + + elsif ghostscript.psfile?(filename) then + + if filename =~ /(.*)\.([^\.]*?)$/io then + filename, filesuffix = $1, $2 + else + filesuffix = 'eps' + end + + report("converting #{filesuffix} (ps) into pdf") + + inpfilename = "#{inppath}#{filename}.#{filesuffix}" + outfilename = "#{outpath}#{prefix}#{filename}#{suffix}.pdf" + + ghostscript.setvariable('inputfile' , inpfilename) + ghostscript.setvariable('outputfile', outfilename) + + if FileTest.file?(inpfilename) then + ok = ghostscript.convert + if ! ok && FileTest.file?(outfilename) then + begin + File.delete(outfilename) + rescue + end + end + else + report("no file with name #{filename} found") + end + + else + report('file must be of type eps/ps/ai/pdf') + end + + return ok + + end + +end + +# ook pdf -> pdf onder optie 0, andere kleurruimte + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registerflag('debug') +commandline.registerflag('verbose') +commandline.registerflag('nopipe') + +commandline.registervalue('method',2) +commandline.registervalue('offset',0) + +commandline.registervalue('prefix') +commandline.registervalue('suffix') + +commandline.registervalue('inputpath') +commandline.registervalue('outputpath') + +commandline.registerflag('watch') +commandline.registerflag('force') + +commandline.registervalue('delay',2) + +commandline.registervalue('colormodel','cmyk') +commandline.registervalue('pattern','') + +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.registeraction('convert', 'convert ps into pdf') +commandline.registeraction('request', 'handles exa request file') +commandline.registeraction('watch', 'watch folders for conversions (untested)') + +commandline.expand + +logger.verbose if (commandline.option('verbose') || commandline.option('debug')) + +Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/rlxtools.rb b/Master/texmf-dist/scripts/context/ruby/rlxtools.rb index 7962474eb10..1225dedb301 100644 --- a/Master/texmf-dist/scripts/context/ruby/rlxtools.rb +++ b/Master/texmf-dist/scripts/context/ruby/rlxtools.rb @@ -97,20 +97,20 @@ 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 = 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| REXML::XPath.each(localsteps,tag) do |extras| REXML::XPath.each(extras,"rl:value") do |value| if name = value.attributes['name'] then - substititute(value,variables[name.to_s]) + substititute(value,variables[name.to_s] || '') end end end @@ -176,7 +176,7 @@ class Commands end end rescue - report('error in manipulating files') + report("error in manipulating files: #{$!}") end begin logname = "#{filename}.log" @@ -202,7 +202,7 @@ class Commands return str.strip end - def substititute(value,str) + def substititute(value,str='') if str then begin if value.attributes.key?('method') then @@ -247,10 +247,83 @@ class Commands end +class Commands + + include CommandBase + + def identify + @commandline.arguments.each do |filename| + if state = do_identify(filename) then + begin + File.open(filename+'.rli','w') do |f| + f << state + end + rescue + report("error in identifying #{filename}") + else + report("#{filename} is identified") + end + end + end + end + + private + + def do_identify(filename) + begin + str = nil + if FileTest.file?(filename) then + result = `identify -format \"x=%x,y=%y,w=%w,h=%h,b=%b\" #{filename}`.chomp.split(',') + tags = Hash.new + result.each do |r| + if rr = r.split("=") then + tags[rr[0]] = rr[1] + end + end + size = (tags['b']||0).to_i + width = unified(tags['w']||0,tags['x']||'1') + height = unified(tags['h']||0,tags['y']||'1') + if size > 0 then + str = '' + str << "<?xml version='1.0' standalone='yes'?>\n" + str << "<rl:identify name='#{File.basename(filename)}'>\n" + str << " <rl:size>#{size}</rl:size>\n" + str << " <rl:path>#{File.dirname(filename).sub(/\\/o,'/')}</rl:path>\n" + str << " <rl:width>#{width}</rl:width>\n" + str << " <rl:height>#{height}</rl:height>\n" + str << "</rl:identify>\n" + end + else + str = nil + end + rescue + str = nil + end + return str + end + + def unified(dim,res) + case res + when /([\d\.]+)\s*PixelsPerInch/io then + sprintf("%.4fin",dim.to_f/$1.to_f) + when /([\d\.]+)\s*PixelsPerCentimeter/io then + sprintf("%.4fcm",dim.to_f/$1.to_f) + when /([\d\.]+)\s*PixelsPerMillimeter/io then + sprintf("%.4fmm",dim.to_f/$1.to_f) + when /([\d\.]+)\s*PixelsPerPoint/io then + sprintf("%.4fbp",dim.to_f/$1.to_f) + else + sprintf("%.4fbp",dim.to_f) + end + end + +end + logger = Logger.new(banner.shift) commandline = CommandLine.new commandline.registeraction('manipulate', ' [--test] manipulatorfile resourselog') +commandline.registeraction('identify', 'filename') commandline.registeraction('help') commandline.registeraction('version') diff --git a/Master/texmf-dist/scripts/context/ruby/rscortool.rb b/Master/texmf-dist/scripts/context/ruby/rscortool.rb new file mode 100644 index 00000000000..c656fed8547 --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb b/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb new file mode 100644 index 00000000000..f3abfdfc703 --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/rslibtool.rb b/Master/texmf-dist/scripts/context/ruby/rslibtool.rb new file mode 100644 index 00000000000..7ae5efb647e --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/runtools.rb b/Master/texmf-dist/scripts/context/ruby/runtools.rb new file mode 100644 index 00000000000..9c504845ab1 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/runtools.rb @@ -0,0 +1,505 @@ +require 'timeout' +require 'ftools' +require 'rbconfig' + +class File + + # we don't want a/b//c + # + # puts File.join('a','b','c') + # puts File.join('/a','b','c') + # puts File.join('a:','b','c') + # puts File.join('a/','/b/','c') + # puts File.join('/a','/b/','c') + # puts File.join('//a/','/b/','c') + + def File.join(*list) + path, prefix = [*list].flatten.join(File::SEPARATOR), '' + path.sub!(/^([\/]+)/) do + prefix = $1 + '' + end + path.gsub!(/([\\\/]+)/) do + File::SEPARATOR + end + prefix + path + end + +end + + +class Job + + $ownfile, $ownpath = '', '' + + def Job::set_own_path(file) + $ownfile, $ownpath = File.basename(file), File.expand_path(File.dirname(file)) + $: << $ownpath + end + + def Job::ownfile + $ownfile + end + + def Job::ownpath + $ownpath + end + +end + +class Job + + def initialize + @startuppath = Dir.getwd + @log = Array.new + @testmode = false + @ownpath = $ownpath + @paths = Array.new + end + + def exit(showlog=false) + Dir.chdir(@startuppath) + show_log if showlog + Kernel::exit + end + + def platform + case Config::CONFIG['host_os'] + when /mswin/ then :windows + else :unix + end + end + + def path(*components) + File.join([*components].flatten) + end + + def found(name) + FileTest.file?(path(name)) || FileTest.directory?(path(name)) + end + + def binary(name) + if platform == :windows then + name.sub(/\.[^\/]+$/o,'') + '.exe' + else + name + end + end + + def suffixed(name,suffix) + if name =~ /\.[^\/]+$/o then + name + else + name + '.' + suffix + end + end + + def expanded(*name) + File.expand_path(File.join(*name)) + end + + def argument(n,default=nil) + ARGV[n] || default + end + + def variable(name,default='') + ENV[name] || default + end + + def change_dir(*dir) + dir, old = expanded(path(*dir)), expanded(Dir.getwd) + unless old == dir then + begin + Dir.chdir(dir) + rescue + error("unable to change to path #{dir}") + else + if old == dir then + error("error in changing to path #{dir}") + else + message("changed to path #{dir}") + end + end + end + # return File.expand_path(Dir.getwd) + end + + def delete_dir(*dir) + begin + dir = path(*dir) + pattern = "#{dir}/**/*" + puts("analyzing dir #{pattern}") + files = Dir.glob(pattern).sort.reverse + files.each do |f| + begin + # FileTest.file?(f) fails on .whatever files + File.delete(f) + rescue + # probably directory + else + puts("deleting file #{f}") + end + end + files.each do |f| + begin + Dir.rmdir(f) + rescue + # strange + else + message("deleting path #{f}") + end + end + begin + Dir.rmdir(dir) + rescue + # strange + else + message("deleting parent #{dir}") + end + Dir.glob(pattern).sort.each do |f| + warning("unable to delete #{f}") + end + rescue + warning("unable to delete path #{File.expand_path(dir)} (#{$!})") + else + message("path #{File.expand_path(dir)} removed") + end + end + + + def create_dir(*dir) + begin + dir = path(*dir) + unless FileTest.directory?(dir) then + File.makedirs(dir) + else + return + end + rescue + error("unable to create path #{File.expand_path(dir)}") + else + message("path #{File.expand_path(dir)} created") + end + end + + def show_dir(delay=0) + _puts_("\n") + print Dir.getwd + ' ' + begin + timeout(delay) do + loop do + print '.' + sleep(1) + end + end + rescue TimeoutError + # ok + end + _puts_("\n\n") + end + + def copy_file(from,to='.',exclude=[]) + to, ex = path(to), [exclude].flatten + Dir.glob(path(from)).each do |file| + tofile = to.sub(/[\.\*]$/o) do File.basename(file) end + _do_copy_(file,tofile) unless ex.include?(File.extname(file)) + end + end + + def clone_file(from,to) + if from and to then + to = File.join(File.basename(from),to) if File.basename(to).empty? + _do_copy_(from,to) + end + end + + 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 + 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 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 + create_dir(File.dirname(tofile)) + File.copy(file,tofile) + rescue + error("unable to copy #{file} to #{tofile}") + else + message("file #{file} copied to #{tofile}") + end + else + puts("file #{file} is not copied") + end + end + + def rename_file(from,to) + from, to = path(from), path(to) + begin + File.move(from,to) + rescue + error("unable to rename #{from} to #{to}") + else + message("#{from} renamed to #{to}") + end + end + + def delete_file(pattern) + Dir.glob(path(pattern)).each do |file| + _do_delete_(file) + end + end + + def delete_files(*files) + [*files].flatten.each do |file| + _do_delete_(file) + end + end + + def _do_delete_(file) + if FileTest.file?(file) then + begin + File.delete(file) + rescue + error("unable to delete file #{file}") + else + message("file #{file} deleted") + end + else + message("no file #{File.expand_path(file)}") + end + end + + def show_log(filename=nil) + if filename then + begin + if f = File.open(filename,'w') then + @log.each do |line| + f.puts(line) + end + f.close + end + message("log data written to #{filename}") + rescue + error("unable to write log to #{filename}") + end + else + @log.each do |line| + _puts_(line) + end + end + end + + def _puts_(str) + begin + STDOUT.puts( str) + rescue + STDERR.puts("error while writing '#{str}' to terminal") + end + end + + def puts(message) + @log << message + _puts_(message) + end + + def error(message) + puts("! #{message}") + exit + end + + def warning(message) + puts("- #{message}") + end + + def message(message) + puts("+ #{message}") + end + + def export_variable(variable,value) + value = path(value) if value.class == Array + ENV[variable] = value + message("environment variable #{variable} set to #{value}") + return value + end + + def execute_command(*command) + begin + command = [*command].flatten.join(' ') + message("running '#{command}'") + _puts_("\n") + ok = system(command) + _puts_("\n") + if true then # ok then + message("finished '#{command}'") + else + error("error in running #{command}") + end + rescue + error("unable to run #{command}") + end + end + + def pipe_command(*command) + begin + command = [*command].flatten.join(' ') + message("running '#{command}'") + result = `#{command}` + _puts_("\n") + _puts_(result) + _puts_("\n") + rescue + error("unable to run #{command}") + end + end + + def execute_script(script) + script = suffixed(script,'rb') + script = path(script_path,File.basename(script)) unless found(script) + if found(script) then + begin + message("loading script #{script}") + load(script) + rescue + error("error in loading script #{script} (#{$!})") + else + message("script #{script} finished") + end + else + warning("no script #{script}") + end + end + + def execute_binary(*command) + command = [*command].flatten.join(' ').split(' ') + command[0] = binary(command[0]) + execute_command(command) + end + + def extend_path(pth) + export_variable('PATH',"#{path(pth)}#{File::PATH_SEPARATOR}#{ENV['PATH']}") + end + + def startup_path + @startuppath + end + + def current_path + Dir.getwd + end + + def script_path + @ownpath + end + + def push_path(newpath) + newpath = File.expand_path(newpath) + @paths.push(newpath) + change_dir(newpath) + end + + def pop_path + change_dir(if @paths.length > 0 then @paths.pop else @startuppath end) + end + + # runner = Runner.new + # runner.texmfstart('texexec','--help') + + def texmfstart(name,args,verbose=false) + command = ['texmfstart',"#{'--verbose' if verbose}",name,args].flatten.join(' ') + system(command) + end + +end + +class Job + + # copied from texmfstart and patched (message/error), different name + + def use_tree(tree) + unless tree.empty? then + begin + setuptex = File.join(tree,'setuptex.tmf') + if FileTest.file?(setuptex) then + message("tex tree : #{setuptex}") + ENV['TEXPATH'] = tree.sub(/\/+$/,'') # + '/' + ENV['TMP'] = ENV['TMP'] || ENV['TEMP'] || ENV['TMPDIR'] || ENV['HOME'] + case RUBY_PLATFORM + when /(mswin|bccwin|mingw|cygwin)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-mswin' + when /(linux)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-linux' + when /(darwin|rhapsody|nextstep)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-macosx' + # when /(netbsd|unix)/i then # todo + else # todo + end + ENV['TEXMFOS'] = "#{ENV['TEXPATH']}/#{ENV['TEXOS']}" + message("preset : TEXPATH => #{ENV['TEXPATH']}") + message("preset : TEXOS => #{ENV['TEXOS']}") + message("preset : TEXMFOS => #{ENV['TEXMFOS']}") + message("preset : TMP => #{ENV['TMP']}") + IO.readlines(File.join(tree,'setuptex.tmf')).each do |line| + case line + when /^[\#\%]/ then + # comment + when /^(.*?)\s+\=\s+(.*)\s*$/ then + k, v = $1, $2 + ENV[k] = v.gsub(/\%(.*?)\%/) do + ENV[$1] || '' + end + message("user set : #{k} => #{ENV[k]}") + end + end + else + warning("no setup file '#{setuptex}', tree not initialized") # no error + end + rescue + warning("error in setup: #{$!}") + end + end + end + +end + +Job::set_own_path($0) + +if Job::ownfile == 'runtools.rb' then + + begin + script = ARGV.shift + if script then + script += '.rb' if File.extname(script).empty? + fullname = File.expand_path(script) + fullname = File.join(Job::ownpath,script) unless FileTest.file?(fullname) + if FileTest.file?(fullname) then + puts("loading script #{fullname}") + Job::set_own_path(fullname) + load(fullname) + else + puts("unknown script #{fullname}") + end + else + puts("provide script name") + end + rescue + puts("fatal error: #{$!}") + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/texexec.rb b/Master/texmf-dist/scripts/context/ruby/texexec.rb new file mode 100644 index 00000000000..1a7175f230e --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/texexec.rb @@ -0,0 +1,708 @@ +banner = ['TeXExec', 'version 6.2.0', '1997-2006', 'PRAGMA ADE/POD'] + +unless defined? ownpath + ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') + $: << ownpath +end + +require 'ftools' # needed ? + +require 'base/switch' +require 'base/logger' +require 'base/variables' +require 'base/system' + +require 'base/state' # needed ? +require 'base/file' # needed ? + +require 'base/tex' +require 'base/texutil' +require 'base/kpse' + +class Commands + + include CommandBase + + def make + if job = TEX.new(logger) then + prepare(job) + # bonus, overloads language switch ! + job.setvariable('language','all') if @commandline.option('all') + if @commandline.arguments.length > 0 then + if @commandline.arguments.first == 'all' then + job.setvariable('texformats',job.defaulttexformats) + job.setvariable('mpsformats',job.defaultmpsformats) + else + job.setvariable('texformats',@commandline.arguments) + job.setvariable('mpsformats',@commandline.arguments) + end + end + job.makeformats + job.inspect && Kpse.inspect if @commandline.option('verbose') + end + end + + def check + if job = TEX.new(logger) then + job.checkcontext + job.inspect && Kpse.inspect if @commandline.option('verbose') + end + end + + def main + if @commandline.arguments.length>0 then + process + else + help + end + end + + def process + if job = TEX.new(logger) then + job.setvariable('files',@commandline.arguments) + prepare(job) + job.processtex + job.inspect && Kpse.inspect if @commandline.option('verbose') + exit 1 if job.error? + end + end + + def mptex + if job = TEX.new(logger) then + job.setvariable('files',@commandline.arguments) + prepare(job) + job.processmptex + job.inspect && Kpse.inspect if @commandline.option('verbose') + exit 1 if job.error? + end + end + + def mpxtex + if job = TEX.new(logger) then + job.setvariable('files',@commandline.arguments) + prepare(job) + job.processmpxtex + job.inspect && Kpse.inspect if @commandline.option('verbose') + exit 1 if job.error? + end + end + + def mpgraphic + if job = TEX.new(logger) then + job.setvariable('files',@commandline.arguments) + prepare(job) + job.processmpgraphic + job.inspect && Kpse.inspect if @commandline.option('verbose') + exit 1 if job.error? + end + end + + # hard coded goodies # to be redone as s-ctx-.. with vars passed as such + + def listing + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + backspace = @commandline.checkedoption('backspace', '1.5cm') + topspace = @commandline.checkedoption('topspace', '1.5cm') + pretty = @commandline.option('pretty') + f << "% interface=english\n" + f << "\\setupbodyfont[11pt,tt]\n" + f << "\\setuplayout\n" + f << " [topspace=#{topspace},backspace=#{backspace},\n" + f << " header=0cm,footer=1.5cm,\n" + f << " width=middle,height=middle]\n" + f << "\\setuptyping[lines=yes]\n" + f << "\\setuptyping[option=color]\n" if pretty + f << "\\starttext\n"; + files.each do |filename| + report("list file: #{filename}") + cleanname = cleantexfilename(filename).downcase + f << "\\page\n" + f << "\\setupfootertexts[\\tttf #{cleanname}][\\tttf \\pagenumber]\n" + f << "\\typefile{#{filename}}\n" + end + f << "\\stoptext\n" + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + else + report('no files to list') + end + else + report('no files to list') + end + job.cleanuptemprunfiles + end + end + + def figures + # this one will be redone using rlxtools + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + # will be replaced, does not work any more + Kpse.runscript('texutil.pl',files.join(' '),'--figures') + figures = @commandline.checkedoption('method', 'a').downcase + paperoffset = @commandline.checkedoption('paperoffset', '0pt') + backspace = @commandline.checkedoption('backspace', '1.5cm') + topspace = @commandline.checkedoption('topspace', '1.5cm') + boxtype = @commandline.checkedoption('boxtype','') + f << "% format=english\n"; + f << "\\setuplayout\n"; + f << " [topspace=#{topspace},backspace=#{backspace},\n" + f << " header=1.5cm,footer=0pt,\n"; + f << " width=middle,height=middle]\n"; + if @commandline.option('fullscreen') then + f << "\\setupinteraction\n"; + f << " [state=start]\n"; + f << "\\setupinteractionscreen\n"; + f << " [option=max]\n"; + end + boxtype += "box" unless boxtype.empty? || (boxtype =~ /box$/io) + f << "\\starttext\n"; + f << "\\showexternalfigures[alternative=#{figures},offset=#{paperoffset},size=#{boxtype}]\n"; + f << "\\stoptext\n"; + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + File.silentdelete('texutil.tuf') + else + report('no figures to show') + end + else + report('no figures to show') + end + job.cleanuptemprunfiles + end + end + + def modules + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + msuffixes = ['tex','mp','pl','pm','rb'] + if files.length > 0 then + files.each do |fname| + fnames = Array.new + if FileTest.file?(fname) then + fnames << fname + else + msuffixes.each do |fsuffix| + fnames << File.suffixed(fname,fsuffix) + end + end + fnames.each do |ffname| + if msuffixes.include?(File.splitname(ffname)[1]) && FileTest.file?(ffname) then + if mod = File.open(job.tempfilename('tex'),'w') then + Kpse.runscript('ctxtools',ffname,'--document') + if ted = File.silentopen(File.suffixed(ffname,'ted')) then + firstline = ted.gets + if firstline =~ /interface=/o then + mod << firstline + else + mod << "% interface=en\n" + end + ted.close + else + mod << "% interface=en\n" + end + mod << "\\usemodule[abr-01,mod-01]\n" + mod << "\\def\\ModuleNumber{1}\n" + mod << "\\starttext\n" + # todo: global file too + mod << "\\readlocfile{#{File.suffixed(ffname,'ted')}}{}{}\n" + mod << "\\stoptext\n" + mod.close + job.setvariable('interface','english') # redundant + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + ["dvi", "pdf","tuo"].each do |s| + File.silentrename(job.tempfilename(s),File.suffixed(ffname,s)); + end + end + end + end + end + else + report('no modules to process') + end + job.cleanuptemprunfiles + end + end + + def arrangeoutput + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + emptypages = @commandline.checkedoption('addempty', '') + paperoffset = @commandline.checkedoption('paperoffset', '0cm') + textwidth = @commandline.checkedoption('textwidth', '0cm') + backspace = @commandline.checkedoption('backspace', '0cm') + topspace = @commandline.checkedoption('topspace', '0cm') + f << "\\definepapersize\n" + f << " [offset=#{paperoffset}]\n" + f << "\\setuplayout\n" + f << " [backspace=#{backspace},\n" + f << " topspace=#{topspace},\n" + f << " marking=on,\n" if @commandline.option('marking') + f << " width=middle,\n" + f << " height=middle,\n" + f << " location=middle,\n" + f << " header=0pt,\n" + f << " footer=0pt]\n" + unless @commandline.option('noduplex') then + f << "\\setuppagenumbering\n" + f << " [alternative=doublesided]\n" + end + f << "\\starttext\n" + files.each do |filename| + report("arranging file #{filename}") + f << "\\insertpages\n" + f << " [#{filename}]\n" + f << " [#{emptypages}]\n" unless emptypages.empty? + f << " [width=#{textwidth}]\n" + end + f << "\\stoptext\n" + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + job.setvariable('arrange',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + else + report('no files to arrange') + end + else + report('no files to arrange') + end + job.cleanuptemprunfiles + end + end + + def selectoutput + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + selection = @commandline.checkedoption('selection', '') + paperoffset = @commandline.checkedoption('paperoffset', '0cm') + textwidth = @commandline.checkedoption('textwidth', '0cm') + backspace = @commandline.checkedoption('backspace', '0cm') + topspace = @commandline.checkedoption('topspace', '0cm') + paperformat = @commandline.checkedoption('paperformat', 'A4*A4').split(/[\*x]/o) + from, to = paperformat[0] || 'A4', paperformat[1] || paperformat[0] || 'A4' + if from == 'fit' or to == 'fit' then + f << "\\getfiguredimensions[#{files.first}]\n" + if from == 'fit' then + f << "\\expanded{\\definepapersize[from-fit][width=\\figurewidth,height=\\figureheight]}\n" + from = 'from-fit' + end + if to == 'fit' then + f << "\\expanded{\\definepapersize[to-fit][width=\\figurewidth,height=\\figureheight]}\n" + to = 'to-fit' + end + end + job.setvariable('paperformat','') # else overloaded later on + f << "\\setuppapersize[#{from}][#{to}]\n" + f << "\\definepapersize\n"; + f << " [offset=#{paperoffset}]\n"; + f << "\\setuplayout\n"; + f << " [backspace=#{backspace},\n"; + f << " topspace=#{topspace},\n"; + f << " marking=on,\n" if @commandline.option('marking') + f << " width=middle,\n"; + f << " height=middle,\n"; + f << " location=middle,\n"; + f << " header=0pt,\n"; + f << " footer=0pt]\n"; + f << "\\setupexternalfigures\n"; + f << " [directory=]\n"; + f << "\\starttext\n"; + unless selection.empty? then + f << "\\filterpages\n" + f << " [#{files.first}][#{selection}][width=#{textwidth}]\n" + end + f << "\\stoptext\n" + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + else + report('no files to selectt') + end + else + report('no files to select') + end + job.cleanuptemprunfiles + end + end + + def copyoutput + copyortrim(false,'copy') + end + + def trimoutput + copyortrim(true,'trim') + end + + def copyortrim(trim=false,what='unknown') + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + scale = @commandline.checkedoption('scale') + scale = (scale * 1000).to_i if scale.to_i < 10 + paperoffset = @commandline.checkedoption('paperoffset', '0cm') + f << "\\starttext\n" + files.each do |filename| + result = @commandline.checkedoption('result','texexec') + if (filename !~ /^texexec/io) && (filename !~ /^#{result}/) then + report("copying file: #{filename}") + f << "\\getfiguredimensions\n" + f << " [#{filename}]\n" + f << " [page=1" + f << ",\n size=trimbox" if trim + f << "]\n" + f << "\\definepapersize\n" + f << " [copy]\n" + f << " [width=\\naturalfigurewidth,\n" + f << " height=\\naturalfigureheight]\n" + f << "\\setuppapersize\n" + f << " [copy][copy]\n" + f << "\\setuplayout\n" + f << " [page]\n" + f << "\\setupexternalfigures\n" + f << " [directory=]\n" + f << "\\copypages\n" + f << " [#{filename}]\n" + f << " [scale=#{scale},\n" + f << " marking=on,\n" if @commandline.option('markings') + f << " size=trimbox,\n" if trim + f << " offset=#{paperoffset}]\n" + end + end + f << "\\stoptext\n" + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + else + report("no files to #{what}") + end + else + report("no files to #{what}") + end + job.cleanuptemprunfiles + end + end + + # todo: make this styles + + def combineoutput + if job = TEX.new(logger) then + prepare(job) + job.cleanuptemprunfiles + files = @commandline.arguments.sort + if files.length > 0 then + if f = File.open(job.tempfilename('tex'),'w') then + paperoffset = @commandline.checkedoption('paperoffset', '0cm') + combination = @commandline.checkedoption('combination','2*2').split(/[\*x]/o) + paperformat = @commandline.checkedoption('paperformat', 'A4*A4').split(/[\*x]/o) + bannerheight = @commandline.checkedoption('bannerheight', '') + nx, ny = combination[0] || '2', combination[1] || combination[0] || '2' + from, to = paperformat[0] || 'A4', paperformat[1] || paperformat[0] || 'A4' + f << "\\setuppapersize[#{from}][#{to}]\n" + f << "\\setuplayout\n" + f << " [topspace=#{paperoffset},backspace=#{paperoffset},\n" + f << " header=0pt,footer=0pt,\n" + f << " width=middle,height=middle]\n" + if bannerheight.empty? then + f << "\\setuplayout[footer=1cm]\n" + else + f << "\\definelayer[page][width=\\paperwidth,height=\\paperheight]\n" + f << "\\setupbackgrounds[page][background=page]\n" + end + if @commandline.option('nobanner') then + f << "\\setuplayout[footer=0cm]\n" + f << "\\setupbackgrounds[page][background=]\n" + end + f << "\\setupexternalfigures\n" + f << " [directory=]\n" + f << "\\starttext\n" + files.each do |filename| + result = @commandline.checkedoption('result','texexec') + if (filename !~ /^texexec/io) && (filename !~ /^#{result}/) then + report("combination file: #{filename}") + cleanname = cleantexfilename(filename).downcase + bannerstring = "\\tttf #{cleanname}\\quad\\quad\\currentdate\\quad\\quad\\pagenumber" + if bannerheight.empty? then + f << "\\setupfootertexts\n" + f << " [#{bannerstring}]\n" + else + # for the moment we lack a better hook + f << "\\setuptexttexts\n" + f << " [{\\setlayerframed[page][preset=middlebottom][frame=off,height=#{bannerheight}]{#{bannerstring}}}]\n" + end + f << "\\combinepages[#{filename}][nx=#{nx},ny=#{ny}]\n" + f << "\\page\n" + end + end + f << "\\stoptext\n" + f.close + job.setvariable('interface','english') + job.setvariable('simplerun',true) + # job.setvariable('nooptionfile',true) + job.setvariable('files',[job.tempfilename]) + job.processtex + else + report('no files to list') + end + else + report('no files to list') + end + job.cleanuptemprunfiles + end + end + + private + + def prepare(job) + + job.booleanvars.each do |k| + job.setvariable(k,@commandline.option(k)) + end + job.stringvars.each do |k| + job.setvariable(k,@commandline.option(k)) unless @commandline.option(k).empty? + end + job.standardvars.each do |k| + job.setvariable(k,@commandline.option(k)) unless @commandline.option(k).empty? + end + job.knownvars.each do |k| + job.setvariable(k,@commandline.option(k)) unless @commandline.option(k).empty? + end + + if (str = @commandline.option('engine')) && ! str.standard? && ! str.empty? then + job.setvariable('texengine',str) + elsif @commandline.oneof('pdfetex','pdftex','pdf') then + job.setvariable('texengine','pdfetex') + elsif @commandline.oneof('xetex','xtx') then + job.setvariable('texengine','xetex') + elsif @commandline.oneof('aleph') then + job.setvariable('texengine','aleph') + elsif @commandline.oneof('luatex') then + job.setvariable('texengine','luatex') + else + job.setvariable('texengine','standard') + end + + if (str = @commandline.option('backend')) && ! str.standard? && ! str.empty? then + job.setvariable('backend',str) + elsif @commandline.oneof('pdfetex','pdftex','pdf','luatex') then + job.setvariable('backend','pdftex') + elsif @commandline.oneof('dvipdfmx','dvipdfm','dpx','dpm') then + job.setvariable('backend','dvipdfmx') + elsif @commandline.oneof('xetex','xtx') then + job.setvariable('backend','xetex') + elsif @commandline.oneof('aleph') then + job.setvariable('backend','dvipdfmx') + elsif @commandline.oneof('dvips','ps') then + job.setvariable('backend','dvips') + elsif @commandline.oneof('xdv') then + job.setvariable('backend','xdv') + else + case job.getvariable('texengine') + when 'pdfetex' then job.setvariable('backend','pdftex') + when 'luatex' then job.setvariable('backend','pdftex') + when 'xetex' then job.setvariable('backend','xetex') + when 'aleph' then job.setvariable('backend','dvipdfmx') + else + job.setvariable('backend','standard') + end + end + + if (str = @commandline.option('engine')) && ! str.standard? && ! str.empty? then + job.setvariable('mpsengine',@commandline.option('engine')) + else + job.setvariable('mpsengine','standard') + end + + end + + def cleantexfilename(filename) + filename.gsub(/([\$\_\#])/) do "\\$1" end.gsub(/([\~])/) do "\\string$1" end + end + +end + +# we will make this pluggable, i.e. load plugins from base/tex that +# extend the class and may even add switches +# +# commandline.load_plugins('base/tex') +# +# maybe it's too slow so for a while keep the --pdf* in here + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registeraction('make', 'make formats') +commandline.registeraction('check', 'check versions') +commandline.registeraction('process', 'process file') +commandline.registeraction('mptex', 'process mp file') +commandline.registeraction('mpxtex', 'process mpx file') +commandline.registeraction('mpgraphic', 'process mp file to stand-alone graphics') + +commandline.registeraction('listing', 'list of file content') +commandline.registeraction('figures', 'generate overview of figures') +commandline.registeraction('modules', 'generate module documentation') +commandline.registeraction('pdfarrange', 'impose pages (booklets)') +commandline.registeraction('pdfselect', 'select pages from file(s)') +commandline.registeraction('pdfcopy', 'copy pages from file(s)') +commandline.registeraction('pdftrim', 'trim pages from file(s)') +commandline.registeraction('pdfcombine', 'combine multiple pages') + +# compatibility switch + +class Commands + + include CommandBase + + alias pdfarrange :arrangeoutput + alias pdfselect :selectoutput + alias pdfcopy :copyoutput + alias pdftrim :trimoutput + alias pdfcombine :combineoutput + +end + +# so far for compatibility, will move to tex + +@@extrastringvars = [ + 'pages', 'background', 'backspace', 'topspace', 'boxtype', 'tempdir','bannerheight', + 'printformat', 'method', 'scale', 'selection', + 'combination', 'textwidth', 'addempty', 'logfile', + 'startline', 'endline', 'startcolumn', 'endcolumn', 'scale' +] + +@@extrabooleanvars = [ + 'centerpage', 'noduplex', 'color', 'pretty', + 'fullscreen', 'screensaver', 'markings' +] + +if job = TEX.new(logger) then + + job.setextrastringvars(@@extrastringvars) + job.setextrabooleanvars(@@extrabooleanvars) + + job.booleanvars.each do |k| + commandline.registerflag(k) + end + job.stringvars.each do |k| + commandline.registervalue(k,'') + end + job.standardvars.each do |k| + commandline.registervalue(k,'standard') + end + job.knownvars.each do |k| + commandline.registervalue(k,'') + end + +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') +commandline.registerflag('pdftex') +commandline.registerflag('pdfetex') +commandline.registerflag('luatex') + +commandline.registerflag('dvipdfmx') +commandline.registerflag('dvipdfm') +commandline.registerflag('dpx') +commandline.registerflag('dpm') + +commandline.registerflag('dvips') +commandline.registerflag('ps') + +commandline.registerflag('xetex') +commandline.registerflag('xtx') +commandline.registerflag('xdv') + +commandline.registerflag('aleph') + +commandline.registerflag('all') +commandline.registerflag('fast') + +# generic + +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.registerflag('verbose') + +commandline.expand + +Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/texmfstart.rb b/Master/texmf-dist/scripts/context/ruby/texmfstart.rb index dc166bf92c3..411cb924873 100644 --- a/Master/texmf-dist/scripts/context/ruby/texmfstart.rb +++ b/Master/texmf-dist/scripts/context/ruby/texmfstart.rb @@ -2,7 +2,7 @@ # program : texmfstart # copyright : PRAGMA Advanced Document Engineering -# version : 1.5.5 - 2003/2005 +# version : 1.9.0 - 2003/2006 # author : Hans Hagen # # project : ConTeXt / eXaMpLe @@ -23,6 +23,7 @@ # --locate => provides location # --exec => exec instead of system # --iftouched=a,b => only if timestamp a<>b +# --ifchanged=a,b => only if checksum changed # # file: path: bin: @@ -32,20 +33,1410 @@ $ownpath = File.expand_path(File.dirname($0)) unless defined? $ownpath +$: << $ownpath + require "rbconfig" +require "md5" + +# kpse_merge_done: require 'base/kpseremote' +# kpse_merge_done: require 'base/kpsedirect' +# kpse_merge_done: require 'base/kpsefast' +# kpse_merge_done: require 'base/merge' + +# kpse_merge_start + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/kpseremote.rb' + +# kpse_merge_done: require 'base/kpsefast' + +case ENV['KPSEMETHOD'] + when /soap/o then # kpse_merge_done: require 'base/kpse/soap' + when /drb/o then # kpse_merge_done: require 'base/kpse/drb' + else # kpse_merge_done: require 'base/kpse/drb' +end + +class KpseRemote + + @@port = ENV['KPSEPORT'] || 7000 + @@method = ENV['KPSEMETHOD'] || 'drb' + + def KpseRemote::available? + @@method && @@port + end + + def KpseRemote::start_server(port=nil) + kpse = KpseServer.new(port || @@port) + kpse.start + end + + def KpseRemote::start_client(port=nil) # keeps object in server + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpse = kpseclient.object + tree = kpse.choose(KpseUtil::identify, KpseUtil::environment) + [kpse, tree] + end + + def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object + kpseclient = KpseClient.new(port || @@port) + kpseclient.start + kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil + end + + def initialize(port=nil) + if KpseRemote::available? then + begin + @kpse, @tree = KpseRemote::start_client(port) + rescue + @kpse, @tree = nil, nil + end + else + @kpse, @tree = nil, nil + end + end + + def progname=(value) + @kpse.set(@tree,'progname',value) + end + def format=(value) + @kpse.set(@tree,'format',value) + end + def engine=(value) + @kpse.set(@tree,'engine',value) + end + + def progname + @kpse.get(@tree,'progname') + end + def format + @kpse.get(@tree,'format') + end + def engine + @kpse.get(@tree,'engine') + end + + def load + @kpse.load(KpseUtil::identify, KpseUtil::environment) + end + def okay? + @kpse && @tree + end + def set(key,value) + @kpse.set(@tree,key,value) + end + def load_cnf + @kpse.load_cnf(@tree) + end + def load_lsr + @kpse.load_lsr(@tree) + end + def expand_variables + @kpse.expand_variables(@tree) + end + def expand_braces(str) + clean_name(@kpse.expand_braces(@tree,str)) + end + def expand_path(str) + clean_name(@kpse.expand_path(@tree,str)) + end + def expand_var(str) + clean_name(@kpse.expand_var(@tree,str)) + end + def show_path(str) + clean_name(@kpse.show_path(@tree,str)) + end + def var_value(str) + clean_name(@kpse.var_value(@tree,str)) + end + def find_file(filename) + clean_name(@kpse.find_file(@tree,filename)) + end + def find_files(filename,first=false) + # dodo: each filename + @kpse.find_files(@tree,filename,first) + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end + + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/kpsefast.rb' + +# module : base/kpsefast +# copyright : PRAGMA Advanced Document Engineering +# version : 2005 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl + + +class String + + def split_path + if self =~ /\;/o || self =~ /^[a-z]\:/io then + self.split(";") + else + self.split(":") + end + end + +end + +class Array + + def join_path + self.join(File::PATH_SEPARATOR) + end + +end + +class File + + def File.locate_file(path,name) + begin + files = Dir.entries(path) + if files.include?(name) then + fullname = File.join(path,name) + return fullname if FileTest.file?(fullname) + end + files.each do |p| + fullname = File.join(path,p) + if p != '.' and p != '..' and FileTest.directory?(fullname) and result = locate_file(fullname,name) then + return result + end + end + rescue + # bad path + end + return nil + end + + def File.glob_file(pattern) + return Dir.glob(pattern).first + end + +end + +module KpseUtil + + # to be adapted, see loading cnf file + + @@texmftrees = ['texmf-local','texmf.local','../..','texmf'] # '../..' is for gwtex + @@texmfcnf = 'texmf.cnf' + + def KpseUtil::identify + # we mainly need to identify the local tex stuff and wse assume that + # the texmfcnf variable is set; otherwise we need to expand the + # TEXMF variable and that takes time since it may involve more + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + ENV['SELFAUTOPARENT'] = ownpath + else + ENV['SELFAUTOPARENT'] = '.' # fall back + # may be too tricky: + # + # (ENV['PATH'] ||'').split_path.each do |p| + # if p.gsub!(/texmf.*?$/o, '') then + # ENV['SELFAUTOPARENT'] = p + # break + # end + # end + end + filenames = Array.new + if ENV['TEXMFCNF'] && ! ENV['TEXMFCNF'].empty? then + ENV['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif ENV['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + @@texmftrees.each do |tree| + filenames << File.join(ENV['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end + end + loop do + busy = false + filenames.collect! do |f| + f.gsub(/\$([a-zA-Z0-9\_\-]+)/o) do + if (! ENV[$1]) || (ENV[$1] == $1) then + "$#{$1}" + else + busy = true + ENV[$1] + end + end + end + break unless busy + end + filenames.delete_if do |f| + ! FileTest.file?(f) + end + return filenames + end + + def KpseUtil::environment + Hash.new.merge(ENV) + end + +end + +class KpseFast + + # formats are an incredible inconsistent mess + + @@suffixes = Hash.new + @@formats = Hash.new + @@suffixmap = Hash.new + + @@texmfcnf = 'texmf.cnf' + + @@suffixes['gf'] = ['.<resolution>gf'] # todo + @@suffixes['pk'] = ['.<resolution>pk'] # todo + @@suffixes['tfm'] = ['.tfm'] + @@suffixes['afm'] = ['.afm'] + @@suffixes['base'] = ['.base'] + @@suffixes['bib'] = ['.bib'] + @@suffixes['bst'] = ['.bst'] + @@suffixes['cnf'] = ['.cnf'] + @@suffixes['ls-R'] = ['ls-R', 'ls-r'] + @@suffixes['fmt'] = ['.fmt', '.efmt', '.efm', '.ofmt', '.ofm', '.oft', '.eofmt', '.eoft', '.eof', '.pfmt', '.pfm', '.epfmt', '.epf', '.xpfmt', '.xpf', '.afmt', '.afm'] + @@suffixes['map'] = ['.map'] + @@suffixes['mem'] = ['.mem'] + @@suffixes['mf'] = ['.mf'] + @@suffixes['mfpool'] = ['.pool'] + @@suffixes['mft'] = ['.mft'] + @@suffixes['mp'] = ['.mp'] + @@suffixes['mppool'] = ['.pool'] + @@suffixes['ocp'] = ['.ocp'] + @@suffixes['ofm'] = ['.ofm', '.tfm'] + @@suffixes['opl'] = ['.opl'] + @@suffixes['otp'] = ['.otp'] + @@suffixes['ovf'] = ['.ovf'] + @@suffixes['ovp'] = ['.ovp'] + @@suffixes['graphic/figure'] = ['.eps', '.epsi'] + @@suffixes['tex'] = ['.tex'] + @@suffixes['texpool'] = ['.pool'] + @@suffixes['PostScript header'] = ['.pro'] + @@suffixes['type1 fonts'] = ['.pfa', '.pfb'] + @@suffixes['vf'] = ['.vf'] + @@suffixes['ist'] = ['.ist'] + @@suffixes['truetype fonts'] = ['.ttf', '.ttc'] + @@suffixes['web'] = ['.web', '.ch'] + @@suffixes['cweb'] = ['.w', '.web', '.ch'] + @@suffixes['enc files'] = ['.enc'] + @@suffixes['cmap files'] = ['.cmap'] + @@suffixes['subfont definition files'] = ['.sfd'] + @@suffixes['lig files'] = ['.lig'] + @@suffixes['bitmap font'] = [] + @@suffixes['MetaPost support'] = [] + @@suffixes['TeX system documentation'] = [] + @@suffixes['TeX system sources'] = [] + @@suffixes['Troff fonts'] = [] + @@suffixes['dvips config'] = [] + @@suffixes['type42 fonts'] = [] + @@suffixes['web2c files'] = [] + @@suffixes['other text files'] = [] + @@suffixes['other binary files'] = [] + @@suffixes['misc fonts'] = [] + @@suffixes['opentype fonts'] = [] + @@suffixes['pdftex config'] = [] + @@suffixes['texmfscripts'] = [] + + # replacements + + @@suffixes['fmt'] = ['.fmt'] + @@suffixes['type1 fonts'] = ['.pfa', '.pfb', '.pfm'] + @@suffixes['tex'] = ['.tex', '.xml'] + @@suffixes['texmfscripts'] = ['rb','lua','py','pl'] + + @@suffixes.keys.each do |k| @@suffixes[k].each do |s| @@suffixmap[s] = k end end + + # TTF2TFMINPUTS + # MISCFONTS + # TEXCONFIG + # DVIPDFMINPUTS + # OTFFONTS + + @@formats['gf'] = '' + @@formats['pk'] = '' + @@formats['tfm'] = 'TFMFONTS' + @@formats['afm'] = 'AFMFONTS' + @@formats['base'] = 'MFBASES' + @@formats['bib'] = '' + @@formats['bst'] = '' + @@formats['cnf'] = '' + @@formats['ls-R'] = '' + @@formats['fmt'] = 'TEXFORMATS' + @@formats['map'] = 'TEXFONTMAPS' + @@formats['mem'] = 'MPMEMS' + @@formats['mf'] = 'MFINPUTS' + @@formats['mfpool'] = 'MFPOOL' + @@formats['mft'] = '' + @@formats['mp'] = 'MPINPUTS' + @@formats['mppool'] = 'MPPOOL' + @@formats['ocp'] = 'OCPINPUTS' + @@formats['ofm'] = 'OFMFONTS' + @@formats['opl'] = 'OPLFONTS' + @@formats['otp'] = 'OTPINPUTS' + @@formats['ovf'] = 'OVFFONTS' + @@formats['ovp'] = 'OVPFONTS' + @@formats['graphic/figure'] = '' + @@formats['tex'] = 'TEXINPUTS' + @@formats['texpool'] = 'TEXPOOL' + @@formats['PostScript header'] = 'TEXPSHEADERS' + @@formats['type1 fonts'] = 'T1FONTS' + @@formats['vf'] = 'VFFONTS' + @@formats['ist'] = '' + @@formats['truetype fonts'] = 'TTFONTS' + @@formats['web'] = '' + @@formats['cweb'] = '' + @@formats['enc files'] = 'ENCFONTS' + @@formats['cmap files'] = 'CMAPFONTS' + @@formats['subfont definition files'] = 'SFDFONTS' + @@formats['lig files'] = 'LIGFONTS' + @@formats['bitmap font'] = '' + @@formats['MetaPost support'] = '' + @@formats['TeX system documentation'] = '' + @@formats['TeX system sources'] = '' + @@formats['Troff fonts'] = '' + @@formats['dvips config'] = '' + @@formats['type42 fonts'] = 'T42FONTS' + @@formats['web2c files'] = 'WEB2C' + @@formats['other text files'] = '' + @@formats['other binary files'] = '' + @@formats['misc fonts'] = '' + @@formats['opentype fonts'] = 'OPENTYPEFONTS' + @@formats['pdftex config'] = 'PDFTEXCONFIG' + @@formats['texmfscripts'] = 'TEXMFSCRIPTS' + + attr_accessor :progname, :engine, :format, :rootpath, :treepath, + :verbose, :remember, :scandisk, :diskcache, :renewcache + + @@cacheversion = '1' + + def initialize + @rootpath = '' + @treepath = '' + @progname = 'kpsewhich' + @engine = 'pdfetex' + @variables = Hash.new + @expansions = Hash.new + @files = Hash.new + @found = Hash.new + @kpsevars = Hash.new + @lsrfiles = Array.new + @cnffiles = Array.new + @verbose = true + @remember = true + @scandisk = true + @diskcache = true + @renewcache = false + @isolate = false + + @diskcache = false + @cachepath = nil + @cachefile = 'tmftools.log' + + @environment = ENV + end + + def set(key,value) + case key + when 'progname' then @progname = value + when 'engine' then @engine = value + when 'format' then @format = value + end + end + + def push_environment(env) + @environment = env + end + + # {$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c} + # + # $SELFAUTOLOC : /usr/tex/bin/platform + # $SELFAUTODIR : /usr/tex/bin + # $SELFAUTOPARENT : /usr/tex + # + # since we live in scriptpath we need a slightly different method + + def load_cnf(filenames=nil) + unless filenames then + ownpath = File.expand_path($0) + if ownpath.gsub!(/texmf.*?$/o, '') then + @environment['SELFAUTOPARENT'] = ownpath + else + @environment['SELFAUTOPARENT'] = '.' + end + unless @treepath.empty? then + unless @rootpath.empty? then + @treepath = @treepath.split(',').collect do |p| File.join(@rootpath,p) end.join(',') + end + @environment['TEXMF'] = @treepath + # only the first one + @environment['TEXMFCNF'] = File.join(@treepath.split(',').first,'texmf/web2c') + end + unless @rootpath.empty? then + @environment['TEXMFCNF'] = File.join(@rootpath,'texmf/web2c') + @environment['SELFAUTOPARENT'] = @rootpath + @isolate = true + end + filenames = Array.new + if @environment['TEXMFCNF'] and not @environment['TEXMFCNF'].empty? then + @environment['TEXMFCNF'].to_s.split_path.each do |path| + filenames << File.join(path,@@texmfcnf) + end + elsif @environment['SELFAUTOPARENT'] == '.' then + filenames << File.join('.',@@texmfcnf) + else + ['texmf-local','texmf'].each do |tree| + filenames << File.join(@environment['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf) + end + end + end + # <root>/texmf/web2c/texmf.cnf + filenames = _expanded_path_(filenames) + @rootpath = filenames.first + 3.times do + @rootpath = File.dirname(@rootpath) + end + filenames.collect! do |f| + f.gsub("\\", '/') + end + filenames.each do |fname| + if FileTest.file?(fname) and f = File.open(fname) then + @cnffiles << fname + while line = f.gets do + loop do + # concatenate lines ending with \ + break unless line.sub!(/\\\s*$/o) do + f.gets || '' + end + end + case line + when /^[\%\#]/o then + # comment + when /^\s*(.*?)\s*\=\s*(.*?)\s*$/o then + key, value = $1, $2 + unless @variables.key?(key) then + value.sub!(/\%.*$/,'') + value.sub!(/\~/, "$HOME") + @variables[key] = value + end + @kpsevars[key] = true + end + end + f.close + end + end + end + + def load_lsr + @lsrfiles = [] + simplified_list(expansion('TEXMF')).each do |p| + ['ls-R','ls-r'].each do |f| + filename = File.join(p,f) + if FileTest.file?(filename) then + @lsrfiles << [filename,File.size(filename)] + break + end + end + end + @files = Hash.new + if @diskcache then + ['HOME','TEMP','TMP','TMPDIR'].each do |key| + if @environment[key] then + if FileTest.directory?(@environment[key]) then + @cachepath = @environment[key] + @cachefile = [@rootpath.gsub(/[^A-Z0-9]/io, '-').gsub(/\-+/,'-'),File.basename(@cachefile)].join('-') + break + end + end + end + if @cachepath and not @renewcache and FileTest.file?(File.join(@cachepath,@cachefile)) then + begin + if f = File.open(File.join(@cachepath,@cachefile)) then + cacheversion = Marshal.load(f) + if cacheversion == @@cacheversion then + lsrfiles = Marshal.load(f) + if lsrfiles == @lsrfiles then + @files = Marshal.load(f) + end + end + f.close + end + rescue + @files = Hash.new + end + end + end + return if @files.size > 0 + @lsrfiles.each do |filedata| + filename, filesize = filedata + filepath = File.dirname(filename) + begin + path = '.' + data = IO.readlines(filename) + if data[0].chomp =~ /% ls\-R \-\- filename database for kpathsea\; do not change this line\./io then + data.each do |line| + case line + when /^[a-zA-Z0-9]/o then + line.chomp! + if @files[line] then + @files[line] << path + else + @files[line] = [path] + end + when /^\.\/(.*?)\:$/o then + path = File.join(filepath,$1) + end + end + end + rescue + # sorry + end + end + if @diskcache and @cachepath and f = File.open(File.join(@cachepath,@cachefile),'wb') then + f << Marshal.dump(@@cacheversion) + f << Marshal.dump(@lsrfiles) + f << Marshal.dump(@files) + f.close + end + end + + def expand_variables + @expansions = Hash.new + if @isolate then + @variables['TEXMFCNF'] = @environment['TEXMFCNF'].dup + @variables['SELFAUTOPARENT'] = @environment['SELFAUTOPARENT'].dup + else + @environment.keys.each do |e| + if e =~ /^([a-zA-Z]+)\_(.*)\s*$/o then + @expansions["#{$1}.#{$2}"] = (@environment[e] ||'').dup + else + @expansions[e] = (@environment[e] ||'').dup + end + end + end + @variables.keys.each do |k| + @expansions[k] = @variables[k].dup unless @expansions[k] + end + loop do + busy = false + @expansions.keys.each do |k| + @expansions[k].gsub!(/\$([a-zA-Z0-9\_\-]*)/o) do + busy = true + @expansions[$1] || '' + end + @expansions[k].gsub!(/\$\{([a-zA-Z0-9\_\-]*)\}/o) do + busy = true + @expansions[$1] || '' + end + end + break unless busy + end + @expansions.keys.each do |k| + @expansions[k] = @expansions[k].gsub("\\", '/') + end + end + + def variable(name='') + (name and not name.empty? and @variables[name.sub('$','')]) or '' + end + + def expansion(name='') + (name and not name.empty? and @expansions[name.sub('$','')]) or '' + end + + def variable?(name='') + name and not name.empty? and @variables.key?(name.sub('$','')) + end + + def expansion?(name='') + name and not name.empty? and @expansions.key?(name.sub('$','')) + end + + def simplified_list(str) + lst = str.gsub(/^\{/o,'').gsub(/\}$/o,'').split(",") + lst.collect do |l| + l.sub(/^[\!]*/,'').sub(/[\/\\]*$/o,'') + end + end + + def original_variable(variable) + if variable?("#{@progname}.#{variable}") then + variable("#{@progname}.#{variable}") + elsif variable?(variable) then + variable(variable) + else + '' + end + end + + def expanded_variable(variable) + if expansion?("#{variable}.#{@progname}") then + expansion("#{variable}.#{@progname}") + elsif expansion?(variable) then + expansion(variable) + else + '' + end + end + + def original_path(filename='') + _expanded_path_(original_variable(var_of_format_or_suffix(filename)).split(";")) + end + + def expanded_path(filename='') + _expanded_path_(expanded_variable(var_of_format_or_suffix(filename)).split(";")) + end + + def _expanded_path_(pathlist) + i, n = 0, 0 + pathlist.collect! do |mainpath| + mainpath.gsub(/([\{\}])/o) do + if $1 == "{" then + i += 1 ; n = i if i > n ; "<#{i}>" + else + i -= 1 ; "</#{i+1}>" + end + end + end + n.times do |i| + loop do + more = false + newlist = [] + pathlist.each do |path| + unless path.sub!(/^(.*?)<(#{n-i})>(.*?)<\/\2>(.*?)$/) do + pre, mid, post = $1, $3, $4 + mid.gsub!(/\,$/,',.') + mid.split(',').each do |m| + more = true + if m == '.' then + newlist << "#{pre}#{post}" + else + newlist << "#{pre}#{m}#{post}" + end + end + end then + newlist << path + end + end + if more then + pathlist = [newlist].flatten # copy -) + else + break + end + end + end + pathlist = pathlist.uniq.collect do |path| + p = path + # p.gsub(/^\/+/o) do '' end + # p.gsub!(/(.)\/\/(.)/o) do "#{$1}/#{$2}" end + # p.gsub!(/\/\/+$/o) do '//' end + p.gsub!(/\/\/+/o) do '//' end + p + end + pathlist + end + + # todo: ignore case + + def var_of_format(str) + @@formats[str] || '' + end + + def var_of_suffix(str) # includes . + if @@suffixmap.key?(str) then @@formats[@@suffixmap[str]] else '' end + end + + def var_of_format_or_suffix(str) + if @@formats.key?(str) then + @@formats[str] + elsif @@suffixmap.key?(File.extname(str)) then # extname includes . + @@formats[@@suffixmap[File.extname(str)]] # extname includes . + else + '' + end + end + +end + +class KpseFast + + # test things + + def list_variables(kpseonly=true) + @variables.keys.sort.each do |k| + if kpseonly then + puts("#{k} = #{@variables[k]}") if @kpsevars[k] + else + puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@variables[k]}") + end + end + end + + def list_expansions(kpseonly=true) + @expansions.keys.sort.each do |k| + if kpseonly then + puts("#{k} = #{@expansions[k]}") if @kpsevars[k] + else + puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@expansions[k]}") + end + end + end + + def list_lsr + puts("files = #{@files.size}") + end + + def set_test_patterns + @variables["KPSE_TEST_PATTERN_A"] = "foo/{1,2}/bar//" + @variables["KPSE_TEST_PATTERN_B"] = "!!x{A,B{1,2}}y" + @variables["KPSE_TEST_PATTERN_C"] = "x{A,B//{1,2}}y" + @variables["KPSE_TEST_PATTERN_D"] = "x{A,B//{1,2,}}//y" + end + + def show_test_patterns + ['A','B','D'].each do |i| + puts "" + puts @variables ["KPSE_TEST_PATTERN_#{i}"] + puts "" + puts expand_path("KPSE_TEST_PATTERN_#{i}").split_path + puts "" + end + end + +end + +class KpseFast + + # kpse stuff + + def expand_braces(str) # output variable and brace expansion of STRING. + _expanded_path_(original_variable(str).split_path).join_path + end + + def expand_path(str) # output complete path expansion of STRING. + _expanded_path_(expanded_variable(str).split_path).join_path + end + + def expand_var(str) # output variable expansion of STRING. + expanded_variable(str) + end + + def show_path(str) # output search path for file type NAME + expanded_path(str).join_path + end + + def var_value(str) # output the value of variable $STRING. + original_variable(str) + end + +end + +class KpseFast + + def _is_cnf_?(filename) + filename == File.basename((@cnffiles.first rescue @@texmfcnf) || @@texmfcnf) + end + + def find_file(filename) + if _is_cnf_?(filename) then + @cnffiles.first rescue '' + else + [find_files(filename,true)].flatten.first || '' + end + end + + def find_files(filename,first=false) + if _is_cnf_?(filename) then + result = @cnffiles.dup + else + if @remember then + # stamp = "#{filename}--#{@format}--#{@engine}--#{@progname}" + stamp = "#{filename}--#{@engine}--#{@progname}" + return @found[stamp] if @found.key?(stamp) + end + pathlist = expanded_path(filename) + result = [] + filelist = if @files.key?(filename) then @files[filename].uniq else nil end + done = false + if pathlist.size == 0 then + if FileTest.file?(filename) then + done = true + result << '.' + end + else + pathlist.each do |path| + doscan = if path =~ /^\!\!/o then false else true end + recurse = if path =~ /\/\/$/o then true else false end + pathname = path.dup + pathname.gsub!(/^\!+/o, '') + done = false + if not done and filelist then + # checking for exact match + if filelist.include?(pathname) then + result << pathname + done = true + end + if not done and recurse then + # checking for fuzzy // + pathname.gsub!(/\/+$/o, '/.*') + # pathname.gsub!(/\/\//o,'/[\/]*/') + pathname.gsub!(/\/\//o,'/.*?/') + re = /^#{pathname}/ + filelist.each do |f| + if re =~ f then + result << f # duplicates will be filtered later + done = true + end + break if done + end + end + end + if not done and doscan then + # checking for path itself + pname = pathname.sub(/\.\*$/,'') + if not pname =~ /\*/o and FileTest.file?(File.join(pname,filename)) then + result << pname + done = true + end + end + break if done and first + end + end + if not done and @scandisk then + pathlist.each do |path| + pathname = path.dup + unless pathname.gsub!(/^\!+/o, '') then # !! prevents scan + recurse = pathname.gsub!(/\/+$/o, '') + complex = pathname.gsub!(/\/\//o,'/*/') + if recurse then + if complex then + if ok = File.glob_file("#{pathname}/**/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif ok = File.locate_file(pathname,filename) then + result << File.dirname(ok) + done = true + end + elsif complex then + if ok = File.glob_file("#{pathname}/#{filename}") then + result << File.dirname(ok) + done = true + end + elsif FileTest.file?(File.join(pathname,filename)) then + result << pathname + done = true + end + break if done and first + end + end + end + result = result.uniq.collect do |pathname| + File.join(pathname,filename) + end + @found[stamp] = result if @remember + end + return result # redundant + end + +end + +class KpseFast + + class FileData + attr_accessor :tag, :name, :size, :date + def initialize(tag=0,name=nil,size=nil,date=nil) + @tag, @name, @size, @date = tag, name, size, date + end + def FileData.sizes(a) + a.collect do |aa| + aa.size + end + end + def report + case @tag + when 1 then "deleted | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 2 then "present | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}" + when 3 then "obsolete | #{' '*8} | #{' '*16} | #{@name}" + end + end + end + + def analyze_files(filter='',strict=false,sort='',delete=false) + puts("command line = #{ARGV.join(' ')}") + puts("number of files = #{@files.size}") + puts("filter pattern = #{filter}") + puts("loaded cnf files = #{@cnffiles.join(' ')}") + puts('') + if filter.gsub!(/^not:/,'') then + def the_same(filter,filename) + not filter or filter.empty? or /#{filter}/ !~ filename + end + else + def the_same(filter,filename) + not filter or filter.empty? or /#{filter}/ =~ filename + end + end + @files.keys.each do |name| + if @files[name].size > 1 then + data = Array.new + @files[name].each do |path| + filename = File.join(path,name) + # if not filter or filter.empty? or /#{filter}/ =~ filename then + if the_same(filter,filename) then + if FileTest.file?(filename) then + if delete then + data << FileData.new(1,filename,File.size(filename),File.mtime(filename)) + begin + File.delete(filename) if delete + rescue + end + else + data << FileData.new(2,filename,File.size(filename),File.mtime(filename)) + end + else + # data << FileData.new(3,filename) + end + end + end + if data.length > 1 then + if strict then + # if data.collect do |d| d.size end.uniq! then + # data.sort! do |a,b| b.size <=> a.size end + # data.each do |d| puts d.report end + # puts '' + # end + data.sort! do |a,b| + if a.size and b.size then + b.size <=> a.size + else + 0 + end + end + bunch = Array.new + done = false + data.each do |d| + if bunch.size == 0 then + bunch << d + elsif bunch[0].size == d.size then + bunch << d + else + if bunch.size > 1 then + bunch.each do |b| + puts b.report + end + done = true + end + bunch = [d] + end + end + puts '' if done + else + case sort + when 'size' then data.sort! do |a,b| a.size <=> b.size end + when 'revsize' then data.sort! do |a,b| b.size <=> a.size end + when 'date' then data.sort! do |a,b| a.date <=> b.date end + when 'revdate' then data.sort! do |a,b| b.date <=> a.date end + end + data.each do |d| puts d.report end + puts '' + end + end + end + end + end + +end + + + # k = KpseFast.new # (root) + # k.set_test_patterns + # k.load_cnf + # k.expand_variables + # k.load_lsr + + # k.show_test_patterns + + # puts k.list_variables + # puts k.list_expansions + # k.list_lsr + # puts k.expansion("$TEXMF") + # puts k.expanded_path("TEXINPUTS","context") + + # k.progname, k.engine, k.format = 'context', 'pdfetex', 'tfm' + # k.scandisk = false # == must_exist + # k.expand_variables + + # 10.times do |i| puts k.find_file('texnansi-lmr10.tfm') end + + # puts "expand braces $TEXMF" + # puts k.expand_braces("$TEXMF") + # puts "expand path $TEXMF" + # puts k.expand_path("$TEXMF") + # puts "expand var $TEXMF" + # puts k.expand_var("$TEXMF") + # puts "expand path $TEXMF" + # puts k.show_path('tfm') + # puts "expand value $TEXINPUTS" + # puts k.var_value("$TEXINPUTS") + # puts "expand value $TEXINPUTS.context" + # puts k.var_value("$TEXINPUTS.context") + + # exit + + + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/kpse/drb.rb' + +require 'drb' +# kpse_merge_done: require 'base/kpse/trees' + +class KpseServer + + attr_accessor :port + + def initialize(port=7000) + @port = port + end + + def start + puts "starting drb service at port #{@port}" + DRb.start_service("druby://localhost:#{@port}", KpseTrees.new) + trap(:INT) do + DRb.stop_service + end + DRb.thread.join + end + + def stop + # todo + end + +end + +class KpseClient + + attr_accessor :port + + def initialize(port=7000) + @port = port + @kpse = nil + end + + def start + # only needed when callbacks are used / slow, due to Socket::getaddrinfo + # DRb.start_service + end + + def object + @kpse = DRbObject.new(nil,"druby://localhost:#{@port}") + end + +end + + +# SERVER_URI="druby://localhost:8787" +# +# # Start a local DRbServer to handle callbacks. +# # +# # Not necessary for this small example, but will be required +# # as soon as we pass a non-marshallable object as an argument +# # to a dRuby call. +# DRb.start_service +# + + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/kpse/trees.rb' + +require 'monitor' +# kpse_merge_done: require 'base/kpsefast' + +class KpseTrees < Monitor + + def initialize + @trees = Hash.new + end + + def pattern(filenames) + filenames.join('|').gsub(/\\+/o,'/').downcase + end + + def choose(filenames,environment) + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "enabling tree #{current}" + current + end + + def fetch(filenames,environment) # will send whole object ! + current = pattern(filenames) + load(filenames,environment) unless @trees[current] + puts "fetching tree #{current}" + @trees[current] + end + + def load(filenames,environment) + current = pattern(filenames) + puts "loading tree #{current}" + @trees[current] = KpseFast.new + @trees[current].push_environment(environment) + @trees[current].load_cnf(filenames) + @trees[current].expand_variables + @trees[current].load_lsr + end + + def set(tree,key,value) + case key + when 'progname' then @trees[tree].progname = value + when 'engine' then @trees[tree].engine = value + when 'format' then @trees[tree].format = value + end + end + def get(tree,key) + case key + when 'progname' then @trees[tree].progname + when 'engine' then @trees[tree].engine + when 'format' then @trees[tree].format + end + end + + def load_cnf(tree) + @trees[tree].load_cnf + end + def load_lsr(tree) + @trees[tree].load_lsr + end + def expand_variables(tree) + @trees[tree].expand_variables + end + def expand_braces(tree,str) + @trees[tree].expand_braces(str) + end + def expand_path(tree,str) + @trees[tree].expand_path(str) + end + def expand_var(tree,str) + @trees[tree].expand_var(str) + end + def show_path(tree,str) + @trees[tree].show_path(str) + end + def var_value(tree,str) + @trees[tree].var_value(str) + end + def find_file(tree,filename) + @trees[tree].find_file(filename) + end + def find_files(tree,filename,first) + @trees[tree].find_files(filename,first) + end + +end + + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/kpsedirect.rb' + +class KpseDirect + + attr_accessor :progname, :format, :engine + + def initialize + @progname, @format, @engine = '', '', '' + end + + def expand_path(str) + clean_name(`kpsewhich -expand-path=#{str}`.chomp) + end + + def expand_var(str) + clean_name(`kpsewhich -expand-var=#{str}`.chomp) + end + + def find_file(str) + clean_name(`kpsewhich #{_progname_} #{_format_} #{str}`.chomp) + end + + def _progname_ + if @progname.empty? then '' else "-progname=#{@progname}" end + end + def _format_ + if @format.empty? then '' else "-format=\"#{@format}\"" end + end + + private + + def clean_name(str) + str.gsub(/\\/,'/') + end + +end + + +# kpse_merge_file: 'C:/data/develop/context/ruby/base/merge.rb' + +# module : base/merge +# copyright : PRAGMA Advanced Document Engineering +# version : 2006 +# author : Hans Hagen +# +# project : ConTeXt / eXaMpLe +# concept : Hans Hagen +# info : j.hagen@xs4all.nl + + +# this module will package all the used modules in the file itself +# so that we can relocate the file at wish, usage: +# +# merge: +# +# unless SelfMerge::ok? && SelfMerge::merge then +# puts("merging should happen on the path were the base inserts reside") +# end +# +# cleanup: +# +# unless SelfMerge::cleanup then +# puts("merging should happen on the path were the base inserts reside") + +module SelfMerge + + @@kpsemergestart = "\# kpse_merge_start" + @@kpsemergestop = "\# kpse_merge_stop" + @@kpsemergefile = "\# kpse_merge_file: " + @@kpsemergedone = "\# kpse_merge_done: " + + @@filename = File.basename($0) + @@ownpath = File.expand_path(File.dirname($0)) + @@modroot = '(base|graphics|rslb|www)' # needed in regex in order not to mess up SelfMerge + @@modules = $".collect do |file| File.expand_path(file) end + + @@modules.delete_if do |file| + file !~ /^#{@@ownpath}\/#{@@modroot}.*$/ + end + + def SelfMerge::ok? + begin + @@modules.each do |file| + return false unless FileTest.file?(file) + end + rescue + return false + else + return true + end + end + + def SelfMerge::merge + begin + if SelfMerge::ok? && rbfile = IO.read(@@filename) then + begin + inserts = "#{@@kpsemergestart}\n\n" + @@modules.each do |file| + inserts << "#{@@kpsemergefile}'#{file}'\n\n" + inserts << IO.read(file).gsub(/^#.*?\n$/,'') + inserts << "\n\n" + end + inserts << "#{@@kpsemergestop}\n\n" + # no gsub! else we end up in SelfMerge + rbfile.sub!(/#{@@kpsemergestart}\s*#{@@kpsemergestop}/mois) do + inserts + end + rbfile.gsub!(/^(.*)(require [\"\'].*?#{@@modroot}.*)$/) do + pre, post = $1, $2 + if pre =~ /#{@@kpsemergedone}/ then + "#{pre}#{post}" + else + "#{pre}#{@@kpsemergedone}#{post}" + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::cleanup + begin + if rbfile = IO.read(@@filename) then + begin + rbfile.sub!(/#{@@kpsemergestart}(.*)#{@@kpsemergestop}\s*/mois) do + "#{@@kpsemergestart}\n\n#{@@kpsemergestop}\n\n" + end + rbfile.gsub!(/^(.*#{@@kpsemergedone}.*)$/) do + str = $1 + if str =~ /require [\"\']/ then + str.gsub(/#{@@kpsemergedone}/, '') + else + str + end + end + rescue + return false + else + begin + File.open(@@filename,'w') do |f| + f << rbfile + end + rescue + return false + end + end + end + rescue + return false + else + return true + end + end + + def SelfMerge::replace + if SelfMerge::ok? then + SelfMerge::cleanup + SelfMerge::merge + end + end + +end + + +# kpse_merge_stop + + $mswindows = Config::CONFIG['host_os'] =~ /mswin/ $separator = File::PATH_SEPARATOR -$version = "1.6.2" +$version = "2.0.0" if $mswindows then - require "win32ole" require "Win32API" - end -exit if defined?(REQUIRE2LIB) +# exit if defined?(REQUIRE2LIB) $stdout.sync = true $stderr.sync = true @@ -61,16 +1452,19 @@ $suffixinputs['lua'] = 'LUAINPUTS' $suffixinputs['jar'] = 'JAVAINPUTS' $suffixinputs['pdf'] = 'PDFINPUTS' -$predefined['texexec'] = 'texexec.pl' -$predefined['texutil'] = 'texutil.pl' +$predefined['texexec'] = 'texexec.rb' +$predefined['texutil'] = 'texutil.rb' $predefined['texfont'] = 'texfont.pl' +$predefined['texshow'] = 'texshow.pl' +$predefined['makempy'] = 'makempy.pl' $predefined['mptopdf'] = 'mptopdf.pl' $predefined['pstopdf'] = 'pstopdf.rb' $predefined['examplex'] = 'examplex.rb' $predefined['concheck'] = 'concheck.rb' +$predefined['runtools'] = 'runtools.rb' $predefined['textools'] = 'textools.rb' $predefined['tmftools'] = 'tmftools.rb' $predefined['ctxtools'] = 'ctxtools.rb' @@ -80,16 +1474,42 @@ $predefined['mpstools'] = 'mpstools.rb' $predefined['exatools'] = 'exatools.rb' $predefined['xmltools'] = 'xmltools.rb' - -if ENV['TEXMFSTART_MODE'] = 'experimental' then - $predefined['texexec'] = 'newtexexec.rb' - $predefined['pstopdf'] = 'newpstopdf.rb' -end +$predefined['newpstopdf'] = 'pstopdf.rb' +$predefined['newtexexec'] = 'texexec.rb' +$predefined['pdftrimwhite'] = 'pdftrimwhite.pl' + +$makelist = [ + # context + 'texexec', + 'texutil', + 'texfont', + # mp/ps + 'pstopdf', + 'mptopdf', + 'makempy', + # misc + 'ctxtools', + 'pdftools', + 'xmltools', + 'textools', + 'mpstools', + 'tmftools', + 'exatools', + 'runtools', + 'rlxtools', + # + # no, 'texmfstart' +] + +# if ENV['TEXMFSTART_MODE'] = 'experimental' then + # $predefined['texexec'] = 'newtexexec.rb' + # $predefined['pstopdf'] = 'newpstopdf.rb' +# end $scriptlist = 'rb|pl|py|lua|jar' $documentlist = 'pdf|ps|eps|htm|html' -$editor = ENV['EDITOR'] || ENV['editor'] || 'scite' +$editor = ENV['TEXMFSTART_EDITOR'] || ENV['EDITOR'] || ENV['editor'] || 'scite' $crossover = true # to other tex tools, else only local @@ -113,6 +1533,31 @@ end $applications['htm'] = $applications['html'] $applications['eps'] = $applications['ps'] +$kpse = nil + +def check_kpse + if $kpse then + # already done + else + begin + if KpseRemote::available? then + $kpse = KpseRemote.new + if $kpse.okay? then + puts("kpse : remote") if $verbose + else + $kpse = KpseDirect.new + puts("kpse : direct (forced)") if $verbose + end + else + $kpse = KpseDirect.new + puts("kpse : direct") if $verbose + end + rescue + puts("kpse : direct (fallback)") if $verbose + end + end +end + if $mswindows then GetShortPathName = Win32API.new('kernel32', 'GetShortPathName', ['P','P','N'], 'N') @@ -170,30 +1615,43 @@ end class File + # def File.needsupdate(oldname,newname) + # begin + # if $mswindows then + # return File.stat(oldname).mtime > File.stat(newname).mtime + # else + # return File.stat(oldname).mtime != File.stat(newname).mtime + # end + # rescue + # return true + # end + # end + + @@update_eps = 1 + def File.needsupdate(oldname,newname) begin - if $mswindows then - return File.stat(oldname).mtime > File.stat(newname).mtime + oldtime = File.stat(oldname).mtime.to_i + newtime = File.stat(newname).mtime.to_i + if newtime >= oldtime then + return false + elsif oldtime-newtime < @@update_eps then + return false else - return File.stat(oldname).mtime != File.stat(newname).mtime + return true end rescue return true end end - def File.timestamp(name) - begin - "#{File.stat(name).mtime}" - rescue - return 'unknown' - end - end - def File.syncmtimes(oldname,newname) + return begin if $mswindows then - # does not work (yet) + # does not work (yet) / gives future timestamp + # t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here + # File.utime(0,t,oldname,newname) else t = File.mtime(oldname) # i'm not sure if the time is frozen, so we do it here File.utime(0,t,oldname,newname) @@ -202,6 +1660,14 @@ class File end end + def File.timestamp(name) + begin + "#{File.stat(name).mtime}" + rescue + return 'unknown' + end + end + end def hashed (arr=[]) @@ -242,6 +1708,10 @@ def launch(filename) end end +# env|environment +# rel|relative +# loc|locate|kpse|path|file + def expanded(arg) # no "other text files", too restricted arg.gsub(/(env|environment)\:([a-zA-Z\-\_\.0-9]+)/o) do method, original, resolved = $1, $2, '' @@ -252,12 +1722,27 @@ def expanded(arg) # no "other text files", too restricted report("environment variable #{original} cannot be resolved") unless $report original end - end . gsub(/(kpse|loc|file|path)\:([a-zA-Z\-\_\.0-9]+)/o) do # was: \S + end . gsub(/(rel|relative)\:([a-zA-Z\-\_\.0-9]+)/o) do + method, original, resolved = $1, $2, '' + ['.','..','../..'].each do |r| + if FileTest.file?(File.join(r,original)) then + resolved = File.join(r,original) + break + end + end + if resolved.empty? then + original + else + resolved + end + end . gsub(/(kpse|loc|locate|file|path)\:([a-zA-Z\-\_\.0-9]+)/o) do method, original, resolved = $1, $2, '' if $program && ! $program.empty? then - pstrings = ["-progname=#{$program}"] + # pstrings = ["-progname=#{$program}"] + pstrings = [$program] else - pstrings = ['','progname=context'] + # pstrings = ['','-progname=context'] + pstrings = ['','context'] end # auto suffix with texinputs as fall back if ENV["_CTX_K_V_#{original}_"] then @@ -265,22 +1750,31 @@ def expanded(arg) # no "other text files", too restricted report("environment provides #{original} as #{resolved}") unless $report resolved else + check_kpse pstrings.each do |pstr| if resolved.empty? then - command = "kpsewhich #{pstr} #{original}" - report("running #{command}") + # command = "kpsewhich #{pstr} #{original}" + # report("running #{command}") + report("locating '#{original}' in program space '#{pstr}'") begin - resolved = `#{command}`.chomp + # resolved = `#{command}`.chomp + $kpse.progname = pstr + $kpse.format = '' + resolved = $kpse.find_file(original).gsub(/\\/,'/') rescue resolved = '' end end # elsewhere in the tree if resolved.empty? then - command = "kpsewhich #{pstr} -format=\"other text files\" #{original}" - report("running #{command}") + # command = "kpsewhich #{pstr} -format=\"other text files\" #{original}" + # report("running #{command}") + report("locating '#{original}' in program space '#{pstr}' using format 'other text files'") begin - resolved = `#{command}`.chomp + # resolved = `#{command}`.chomp + $kpse.progname = pstr + $kpse.format = 'other text files' + resolved = $kpse.find_file(original).gsub(/\\/,'/') rescue resolved = '' end @@ -339,8 +1833,11 @@ def runoneof(application,fullname,browserpermitted) else report("starting #{$filename}") unless $report output("\n") if $report && $verbose - applications = $applications[application] - if applications.class == Array then + applications = $applications[application.downcase] + if ! applications then + output("problems with determining application type") + return true + elsif applications.class == Array then if $report then output([fullname,expanded($arguments)].join(' ')) return true @@ -369,21 +1866,22 @@ def runoneof(application,fullname,browserpermitted) end def report(str) - $stderr.puts(str) if $verbose + $stdout.puts(str) if $verbose end def output(str) - $stderr.puts(str) + $stdout.puts(str) end def usage - print "version : #{$version} - 2003/2005 - www.pragma-ade.com\n" + print "version : #{$version} - 2003/2006 - www.pragma-ade.com\n" print("\n") print("usage : texmfstart [switches] filename [optional arguments]\n") print("\n") - print("switches : --verbose --report --browser --direct --execute --locate --iftouched\n") + print("switches : --verbose --report --browser --direct --execute --locate --iftouched --ifchanged\n") print(" --program --file --page --arguments --batch --edit --report --clear\n") print(" --make --lmake --wmake --path --stubpath --indirect --before --after\n") + print(" --tree --autotree --environment --showenv\n") print("\n") print("example : texmfstart pstopdf.rb cow.eps\n") print(" texmfstart --locate examplex.rb\n") @@ -391,13 +1889,22 @@ def usage print(" texmfstart --browser examplap.pdf\n") print(" texmfstart showcase.pdf\n") print(" texmfstart --page=2 --file=showcase.pdf\n") - print(" texmfstart --program=yourtex yourscript.pl arg-1 arg-2\n") + print(" texmfstart --program=yourtex yourscript.rb arg-1 arg-2\n") print(" texmfstart --direct xsltproc kpse:somefile.xsl somefile.xml\n") + print(" texmfstart --direct ruby rel:wn-cleanup-1.rb oldfile.xml newfile.xml\n") print(" texmfstart bin:xsltproc env:somepreset path:somefile.xsl somefile.xml\n") print(" texmfstart --iftouched=normal,lowres downsample.rb normal lowres\n") - print(" texmfstart texmfstart bin:scite kpse:texmf.cnf\n") - print(" texmfstart texmfstart --exec bin:scite *.tex\n") - print(" texmfstart texmfstart --edit texmf.cnf\n") + print(" texmfstart --ifchanged=somefile.dat --direct processit somefile.dat\n") + print(" texmfstart bin:scite kpse:texmf.cnf\n") + print(" texmfstart --exec bin:scite *.tex\n") + print(" texmfstart --edit texmf.cnf\n") + print(" texmfstart --edit kpse:texmf.cnf\n") + print(" texmfstart --serve\n") + print("\n") + print(" texmfstart --stubpath=/usr/local/bin [--make --remove] --verbose all\n") + print(" texmfstart --stubpath=auto [--make --remove] all\n") + print("\n") + check_kpse end # somehow registration does not work out (at least not under windows) @@ -411,7 +1918,7 @@ def registered?(filename) end def registered(filename) - return ENV[tag(filename)] + return ENV[tag(filename)] || 'unknown' end def register(filename,fullname) @@ -425,204 +1932,220 @@ def register(filename,fullname) end def find(filename,program) - filename = filename.sub(/script:/o, '') # so we have bin: and script: and nothing - if $predefined.key?(filename) then - report("expanding '#{filename}' to '#{$predefined[filename]}'") - filename = $predefined[filename] - end - if registered?(filename) then - report("already located '#{filename}'") - return registered(filename) - end - # create suffix list - if filename =~ /^(.*)\.(.+)$/ then - filename = $1 - suffixlist = [$2] - else - suffixlist = [$scriptlist.split('|'),$documentlist.split('|')].flatten - end - # first we honor a given path - if filename =~ /[\\\/]/ then - report("trying to honor '#{filename}'") - suffixlist.each do |suffix| - fullname = filename+'.'+suffix - if FileTest.file?(fullname) && register(filename,fullname) - return shortpathname(fullname) - end + begin + filename = filename.sub(/script:/o, '') # so we have bin: and script: and nothing + if $predefined.key?(filename) then + report("expanding '#{filename}' to '#{$predefined[filename]}'") + filename = $predefined[filename] end - end - filename.sub!(/^.*[\\\/]/, '') - # next we look at the current path and the callerpath - [['.','current'],[$ownpath,'caller'],[registered("THREAD"),'thread']].each do |p| - if p && ! p.empty? then + if registered?(filename) then + report("already located '#{filename}'") + return registered(filename) + end + # create suffix list + if filename =~ /^(.*)\.(.+)$/ then + filename = $1 + suffixlist = [$2] + else + suffixlist = [$scriptlist.split('|'),$documentlist.split('|')].flatten + end + # first we honor a given path + if filename =~ /[\\\/]/ then + report("trying to honor '#{filename}'") suffixlist.each do |suffix| - fname = "#{filename}.#{suffix}" - fullname = File.expand_path(File.join(p[0],fname)) - report("locating '#{fname}' in #{p[1]} path '#{p[0]}'") - if FileTest.file?(fullname) && register(filename,fullname) then - report("'#{fname}' located in #{p[1]} path") + fullname = filename+'.'+suffix + if FileTest.file?(fullname) && register(filename,fullname) return shortpathname(fullname) end end end - end - # now we consult environment settings - fullname = nil - suffixlist.each do |suffix| - begin - break unless $suffixinputs[suffix] - environment = ENV[$suffixinputs[suffix]] || ENV[$suffixinputs[suffix]+".#{$program}"] - if ! environment || environment.empty? then - begin - environment = `kpsewhich -expand-path=\$#{$suffixinputs[suffix]}`.chomp - rescue - environment = nil - else - if environment && ! environment.empty? then - report("using kpsewhich variable #{$suffixinputs[suffix]}") + filename.sub!(/^.*[\\\/]/, '') + # next we look at the current path and the callerpath + [['.','current'],[$ownpath,'caller'],[registered("THREAD"),'thread']].each do |p| + if p && ! p.empty? && ! (p[0] == 'unknown') then + suffixlist.each do |suffix| + fname = "#{filename}.#{suffix}" + fullname = File.expand_path(File.join(p[0],fname)) + report("locating '#{fname}' in #{p[1]} path '#{p[0]}'") + if FileTest.file?(fullname) && register(filename,fullname) then + report("'#{fname}' located in #{p[1]} path") + return shortpathname(fullname) end end - elsif environment && ! environment.empty? then - report("using environment variable #{$suffixinputs[suffix]}") - end - if environment && ! environment.empty? then - environment.split($separator).each do |e| - e.strip! - e = '.' if e == '\.' # somehow . gets escaped - e += '/' unless e =~ /[\\\/]$/ - fullname = e + filename + '.' + suffix - report("testing '#{fullname}'") - if FileTest.file?(fullname) then - break + end + end + # now we consult environment settings + fullname = nil + check_kpse + $kpse.progname = program + suffixlist.each do |suffix| + begin + break unless $suffixinputs[suffix] + environment = ENV[$suffixinputs[suffix]] || ENV[$suffixinputs[suffix]+".#{$program}"] + if ! environment || environment.empty? then + begin + # environment = `kpsewhich -expand-path=\$#{$suffixinputs[suffix]}`.chomp + environment = $kpse.expand_path("\$#{$suffixinputs[suffix]}") + rescue + environment = nil else - fullname = nil + if environment && ! environment.empty? then + report("using kpsewhich variable #{$suffixinputs[suffix]}") + end end + elsif environment && ! environment.empty? then + report("using environment variable #{$suffixinputs[suffix]}") end + if environment && ! environment.empty? then + environment.split($separator).each do |e| + e.strip! + e = '.' if e == '\.' # somehow . gets escaped + e += '/' unless e =~ /[\\\/]$/ + fullname = e + filename + '.' + suffix + report("testing '#{fullname}'") + if FileTest.file?(fullname) then + break + else + fullname = nil + end + end + end + rescue + report("environment string '#{$suffixinputs[suffix]}' cannot be used to locate '#{filename}'") + fullname = nil + else + return shortpathname(fullname) if register(filename,fullname) end - rescue - report("environment string '#{$suffixinputs[suffix]}' cannot be used to locate '#{filename}'") - fullname = nil - else - return shortpathname(fullname) if register(filename,fullname) end - end - return shortpathname(fullname) if register(filename,fullname) - # then we fall back on kpsewhich - suffixlist.each do |suffix| - # TDS script scripts location as per 2004 - if suffix =~ /(#{$scriptlist})/ then + return shortpathname(fullname) if register(filename,fullname) + # then we fall back on kpsewhich + suffixlist.each do |suffix| + # TDS script scripts location as per 2004 + if suffix =~ /(#{$scriptlist})/ then + begin + report("using 'kpsewhich' to locate '#{filename}' in suffix space '#{suffix}' (1)") + # fullname = `kpsewhich -progname=#{program} -format=texmfscripts #{filename}.#{suffix}`.chomp + $kpse.format = 'texmfscripts' + fullname = $kpse.find_file("#{filename}.#{suffix}").gsub(/\\/,'/') + rescue + report("kpsewhich cannot locate '#{filename}' in suffix space '#{suffix}' (1)") + fullname = nil + else + return shortpathname(fullname) if register(filename,fullname) + end + end + # old TDS location: .../texmf/context/... begin - report("using 'kpsewhich' to locate '#{filename}' in suffix space '#{suffix}' (1)") - fullname = `kpsewhich -progname=#{program} -format=texmfscripts #{filename}.#{suffix}`.chomp + report("using 'kpsewhich' to locate '#{filename}' in suffix space '#{suffix}' (2)") + # fullname = `kpsewhich -progname=#{program} -format="other text files" #{filename}.#{suffix}`.chomp + $kpse.format = 'other text files' + fullname = $kpse.find_file("#{filename}.#{suffix}").gsub(/\\/,'/') rescue - report("kpsewhich cannot locate '#{filename}' in suffix space '#{suffix}' (1)") + report("kpsewhich cannot locate '#{filename}' in suffix space '#{suffix}' (2)") fullname = nil else return shortpathname(fullname) if register(filename,fullname) end end - # old TDS location: .../texmf/context/... - begin - report("using 'kpsewhich' to locate '#{filename}' in suffix space '#{suffix}' (2)") - fullname = `kpsewhich -progname=#{program} -format="other text files" #{filename}.#{suffix}`.chomp - rescue - report("kpsewhich cannot locate '#{filename}' in suffix space '#{suffix}' (2)") - fullname = nil - else - return shortpathname(fullname) if register(filename,fullname) - end - end - return shortpathname(fullname) if register(filename,fullname) - # let's take a look at the path - paths = ENV['PATH'].split($separator) - suffixlist.each do |s| - paths.each do |p| - report("checking #{p} for suffix #{s}") - if FileTest.file?(File.join(p,"#{filename}.#{s}")) then - fullname = File.join(p,"#{filename}.#{s}") - return shortpathname(fullname) if register(filename,fullname) + return shortpathname(fullname) if register(filename,fullname) + # let's take a look at the path + paths = ENV['PATH'].split($separator) + suffixlist.each do |s| + paths.each do |p| + suffixedname = "#{filename}.#{s}" + report("checking #{p} for #{filename}") + if FileTest.file?(File.join(p,suffixedname)) then + fullname = File.join(p,suffixedname) + return shortpathname(fullname) if register(filename,fullname) + end end end - end - # bad luck, we need to search the tree ourselves - if (suffixlist.length == 1) && (suffixlist.first =~ /(#{$documentlist})/) then - report("aggressively locating '#{filename}' in document trees") - begin - texroot = `kpsewhich -expand-var=$SELFAUTOPARENT`.chomp - rescue - texroot = '' - else - texroot.sub!(/[\\\/][^\\\/]*?$/, '') - end - if not texroot.empty? then - sffxlst = suffixlist.join(',') + # bad luck, we need to search the tree ourselves + if (suffixlist.length == 1) && (suffixlist.first =~ /(#{$documentlist})/) then + report("aggressively locating '#{filename}' in document trees") begin - report("locating '#{filename}' in document tree '#{texroot}/doc*'") - if (result = Dir.glob("#{texroot}/doc*/**/#{filename}.{#{sffxlst}}")) && result && result[0] && FileTest.file?(result[0]) then - fullname = result[0] - end + # texroot = `kpsewhich -expand-var=$SELFAUTOPARENT`.chomp + texroot = $kpse.expand_var("$SELFAUTOPARENT") rescue - report("locating '#{filename}.#{suffix}' in tree '#{texroot}' aborted") + texroot = '' + else + texroot.sub!(/[\\\/][^\\\/]*?$/, '') + end + if not texroot.empty? then + sffxlst = suffixlist.join(',') + begin + report("locating '#{filename}' in document tree '#{texroot}/doc*'") + if (result = Dir.glob("#{texroot}/doc*/**/#{filename}.{#{sffxlst}}")) && result && result[0] && FileTest.file?(result[0]) then + fullname = result[0] + end + rescue + report("locating '#{filename}.#{suffixlist.join('|')}' in tree '#{texroot}' aborted") + end end + return shortpathname(fullname) if register(filename,fullname) end - return shortpathname(fullname) if register(filename,fullname) - end - report("aggressively locating '#{filename}' in tex trees") - begin - textrees = `kpsewhich -expand-var=$TEXMF`.chomp - rescue - textrees = '' - end - if not textrees.empty? then - textrees.gsub!(/[\{\}\!]/, '') - textrees = textrees.split(',') - if (suffixlist.length == 1) && (suffixlist.first =~ /(#{$documentlist})/) then - speedup = ['doc**','**'] - else - speedup = ['**'] + report("aggressively locating '#{filename}' in tex trees") + begin + # textrees = `kpsewhich -expand-var=$TEXMF`.chomp + textrees = $kpse.expand_var("$TEXMF") + rescue + textrees = '' end - sffxlst = suffixlist.join(',') - speedup.each do |speed| - textrees.each do |tt| - tt.gsub!(/[\\\/]$/, '') - if FileTest.directory?(tt) then - begin - report("locating '#{filename}' in tree '#{tt}/#{speed}/#{filename}.{#{sffxlst}}'") - if (result = Dir.glob("#{tt}/#{speed}/#{filename}.{#{sffxlst}}")) && result && result[0] && FileTest.file?(result[0]) then - fullname = result[0] - break + if not textrees.empty? then + textrees.gsub!(/[\{\}\!]/, '') + textrees = textrees.split(',') + if (suffixlist.length == 1) && (suffixlist.first =~ /(#{$documentlist})/) then + speedup = ['doc**','**'] + else + speedup = ['**'] + end + sffxlst = suffixlist.join(',') + speedup.each do |speed| + textrees.each do |tt| + tt.gsub!(/[\\\/]$/, '') + if FileTest.directory?(tt) then + begin + report("locating '#{filename}' in tree '#{tt}/#{speed}/#{filename}.{#{sffxlst}}'") + if (result = Dir.glob("#{tt}/#{speed}/#{filename}.{#{sffxlst}}")) && result && result[0] && FileTest.file?(result[0]) then + fullname = result[0] + break + end + rescue + report("locating '#{filename}' in tree '#{tt}' aborted") + next end - rescue - report("locating '#{filename}' in tree '#{tt}' aborted") - next end end + break if fullname && ! fullname.empty? end - break if fullname && ! fullname.empty? end - end - if register(filename,fullname) then - return shortpathname(fullname) - else - return '' + if register(filename,fullname) then + return shortpathname(fullname) + else + return '' + end + rescue + # error, trace = $!, $@.join("\n") + # report("fatal error: #{error}\n#{trace}") + report("fatal error") end end def run(fullname) if ! fullname || fullname.empty? then - report("the file '#{$filename}' is not found") + output("the file '#{$filename}' is not found") elsif FileTest.file?(fullname) then begin case fullname - when /\.(#{$scriptlist})$/ then + when /\.(#{$scriptlist})$/i then return runoneof($1,fullname,false) - when /\.(#{$documentlist})$/ then + when /\.(#{$documentlist})$/i then return runoneof($1,fullname,true) else return runoneof('unknown',fullname,false) end rescue - report("starting '#{$filename}' in program space '#{$program}' fails") + report("starting '#{$filename}' in program space '#{$program}' fails (#{$!})") end else report("the file '#{$filename}' in program space '#{$program}' is not accessible") @@ -646,13 +2169,33 @@ def edit(filename) end end -def make(filename,windows=false,linux=false) - basename = filename.dup - basename.sub!(/\.[^.]+?$/, '') - basename.sub!(/^.*[\\\/]/, '') - basename = $stubpath + '/' + basename unless $stubpath.empty? - if basename == filename then - report('nothing made') +def make(filename,windows=false,linux=false,remove=false) + basename = File.basename(filename).gsub(/\.[^.]+?$/, '') + if $stubpath == 'auto' then + basename = File.dirname($0) + '/' + basename + else + basename = $stubpath + '/' + basename unless $stubpath.empty? + end + 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 @@ -660,31 +2203,39 @@ def make(filename,windows=false,linux=false) end filename = "\"#{filename}\"" if filename =~ /\s/ program = 'texmfstart' if $indirect || ! program || program.empty? - begin + 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} #{filename} %*\015\012") + f.write("#{program} #{callname} %*\015\012") f.close - report("windows stub '#{basename}.bat' made") + 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} #{filename} $@\012") + f.write("#{program} #{callname} \"$@\"\012") f.close - report("unix stub '#{basename}' made") + 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) - if $iftouched then files = $directives['iftouched'].split(',') oldname, newname = files[0], files[1] @@ -697,52 +2248,145 @@ def process(&block) else report("file #{oldname} is untouched") end + elsif $ifchanged then + filename = $directives['ifchanged'] + checkname = filename + ".md5" + oldchecksum, newchecksum = "old", "new" + begin + newchecksum = MD5.new(IO.read(filename)).hexdigest.upcase + rescue + newchecksum = "new" + else + begin + oldchecksum = IO.read(checkname).chomp + rescue + oldchecksum = "old" + end + end + if $verbose then + report("old checksum #{filename}: #{oldchecksum}") + report("new checksum #{filename}: #{newchecksum}") + end + if oldchecksum != newchecksum then + report("file is changed, processing started") + begin + File.open(checkname,'w') do |f| + f << newchecksum + end + rescue + end + yield + else + report("file #{filename} is unchanged") + end else yield end +end +def checkenvironment(tree) + report('') + ENV['TMP'] = ENV['TMP'] || ENV['TEMP'] || ENV['TMPDIR'] || ENV['HOME'] + case RUBY_PLATFORM + when /(mswin|bccwin|mingw|cygwin)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-mswin' + when /(linux)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-linux' + when /(darwin|rhapsody|nextstep)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-macosx' + # when /(netbsd|unix)/i then # todo + else # todo + end + ENV['TEXOS'] = "#{ENV['TEXOS'].sub(/^[\\\/]*/, '').sub(/[\\\/]*$/, '')}" + ENV['TEXPATH'] = tree.sub(/\/+$/,'') # + '/' + ENV['TEXMFOS'] = "#{ENV['TEXPATH']}/#{ENV['TEXOS']}" + report('') + report("preset : TEXPATH => #{ENV['TEXPATH']}") + report("preset : TEXOS => #{ENV['TEXOS']}") + report("preset : TEXMFOS => #{ENV['TEXMFOS']}") + report("preset : TMP => #{ENV['TMP']}") + report('') end -def checktree(tree) - unless tree.empty? then - begin - setuptex = File.join(tree,'setuptex.tmf') - if FileTest.file?(setuptex) then - report('') - report("tex tree : #{setuptex}") - ENV['TEXPATH'] = tree.sub(/\/+$/,'') # + '/' - ENV['TMP'] = ENV['TMP'] || ENV['TEMP'] || ENV['TMPDIR'] || ENV['HOME'] - case RUBY_PLATFORM - when /(mswin|bccwin|mingw|cygwin)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-mswin' - when /(linux)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-linux' - when /(darwin|rhapsody|nextstep)/i then ENV['TEXOS'] = ENV['TEXOS'] || 'texmf-macosx' - # when /(netbsd|unix)/i then # todo - else # todo - end - ENV['TEXMFOS'] = "#{ENV['TEXPATH']}/#{ENV['TEXOS']}" - report('') - report("preset : TEXPATH => #{ENV['TEXPATH']}") - report("preset : TEXOS => #{ENV['TEXOS']}") - report("preset : TEXMFOS => #{ENV['TEXMFOS']}") - report("preset : TMP => #{ENV['TMP']}") - report('') - IO.readlines(File.join(tree,'setuptex.tmf')).each do |line| - case line - when /^[\#\%]/ then - # comment - when /^(.*?)\s+\=\s+(.*)\s*$/ then - k, v = $1, $2 - ENV[k] = v.gsub(/\%(.*?)\%/) do - ENV[$1] || '' - end - report("user set : #{k} => #{ENV[k]}") +def loadfile(filename) + begin + IO.readlines(filename).each do |line| + case line.chomp + when /^[\#\%]/ then + # comment + when /^(.*?)\s*(\>|\=|\<)\s*(.*)\s*$/ then + # = assign | > prepend | < append + key, how, value = $1, $2, $3 + begin + # $SAFE = 0 + value.gsub!(/\%(.*?)\%/) do + ENV[$1] || '' + end + # value.gsub!(/\;/,$separator) if key =~ /PATH/i then + case how + when '=', '<<' then ENV[key] = value + when '?', '??' then ENV[key] = ENV[key] || value + when '<', '+=' then ENV[key] = (ENV[key] || '') + $separator + value + when '>', '=+' then ENV[key] = value + $separator + (ENV[key] ||'') + end + rescue + report("user set failed : #{key} (#{$!})") + else + report("user set : #{key} => #{ENV[key]}") end - end + end + end + rescue + report("error in reading file '#{filename}'") + end +end + +def loadtree(tree) + begin + unless tree.empty? then + if File.directory?(tree) then + setuptex = File.join(tree,'setuptex.tmf') + else + setuptex = tree.dup + end + if FileTest.file?(setuptex) then + report("tex tree definition: #{setuptex}") + checkenvironment(File.dirname(setuptex)) + loadfile(setuptex) else report("no setup file '#{setuptex}'") end - rescue end + rescue + # maybe tree is empty or boolean (no arg given) + end +end + +def loadenvironment(environment) + begin + unless environment.empty? then + filename = if $path.empty? then environment else File.expand_path(File.join($path,environment)) end + if FileTest.file?(filename) then + report("environment : #{environment}") + loadfile(filename) + else + report("no environment file '#{environment}'") + end + end + rescue + report("problem while loading '#{environment}'") + end +end + +def show_environment + if $showenv then + keys = ENV.keys.sort + size = 0 + keys.each do |k| + size = k.size if k.size > size + end + report('') + keys.each do |k| + report("#{k.rjust(size)} => #{ENV[k]}") + end + report('') end end @@ -752,38 +2396,54 @@ def execute(arguments) $directives = hashed(arguments) - $help = $directives['help'] || false - $batch = $directives['batch'] || false - $filename = $directives['file'] || '' - $program = $directives['program'] || 'context' - $direct = $directives['direct'] || false - $edit = $directives['edit'] || false - $page = $directives['page'] || 0 - $browser = $directives['browser'] || false - $report = $directives['report'] || false - $verbose = $directives['verbose'] || (ENV['_CTX_VERBOSE_'] =~ /(y|yes|t|true|on)/io) || false - $arguments = $directives['arguments'] || '' - $execute = $directives['execute'] || $directives['exec'] || false - $locate = $directives['locate'] || false - - $path = $directives['path'] || '' - $tree = $directives['tree'] || '' + $help = $directives['help'] || false + $batch = $directives['batch'] || false + $filename = $directives['file'] || '' + $program = $directives['program'] || 'context' + $direct = $directives['direct'] || false + $edit = $directives['edit'] || false + $page = $directives['page'] || 0 + $browser = $directives['browser'] || false + $report = $directives['report'] || false + $verbose = $directives['verbose'] || false + $arguments = $directives['arguments'] || '' + $execute = $directives['execute'] || $directives['exec'] || false + $locate = $directives['locate'] || false + + $autotree = if $directives['autotree'] then (ENV['TEXMFSTART_TREE'] || ENV['TEXMFSTARTTREE'] || '') else '' end + + $path = $directives['path'] || '' + $tree = $directives['tree'] || $autotree || '' + $environment = $directives['environment'] || '' + + $make = $directives['make'] || false + $remove = $directives['remove'] || $directives['delete'] || false + $unix = $directives['unix'] || false + $windows = $directives['windows'] || $directives['mswin'] || false + $stubpath = $directives['stubpath'] || '' + $indirect = $directives['indirect'] || false + + $before = $directives['before'] || '' + $after = $directives['after'] || '' + + $iftouched = $directives['iftouched'] || false + $ifchanged = $directives['ifchanged'] || false + + $openoffice = $directives['oo'] || false + $crossover = false if $directives['clear'] - $make = $directives['make'] || false - $unix = $directives['unix'] || false - $windows = $directives['windows'] || false - $stubpath = $directives['stubpath'] || '' - $indirect = $directives['indirect'] || false + $showenv = $directives['showenv'] || false + $verbose = true if $showenv - $before = $directives['before'] || '' - $after = $directives['after'] || '' + $serve = $directives['serve'] || false - $iftouched = $directives['iftouched'] || false + $verbose = true if (ENV['_CTX_VERBOSE_'] =~ /(y|yes|t|true|on)/io) && ! $locate && ! $report - $openoffice = $directives['oo'] || false + # private: - $crossover = false if $directives['clear'] + $selfmerge = $directives['selfmerge'] || false + $selfcleanup = $directives['selfclean'] || $directives['selfcleanup'] || false ENV['_CTX_VERBOSE_'] = 'yes' if $verbose @@ -805,21 +2465,49 @@ def execute(arguments) end end - if $help || ! $filename || $filename.empty? then + if $selfmerge then + output("ruby libraries are cleaned up") if SelfMerge::cleanup + output("ruby libraries are merged") if SelfMerge::merge + elsif $selfcleanup then + output("ruby libraries are cleaned up") if SelfMerge::cleanup + elsif $serve then + if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then + # # kpse_merge_done: require 'base/kpseremote' + begin + KpseRemote::start_server + rescue + end + else + usage + puts("") + puts("message : set 'KPSEMETHOD' and 'KPSEPORT' variables") + end + elsif $help || ! $filename || $filename.empty? then usage - checktree($tree) + loadtree($tree) + loadenvironment($environment) + show_environment() elsif $batch && $filename && ! $filename.empty? then # todo, take commands from file and avoid multiple starts and checks else report("texmfstart version #{$version}") - checktree($tree) - if $make then - if $windows then - make($filename,true,false) - elsif $unix then - make($filename,false,true) + loadtree($tree) + loadenvironment($environment) + show_environment() + if $make || $remove then + if $filename == 'all' then + makelist = $makelist else - make($filename,$mswindows,!$mswindows) + makelist = [$filename] + end + makelist.each do |filename| + if $windows then + make(filename,true,false,$remove) + elsif $unix then + make(filename,false,true,$remove) + else + make(filename,$mswindows,!$mswindows,$remove) + end end elsif $browser && $filename =~ /^http\:\/\// then launch($filename) @@ -832,8 +2520,12 @@ def execute(arguments) edit($filename) else # script: or no prefix command = find(shortpathname($filename),$program) - register("THREAD",File.dirname(File.expand_path(command))) - run(command) + if command then + register("THREAD",File.dirname(File.expand_path(command))) + run(command) + else + report('unable to locate program') + end end end rescue @@ -845,3 +2537,5 @@ def execute(arguments) end execute(ARGV) + +exit (if ($?.to_i rescue 0) > 0 then 1 else 0 end) diff --git a/Master/texmf-dist/scripts/context/ruby/textools.rb b/Master/texmf-dist/scripts/context/ruby/textools.rb index 78982f175a1..99338834736 100644 --- a/Master/texmf-dist/scripts/context/ruby/textools.rb +++ b/Master/texmf-dist/scripts/context/ruby/textools.rb @@ -13,7 +13,7 @@ # This script will harbor some handy manipulations on tex # related files. -banner = ['TeXTools', 'version 1.2.2', '2002/2005', 'PRAGMA ADE/POD'] +banner = ['TeXTools', 'version 1.3.1', '2002/2006', 'PRAGMA ADE/POD'] unless defined? ownpath ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') @@ -41,6 +41,69 @@ class Commands include CommandBase + def tpmmake + if filename = @commandline.argument('first') then + filename = File.join('tpm',filename) unless filename =~ /^tpm[\/\\]/ + filename += '.tpm' unless filename =~ /\.tpm$/ + if FileTest.file?(filename) then + data = IO.read(filename) rescue '' + data, fn, n = calculate_tpm(data,"TPM:RunFiles") + data, fm, m = calculate_tpm(data,"TPM:DocFiles") + data = replace_tpm(data,"TPM:Size",n+m) + report("total size #{n+m}") + begin + File.open(filename, 'w') do |f| + f << data + end + rescue + report("unable to save '#{filename}'") + else + report("file '#{filename}' is updated") + filename = File.basename(filename).sub(/\..*$/,'') + zipname = sprintf("%s-%04i.%02i.%02i%s",filename,Time.now.year,Time.now.month,Time.now.day,'.zip') + File.delete(zipname) rescue true + report("zipping file '#{zipname}'") + system("zip -r -9 -q #{zipname} #{[fn,fm].flatten.join(' ')}") + end + else + report("no file '#{filename}'") + end + end + end + + def calculate_tpm(data, tag='') + size, ok = 0, Array.new + data.gsub!(/<#{tag}.*>(.*?)<\/#{tag}>/m) do + content = $1 + files = content.split(/\s+/) + files.each do |file| + unless file =~ /^\s*$/ then + if FileTest.file?(file) then + report("found file #{file}") + size += FileTest.size(file) rescue 0 + ok << file + else + report("missing file #{file}") + end + end + end + "<#{tag} size=\"#{size}\">#{content}</#{tag}>" + end + [data, ok, size] + end + + def replace_tpm(data, tag='', txt='') + data.gsub(/(<#{tag}.*>)(.*?)(<\/#{tag}>)/m) do + $1 + txt.to_s + $3 + end + end + +end + +class Commands + + include CommandBase + def hidemapnames report('hiding FontNames in map files') xidemapnames(true) @@ -871,6 +934,8 @@ commandline.registeraction('downcasefilenames', '[--recurse] [--force]') # not y commandline.registeraction('stripformfeeds' , '[--recurse] [--force]') # not yet documented commandline.registeraction('showfont' , 'filename') +commandline.registeraction('tpmmake' , 'tpm file (run in texmf root)') + commandline.registeraction('help') commandline.registeraction('version') diff --git a/Master/texmf-dist/scripts/context/ruby/texutil.rb b/Master/texmf-dist/scripts/context/ruby/texutil.rb new file mode 100644 index 00000000000..da9a192054d --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/texutil.rb @@ -0,0 +1,96 @@ +banner = ['TeXUtil ', 'version 9.1.0', '1997-2005', 'PRAGMA ADE/POD'] + +unless defined? ownpath + ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') + $: << ownpath +end + +require 'base/switch' +require 'base/logger' +require 'base/file' +require 'base/texutil' + +class Commands + + include CommandBase + + def references + filename = @commandline.argument('first') + if not filename.empty? and FileTest.file?(File.suffixed(filename,'tuo')) then + if tu = TeXUtil::Converter.new(logger) and tu.loaded(filename) then + tu.saved if tu.processed + end + end + end + + def main + if @commandline.arguments.length>0 then + references + else + help + end + end + + def purgefiles + system("texmfstart ctxtools --purge #{@commandline.arguments.join(' ')}") + end + + def purgeallfiles + system("texmfstart ctxtools --purge --all #{@commandline.arguments.join(' ')}") + end + + def documentation + system("texmfstart ctxtools --document #{@commandline.arguments.join(' ')}") + end + + def analyzefile + system("texmfstart pdftools --analyze #{@commandline.arguments.join(' ')}") + end + + def filterpages # obsolete + system("texmfstart ctxtools --purge #{@commandline.arguments.join(' ')}") + end + + def figures + report("this code is not yet converted from perl to ruby") + end + + def logfile + report("this code is not yet converted from perl to ruby") + end + +end + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +# main feature + +commandline.registeraction('references', 'convert tui file into tuo file') + +# todo features + +commandline.registeraction('figures', 'generate figure dimensions file') +commandline.registeraction('logfile', 'filter essential log messages') + +# backward compatibility features + +commandline.registeraction('purgefiles', 'remove most temporary files') +commandline.registeraction('purgeallfiles', 'remove all temporary files') +commandline.registeraction('documentation', 'generate documentation file from source') +commandline.registeraction('analyzefile', 'analyze pdf file') + +# old feature, not needed any longer due to extension of pdftex + +commandline.registeraction('filterpages') + +# generic features + +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.registerflag('verbose') + +commandline.expand + +Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/tmftools.rb b/Master/texmf-dist/scripts/context/ruby/tmftools.rb index d125c5cae69..074e949d139 100644 --- a/Master/texmf-dist/scripts/context/ruby/tmftools.rb +++ b/Master/texmf-dist/scripts/context/ruby/tmftools.rb @@ -21,10 +21,11 @@ # tmftools --analyze > kpsewhat.log # tmftools --analyze --strict > kpsewhat.log # tmftools --analyze --delete --force "texmf-local/fonts/.*/somename" +# tmftools --serve # the real thing -banner = ['TMFTools', 'version 1.0.0 (experimental, no help yet)', '2005', 'PRAGMA ADE/POD'] +banner = ['TMFTools', 'version 1.1.0 (experimental, no help yet)', '2005/2006', 'PRAGMA ADE/POD'] unless defined? ownpath ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') @@ -33,27 +34,58 @@ end require 'base/switch' require 'base/logger' -require 'base/kpsefast' class Commands include CommandBase def init_kpse - k = KPSEFAST.new - k.rootpath = @commandline.option('rootpath') - k.treepath = @commandline.option('treepath') - k.progname = @commandline.option('progname') - k.engine = @commandline.option('engine') - k.format = @commandline.option('format') - k.diskcache = @commandline.option('diskcache') - k.renewcache = @commandline.option('renewcache') - k.load_cnf - k.expand_variables - k.load_lsr + # require 'base/kpseremote' + # if KpseRemote::available? then + if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then + require 'base/kpseremote' + k = KpseRemote.new + else + k = nil + end + if k && k.okay? then + k.progname = @commandline.option('progname') + k.engine = @commandline.option('engine') + k.format = @commandline.option('format') + else + require 'base/kpsefast' + k = KpseFast.new + k.rootpath = @commandline.option('rootpath') + k.treepath = @commandline.option('treepath') + k.progname = @commandline.option('progname') + k.engine = @commandline.option('engine') + k.format = @commandline.option('format') + k.diskcache = @commandline.option('diskcache') + k.renewcache = @commandline.option('renewcache') + k.load_cnf + k.expand_variables + k.load_lsr + end return k end + def serve + if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then + require 'base/kpseremote' + begin + KpseRemote::start_server + rescue + end + end + end + + def reload + begin + init_kpse.load + rescue + end + end + def main if option = @commandline.option('expand-braces') and not option.empty? then puts init_kpse.expand_braces(option) @@ -128,6 +160,9 @@ commandline.registerflag('verbose') commandline.registeraction('help') commandline.registeraction('version') +commandline.registeraction('reload', 'reload file database') +commandline.registeraction('serve', 'act as kpse server') + commandline.expand Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/www/admin.rb b/Master/texmf-dist/scripts/context/ruby/www/admin.rb new file mode 100644 index 00000000000..8983ca2b77c --- /dev/null +++ b/Master/texmf-dist/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 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} </th>\n" + end + + def td(str) + "<td><code>#{str || ''}  </code></td>\n" + end + +end diff --git a/Master/texmf-dist/scripts/context/ruby/www/common.rb b/Master/texmf-dist/scripts/context/ruby/www/common.rb new file mode 100644 index 00000000000..9c383229456 --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/www/dir.rb b/Master/texmf-dist/scripts/context/ruby/www/dir.rb new file mode 100644 index 00000000000..09e088d772d --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/www/exa.rb b/Master/texmf-dist/scripts/context/ruby/www/exa.rb new file mode 100644 index 00000000000..c6d59d86434 --- /dev/null +++ b/Master/texmf-dist/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','exalogin-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/Master/texmf-dist/scripts/context/ruby/www/lib.rb b/Master/texmf-dist/scripts/context/ruby/www/lib.rb new file mode 100644 index 00000000000..542c31166d3 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/www/lib.rb @@ -0,0 +1,1399 @@ +#!/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' , 'exalogin-template.htm') + @interface.set('template:status' , 'exalogin-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 + @interface.set('path:root', File.dirname(@interface.get('path:docroot'))) + 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!("&","&") + val.gsub!("<","<") + val.gsub!(">",">") + 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) + targetname = File.join(cache_root,dir,resultname) + # make sure that there is no target left in case of an + # error; needed in case of given session name + if FileTest.directory?(File.join(cache_root,dir)) then + File.delete(targetname) rescue false + end + # now try to locate the file + if FileTest.file?(fullname) then + if indirect?(fullname) then + begin + # check if directory exists and (if so) delete left overs + File.makedirs(File.join(cache_root,dir)) + File.delete(targetname) rescue false + 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> (#{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/Master/texmf-dist/scripts/context/ruby/www/login.rb b/Master/texmf-dist/scripts/context/ruby/www/login.rb new file mode 100644 index 00000000000..1c88a97e6ad --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/wwwclient.rb b/Master/texmf-dist/scripts/context/ruby/wwwclient.rb new file mode 100644 index 00000000000..8f5451a8dcf --- /dev/null +++ b/Master/texmf-dist/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/Master/texmf-dist/scripts/context/ruby/wwwserver.rb b/Master/texmf-dist/scripts/context/ruby/wwwserver.rb new file mode 100644 index 00000000000..18ea57df1d2 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/wwwserver.rb @@ -0,0 +1,293 @@ +#!/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, + :DocumentRootOptions => { :FancyIndexing => false }, + :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/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb b/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb new file mode 100644 index 00000000000..0afb9a39291 --- /dev/null +++ b/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb @@ -0,0 +1,483 @@ +#!/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, :create, :cache_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 + @create = false + # [:INT, :TERM, :EXIT].each do |signal| + # trap(signal) do + # kill + # exit # rescue false + # 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(Dir.pwd),'.')) 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 @cache_path.empty? + @cache_path = File.expand_path(File.join(File.dirname(@work_path),'cache')) if @cache_path.empty? + if @create then + begin File.makedirs(@work_path) ; rescue ; end + begin File.makedirs(@cache_path) ; rescue ; end + end + unless File.writable?(@work_path) then + @work_path = File.expand_path(File.join(Dir.tmpdir,'work','watch')) + if @create then + begin File.makedirs(@work_path) ; rescue ; end + end + end + unless File.writable?(@cache_path) then + @cache_path = File.expand_path(File.join(Dir.tmpdir,'work','cache')) + if @create then + begin File.makedirs(@cache_path) ; rescue ; end + end + end + unless File.writable?(@work_path) then + puts "no valid work path: #{@work_path}" + exit! rescue false # no checking, no at_exit done + 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 + else + report("provide valid work path") + end + end + def main + watch + end + + private + + def setup + if watch = Watch.new(logger) then + watch.root_path = @commandline.option('root') + watch.work_path = @commandline.option('work') + watch.cache_path = @commandline.option('cache') + watch.create = @commandline.option('create') + watch.verbose = @commandline.option('verbose') + begin + watch.max_threads = @commandline.option('threads').to_i + rescue + watch.max_threads = 5 + end + watch.setup + end + return watch + end + +end + +logger = Logger.new(banner.shift) +commandline = CommandLine.new + +commandline.registervalue('root', '') +commandline.registervalue('work', '') +commandline.registervalue('cache', '') +commandline.registervalue('threads', '5') + +commandline.registerflag('create') + +commandline.registeraction('watch', '[--work=path] [--root=path] [--create]') + +commandline.registerflag('verbose') +commandline.registeraction('help') +commandline.registeraction('version') + +commandline.expand + +Commands.new(commandline,logger,banner).send(commandline.action || 'main') diff --git a/Master/texmf-dist/scripts/context/ruby/xmltools.rb b/Master/texmf-dist/scripts/context/ruby/xmltools.rb index a57f7445020..a10d34314aa 100644 --- a/Master/texmf-dist/scripts/context/ruby/xmltools.rb +++ b/Master/texmf-dist/scripts/context/ruby/xmltools.rb @@ -10,13 +10,16 @@ # info : j.hagen@xs4all.nl # www : www.pragma-ade.com +# todo : use kpse lib + # This script will harbor some handy manipulations on tex # related files. -banner = ['XMLTools', 'version 1.1.1', '2002/2005', 'PRAGMA ADE/POD'] +banner = ['XMLTools', 'version 1.2.0', '2002/2006', 'PRAGMA ADE/POD'] unless defined? ownpath ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'') + # ownpath = File.dirname($0) $: << ownpath end @@ -170,7 +173,7 @@ class Commands if FileTest.file?(file+'.xml') then style = "--arg=\"style=#{style}\"" unless style.empty? modes = "--mode=#{modes}" unless modes.empty? - if system("texmfstart texexec.pl --batch --pdf --once --result=#{long} --use=mmlpag #{style} #{modes} #{file}.xml") then + if system("texmfstart texexec --batch --pdf --once --result=#{long} --use=mmlpag #{style} #{modes} #{file}.xml") then if eps then if f = open("#{file}-mmlpages.txt") then while line = f.gets do @@ -226,7 +229,7 @@ class Commands else report("error in processing file #{file}") end - system("texmfstart texutil --purge") + system("texmfstart ctxtools --purge") else report("error in processing file #{file}") end @@ -235,77 +238,188 @@ class Commands def analyze - file = @commandline.argument('first') - result = @commandline.option('output') + file = @commandline.argument('first') + result = @commandline.option('output') + utf = @commandline.option('utf') + process = @commandline.option('process') if FileTest.file?(file) then if data = IO.read(file) then - report("xml file #{file} loaded") - elements = Hash.new - attributes = Hash.new - entities = Hash.new - data.scan(/<([^>\s\/\!\?]+)([^>]*?)>/o) do - element, attributelist = $1, $2 - if elements.key?(element) then - elements[element] += 1 - else - elements[element] = 1 - end - attributelist.scan(/\s*([^\=]+)\=([\"\'])(.*?)(\2)/) do - key, value = $1, $3 - attributes[element] = Hash.new unless attributes.key?(element) - attributes[element][key] = Hash.new unless attributes[element].key?(key) - if attributes[element][key].key?(value) then - attributes[element][key][value] += 1 + if data =~ /<?xml.*?version\=/ then + report("xml file #{file} loaded") + elements = Hash.new + attributes = Hash.new + entities = Hash.new + chars = Hash.new + unicodes = Hash.new + names = Hash.new + data.scan(/<([^>\s\/\!\?]+)([^>]*?)>/o) do + element, attributelist = $1, $2 + if elements.key?(element) then + elements[element] += 1 else - attributes[element][key][value] = 1 + elements[element] = 1 + end + attributelist.scan(/\s*([^\=]+)\=([\"\'])(.*?)(\2)/) do + key, value = $1, $3 + attributes[element] = Hash.new unless attributes.key?(element) + attributes[element][key] = Hash.new unless attributes[element].key?(key) + if attributes[element][key].key?(value) then + attributes[element][key][value] += 1 + else + attributes[element][key][value] = 1 + end end end - end - data.scan(/\&([^\;]+)\;/o) do - entity = $1 - if entities.key?(entity) then - entities[entity] += 1 - else - entities[entity] = 1 + data.scan(/\&([^\;]+)\;/o) do + entity = $1 + if entities.key?(entity) then + entities[entity] += 1 + else + entities[entity] = 1 + end end - end - result = file.gsub(/\..*?$/, '') + '.xlg' if result.empty? - if f = File.open(result,'w') then - report("saving report in #{result}") - f.puts "<?xml version='1.0'?>\n" - f.puts "<document>\n" - if entities.length>0 then - f.puts " <entities>\n" - entities.keys.asort.each do |entity| - f.puts " <entity name=#{entity.xstring} n=#{entities[entity].to_s.xstring}/>\n" + if utf then + data.scan(/(\w)/u) do + chars[$1] = (chars[$1] || 0) + 1 + end + if chars.size > 0 then + begin + # todo : use kpse lib + filename, ownpath, foundpath = 'contextnames.txt', File.dirname($0), '' + begin + foundpath = File.dirname(`kpsewhich -progname=context -format=\"other text files\" #{filename}`.chomp) + rescue + foundpath = '.' + else + foundpath = '.' if foundpath.empty? + end + [foundpath,ownpath,File.join(ownpath,'../../../context/data')].each do |path| + fullname = File.join(path,filename) + if FileTest.file?(fullname) then + report("loading '#{fullname}'") + # rough scan, we assume no valid lines after comments + IO.read(fullname).scan(/^([0-9A-F][0-9A-F][0-9A-F][0-9A-F])\s*\;\s*(.*?)\s*\;\s*(.*?)\s*\;\s*(.*?)\s*$/) do + names[$1.hex.to_i.to_s] = [$2,$3,$4] + end + break + end + end + rescue + end end - f.puts " </entities>\n" end - if elements.length>0 then - f.puts " <elements>\n" - elements.keys.sort.each do |element| - if attributes.key?(element) then - f.puts " <element name=#{element.xstring} n=#{elements[element].to_s.xstring}>\n" + result = file.gsub(/\..*?$/, '') + '.xlg' if result.empty? + if f = File.open(result,'w') then + report("saving report in #{result}") + f.puts "<?xml version='1.0'?>\n" + f.puts "<document>\n" + if entities.length>0 then + total = 0 + entities.each do |k,v| + total += v + end + f.puts " <entities n=#{total.to_s.xstring}>\n" + entities.keys.asort.each do |entity| + f.puts " <entity name=#{entity.xstring} n=#{entities[entity].to_s.xstring}/>\n" + end + f.puts " </entities>\n" + end + if utf && (chars.size > 0) then + total = 0 + chars.each do |k,v| + total += v + end + f.puts " <characters n=#{total.to_s.xstring}>\n" + chars.each do |k,v| + if k.length > 1 then + begin + u = k.unpack('U') + unicodes[u] = (unicodes[u] || 0) + v + rescue + report("invalid utf codes") + end + end + end + unicodes.keys.sort.each do |u| + ustr = u.to_s + if names[ustr] then + f.puts " <character number=#{ustr.xstring} pname=#{names[ustr][0].xstring} cname=#{names[ustr][1].xstring} uname=#{names[ustr][2].xstring} n=#{unicodes[u].to_s.xstring}/>\n" + else + f.puts " <character number=#{ustr.xstring} n=#{unicodes[u].to_s.xstring}/>\n" + end + end + f.puts " </characters>\n" + end + if elements.length>0 then + f.puts " <elements>\n" + elements.keys.sort.each do |element| if attributes.key?(element) then - attributes[element].keys.asort.each do |attribute| - f.puts " <attribute name=#{attribute.xstring}>\n" - attributes[element][attribute].keys.asort.each do |value| - f.puts " <instance value=#{value.xstring} n=#{attributes[element][attribute][value].to_s.xstring}/>\n" + f.puts " <element name=#{element.xstring} n=#{elements[element].to_s.xstring}>\n" + if attributes.key?(element) then + attributes[element].keys.asort.each do |attribute| + f.puts " <attribute name=#{attribute.xstring}>\n" + if attribute =~ /id$/o then + nn = 0 + attributes[element][attribute].keys.asort.each do |value| + nn += attributes[element][attribute][value].to_i + end + f.puts " <instance value=#{"*".xstring} n=#{nn.to_s.xstring}/>\n" + else + attributes[element][attribute].keys.asort.each do |value| + f.puts " <instance value=#{value.xstring} n=#{attributes[element][attribute][value].to_s.xstring}/>\n" + end + end + f.puts " </attribute>\n" end - f.puts " </attribute>\n" end + f.puts " </element>\n" + else + f.puts " <element name=#{element.xstring} n=#{elements[element].to_s.xstring}/>\n" end - f.puts " </element>\n" - else - f.puts " <element name=#{element.xstring} n=#{elements[element].to_s.xstring}/>\n" end + f.puts " </elements>\n" + end + f.puts "</document>\n" + f.close + if process then + system("texmfstart texexec --purge --pdf --use=xml-analyze #{result}") + end + else + report("unable to open file '#{result}'") + end + else + report("invalid xml file '#{file}'") + end + else + report("unable to load file '#{file}'") + end + else + report("unknown file '#{file}'") + end + end + + def cleanup # todo, share loading/saving with previous + + file = @commandline.argument('first') + force = @commandline.option('force') + verbose = @commandline.option('verbose') + + if FileTest.file?(file) then + if data = IO.read(file) then + if data =~ /<?xml.*?version\=/ then + data = doxmlcleanup(data,verbose) + result = if force then file else file.gsub(/\..*?$/, '') + '.xlg' end + begin + if f = File.open(result,'w') then + f << data + f.close end - f.puts " </elements>\n" + rescue + report("unable to open file '#{result}'") end - f.puts "</document>\n" else - report("unable to open file '#{result}'") + report("invalid xml file '#{file}'") end else report("unable to load file '#{file}'") @@ -313,8 +427,77 @@ class Commands else report("unknown file '#{file}'") end + end + def doxmlreport(str,verbose=false) + if verbose then + result = str + report(result) + return result + else + return str + end + end + + def doxmlcleanup(data="",verbose=false) + + # remove funny spaces (looks cleaner) + # + # data = "<whatever ></whatever ><whatever />" + + data.gsub!(/\<(\/*\w+)\s*(\/*)>/o) do + "<#{$1}#{$2}>" + end + + # remove funny ampersands + # + # data = "<x> B&W </x>" + + data.gsub!(/\&([^\<\>\&]*?)\;/mo) do + "<entity name='#{$1}'/>" + end + data.gsub!(/\&/o) do + doxmlreport("&",verbose) + end + data.gsub!(/\<entity name=\'(.*?)\'\/\>/o) do + doxmlreport("&#{$1};",verbose) + end + + # remove funny < > + # + # data = "<x> < 5% </x>" + + data.gsub!(/<([^>].*?)>/o) do + tag = $1 + case tag + when /^\//o then + "<#{tag}>" # funny tag but ok + when /\/$/o then + "<#{tag}>" # funny tag but ok + when /</o then + doxmlreport("<#{tag}>",verbose) + else + "<#{tag}>" + end + end + + # remove funny < > + # + # data = "<x> > 5% </x>" + + data.gsub!(/<([^>].*?)>([^\>\<]*?)>/o) do + doxmlreport("<#{$1}>#{$2}>",verbose) + end + + return data + end + + # puts doxmlcleanup("<whatever ></whatever ><whatever />") + # puts doxmlcleanup("<x> B&W </x>") + # puts doxmlcleanup("<x> < 5% </x>") + # puts doxmlcleanup("<x> > 5% </x>") + end logger = Logger.new(banner.shift) @@ -322,7 +505,8 @@ commandline = CommandLine.new commandline.registeraction('dir', 'generate directory listing') commandline.registeraction('mmlpages','generate graphic from mathml') -commandline.registeraction('analyze', 'report entities and elements') +commandline.registeraction('analyze', 'report entities and elements [--utf --process]') +commandline.registeraction('cleanup', 'cleanup xml file [--force]') # commandline.registeraction('dir', 'filename --pattern= --output= [--recurse --stripname --longname --url --root]') # commandline.registeraction('mmlpages','filename [--eps --jpg --png --style= --mode=]') @@ -344,8 +528,11 @@ commandline.registervalue('root') commandline.registerflag('eps') commandline.registerflag('png') commandline.registerflag('jpg') +commandline.registerflag('utf') +commandline.registerflag('process') commandline.registervalue('style') commandline.registervalue('modes') +commandline.registervalue('verbose') commandline.expand diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat new file mode 100644 index 00000000000..13614a1d98d --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart ctxtools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat new file mode 100644 index 00000000000..36230a528f4 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart exatools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat b/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat new file mode 100644 index 00000000000..9622d96a5f3 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart makempy.pl %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat new file mode 100644 index 00000000000..10ec3f1d687 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart mpstools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat b/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat new file mode 100644 index 00000000000..757ea58f9ce --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart mptopdf.pl %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat new file mode 100644 index 00000000000..71662d3f766 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart pdftools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat b/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat new file mode 100644 index 00000000000..091b0d4d7bd --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart pstopdf.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat new file mode 100644 index 00000000000..e5cdca25009 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart rlxtools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat new file mode 100644 index 00000000000..37df398ab37 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart runtools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat new file mode 100644 index 00000000000..44615dbd918 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart texexec.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat new file mode 100644 index 00000000000..9dd50309ff3 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart texfont.pl %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat new file mode 100644 index 00000000000..4fc5c549255 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart textools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat new file mode 100644 index 00000000000..61e90ca6509 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart texutil.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat new file mode 100644 index 00000000000..a7053043d22 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart tmftools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat new file mode 100644 index 00000000000..9721095b298 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat @@ -0,0 +1,2 @@ +@echo off
+texmfstart xmltools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/ctxtools b/Master/texmf-dist/scripts/context/stubs/unix/ctxtools new file mode 100644 index 00000000000..84e47bbee3c --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/ctxtools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart ctxtools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/exatools b/Master/texmf-dist/scripts/context/stubs/unix/exatools new file mode 100644 index 00000000000..50ff0f07e46 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/exatools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart exatools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/makempy b/Master/texmf-dist/scripts/context/stubs/unix/makempy new file mode 100644 index 00000000000..4bf7a1af230 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/makempy @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart makempy.pl "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mpstools b/Master/texmf-dist/scripts/context/stubs/unix/mpstools new file mode 100644 index 00000000000..b4c8f634548 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/mpstools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart mpstools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mptopdf b/Master/texmf-dist/scripts/context/stubs/unix/mptopdf new file mode 100644 index 00000000000..980a3123d1a --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/mptopdf @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart mptopdf.pl "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/pdftools b/Master/texmf-dist/scripts/context/stubs/unix/pdftools new file mode 100644 index 00000000000..92ee803a868 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/pdftools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart pdftools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/pstopdf b/Master/texmf-dist/scripts/context/stubs/unix/pstopdf new file mode 100644 index 00000000000..5b38ed426cb --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/pstopdf @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart pstopdf.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/rlxtools b/Master/texmf-dist/scripts/context/stubs/unix/rlxtools new file mode 100644 index 00000000000..41cea40fc09 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/rlxtools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart rlxtools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/runtools b/Master/texmf-dist/scripts/context/stubs/unix/runtools new file mode 100644 index 00000000000..ff9a333791b --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/runtools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart runtools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texexec b/Master/texmf-dist/scripts/context/stubs/unix/texexec new file mode 100644 index 00000000000..2158172906a --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/texexec @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart texexec.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texfont b/Master/texmf-dist/scripts/context/stubs/unix/texfont new file mode 100644 index 00000000000..a91f786e3f8 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/texfont @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart texfont.pl "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/textools b/Master/texmf-dist/scripts/context/stubs/unix/textools new file mode 100644 index 00000000000..7445eac371c --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/textools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart textools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texutil b/Master/texmf-dist/scripts/context/stubs/unix/texutil new file mode 100644 index 00000000000..607154af059 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/texutil @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart texutil.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/tmftools b/Master/texmf-dist/scripts/context/stubs/unix/tmftools new file mode 100644 index 00000000000..7531a966390 --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/tmftools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart tmftools.rb "$@" diff --git a/Master/texmf-dist/scripts/context/stubs/unix/xmltools b/Master/texmf-dist/scripts/context/stubs/unix/xmltools new file mode 100644 index 00000000000..03086d0436b --- /dev/null +++ b/Master/texmf-dist/scripts/context/stubs/unix/xmltools @@ -0,0 +1,2 @@ +#!/bin/sh +texmfstart xmltools.rb "$@" |