rlxtools.rb / last modification: 2020-01-30 14:16
#!/usr/bin/env ruby

# program   : rlxtools
# copyright : PRAGMA Advanced Document Engineering
# version   : 2004-2005
# author    : Hans Hagen
#
# project   : ConTeXt / eXaMpLe
# concept   : Hans Hagen
# info      : j.hagen@xs4all.nl
# www       : www.pragma-ade.com

banner = ['RlxTools', 'version 1.0.1', '2004/2005', 'PRAGMA ADE/POD']

$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,&#39;lib') ; $:.uniq!

require &#39;base/switch'
require &#39;base/logger'
require &#39;base/system'
require &#39;base/kpse'

require &#39;fileutils'
# require 'ftools'
require &#39;rexml/document'

class Commands

    include CommandBase

    # <?xml version='1.0 standalone='yes'?>
    # <rl:manipulators>
    #    <rl:manipulator name='lowres' suffix='pdf'>
    #         <rl:step>
    #             texmfstart
    #             --verbose
    #             --iftouched=<rl:value name='path'/>/<rl:value name='file'/>,<rl:value name=&#39;path'/>/<rl:value name='prefix'/><rl:value name='file'/>
    #             pstopdf
    #             --method=5
    #             --inputpath=<rl:value name='path'/>
    #             --outputpath=<rl:value name='path'/>/<rl:value name='prefix'/&gt;
    #             <rl:value name='file'/>
    #         </rl:step>
    #     </rl:manipulator>
    # </rl:manipulators>
    #
    # <?xml version='1.0' standalone='yes'?>
    # <rl:library>
    #     <rl:usage>
    #         <rl:type>figure</rl:type>
    #         <rl:state>found</rl:state>
    #         <rl:file>cow.pdf</rl:file>
    #         <rl:suffix>pdf</rl:suffix>
    #         <rl:path>.</rl:path>
    #         <rl:conversion>lowres</rl:conversion>
    #         <rl:prefix>lowres/</rl:prefix>
    #         <rl:width>276.03125pt</rl:width>
    #         <rl:height>200.75pt</rl:height>
    #     </rl:usage>
    # </r:library>

    def manipulate

        procname = @commandline.argument(&#39;first')  || ''
        filename = @commandline.argument(&#39;second') || ''

        procname = Kpse.found(procname)

        if procname.empty? || ! FileTest.file?(procname) then
            report(&#39;provide valid manipulator file')
        elsif filename.empty? || ! FileTest.file?(filename) then
            report(&#39;provide valid resource log file')
        else
            begin
                data = REXML::Document.new(File.new(filename))
            rescue
                report(&#39;provide valid resource log file (xml error)')
                return
            end
            begin
                proc = REXML::Document.new(File.new(procname))
            rescue
                report(&#39;provide valid manipulator file (xml error)')
                return
            end
            report("manipulator file: #{procname}")
            report("resourcelog file: #{filename}")
            begin
                nofrecords, nofdone = 0, 0
                REXML::XPath.each(data.root,"/rl:library/rl:usage") do |usage|
                    nofrecords += 1
                    variables = Hash.new
                    usage.elements.each do |e|
                        variables[e.name] = e.text.to_s
                    end
                    report("processing record #{nofrecords} (#{variables['file'] || 'noname'}: #{variables.size} entries)")
                    if conversion = variables[&#39;conversion'] then
                        report("testing for conversion #{conversion}")
                        if suffix = variables[&#39;suffix'] then
                            suffix.downcase!
                            if ! suffix.empty? && variables[&#39;file'] && variables['file'] !~ /\.([a-z]+)$/i then
                                variables[&#39;file'] += ".#{suffix}"
                            end
                            if file = variables[&#39;file'] then
                                report("conversion #{conversion} for suffix #{suffix} for file #{file}")
                            else
                                report("conversion #{conversion} for suffix #{suffix}")
                            end
                            pattern = "@name=&#39;#{conversion}' and @suffix='#{suffix}'"
                            if steps = REXML::XPath.first(proc.root,"/rl:manipulators/rl:manipulator[#{pattern}]") then
                                localsteps = steps.deep_clone
                                [&#39;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[&#39;name'] then
                                                substitute(value,variables[name.to_s] || &#39;')
                                            end
                                        end
                                    end
                                end
                                old = REXML::XPath.first(localsteps,"rl:old")
                                new = REXML::XPath.first(localsteps,"rl:new")
                                if old && new then
                                    old, new = justtext(old.to_s), justtext(new.to_s)
                                    variables[&#39;old'], variables['new'] = old, new
                                    begin
                                        [old,new].each do |d|
                                            File.makedirs(File.dirname(d))
                                        end
                                    rescue
                                        report("error during path creation")
                                    end
                                    report("old file #{old}")
                                    report("new file #{new}")
                                    level = if File.needsupdate(old,new) then 2 else 0 end
                                else
                                    level = 1
                                end
                                if level>0 then
                                    REXML::XPath.each(localsteps,"rl:step") do |command|
                                        REXML::XPath.each(command,"rl:old") do |value|
                                            replace(value,old)
                                        end
                                        REXML::XPath.each(command,"rl:new") do |value|
                                            replace(value,new)
                                        end
                                        REXML::XPath.each(command,"rl:value") do |value|
                                            if name = value.attributes[&#39;name'] then
                                                substitute(value,variables[name.to_s])
                                            end
                                        end
                                        str = justtext(command.to_s)
                                        # str.gsub!(/(\.\/)+/io, '')
                                        report("command #{str}")
                                        System.run(str) unless @commandline.option(&#39;test')
                                        report("synchronizing #{old} and #{new}")
                                        File.syncmtimes(old,new) if level > 1
                                        nofdone += 1
                                    end
                                else
                                    report("no need for a manipulation")
                                end
                            else
                                report("no manipulator found")
                            end
                        else
                            report("no suffix specified")
                        end
                    else
                        report("no conversion needed")
                    end
                end
                if nofdone > 0 then
                    jobname = filename.gsub(/\.(.*?)$/,&#39;') # not 'tuo' here
                    tuoname = jobname + &#39;.tuo'
                    if FileTest.file?(tuoname) && (f = File.open(tuoname,&#39;a')) then
                        f.puts("%\n% number of rlx manipulations: #{nofdone}\n")
                        f.close
                    end
                end
            rescue
                report("error in manipulating files: #{$!}")
            end
            begin
                logname = "#{filename}.log"
                File.delete(logname) if FileTest.file?(logname)
                File.copy(filename,logname)
            rescue
            end
        end

    end

    private

    def justtext(str)
        str = str.to_s
        str.gsub!(/<[^>]*?>/o, &#39;')
        str.gsub!(/\s+/o, &#39; ')
        str.gsub!(/&lt;/o, &#39;<')
        str.gsub!(/&gt;/o, &#39;>')
        str.gsub!(/&amp;/o, &#39;&')
        str.gsub!(/&quot;/o, &#39;"')
        str.gsub!(/[\/\\]+/o, &#39;/')
        return str.strip
    end

    def substitute(value,str=&#39;')
        if str then
            begin
                if value.attributes.key?(&#39;method') then
                    str = filtered(str.to_s,value.attributes[&#39;method'].to_s)
                end
                if str.empty? && value.attributes.key?(&#39;default') then
                    str = value.attributes[&#39;default'].to_s
                end
                value.insert_after(value,REXML::Text.new(str.to_s))
            rescue Exception
            end
        end
    end

    def replace(value,str)
        if str then
            begin
                value.insert_after(value,REXML::Text.new(str.to_s))
            rescue Exception
            end
        end
    end

    def filtered(str,method)

        str = str.to_s # to be sure
        case method
            when &#39;name' then # no path, no suffix
                case str
                    when /^.*[\\\/](.+?)\..*?$/o then $1
                    when /^.*[\\\/](.+?)$/o      then $1
                    when /^(.*)\..*?$/o          then $1
                    else                              str
                end
            when &#39;path'     then if str =~ /^(.+)([\\\/])(.*?)$/o then $1 else ''  end
            when &#39;suffix'   then if str =~ /^.*\.(.*?)$/o         then $1 else ''  end
            when &#39;nosuffix' then if str =~ /^(.*)\..*?$/o         then $1 else str end
            when &#39;nopath'   then if str =~ /^.*[\\\/](.*?)$/o     then $1 else str end
            else                                                               str
        end
    end

end

class Commands

    include CommandBase

    @@xmlbanner = "<?xml version=&#39;1.0' standalone='yes'?>"

    def identify(resultfile=&#39;rlxtools.rli')
        if @commandline.option(&#39;collect') then
            begin
                File.open(resultfile,&#39;w') do |f|
                    f << "#{@@xmlbanner}\n"
                    f << "<rl:identification>\n"
                    @commandline.arguments.each do |filename|
                        if state = do_identify(filename) then
                            report("#{filename} is identified")
                            f << state
                        else
                            report("unable to identify #{filename}")
                        end
                    end
                    f << "</rl:identification>\n"
                    report("result saved in #{resultfile}")
                end
            rescue
                report("error in writing result")
            end
        else
            @commandline.arguments.each do |filename|
                if state = do_identify(filename) then
                    begin
                        File.open(filename+&#39;.rli','w') do |f|
                            f << "#{@@xmlbanner}\n"
                            f << state
                        end
                    rescue
                        report("error in identifying #{filename}")
                    else
                        report("#{filename} is identified")
                    end
                else
                    report("unable to identify #{filename}")
                end
            end
        end
    end

    private

    def do_identify(filename,centimeters=false)
        begin
            str = nil
            if FileTest.file?(filename) then
                # todo: use pdfinto for pdf files, identify is bugged
                if centimeters then
                    result = `identify -units PixelsPerCentimeter -format \"x=%x,y=%y,w=%w,h=%h,b=%b\" #{filename}`.chomp.split(',')
                else
                    result = `identify -units PixelsPerInch       -format \"x=%x,y=%y,w=%w,h=%h,b=%b\" #{filename}`.chomp.split(',')
                end
                tags = Hash.new
                result.each do |r|
                    if rr = r.split("=") then
                        tags[rr[0]] = rr[1]
                    end
                end
                size   = (tags[&#39;b']||0).to_i
                width  = unified(tags[&#39;w']||0,tags['x']||'1')
                height = unified(tags[&#39;h']||0,tags['y']||'1')
                if size > 0 then
                    str = &#39;'
                    str << "<rl:identify name=&#39;#{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(&#39;manipulate', '[--test] manipulatorfile resourselog')
commandline.registeraction(&#39;identify'  , '[--collect] filename')

commandline.registeraction(&#39;help')
commandline.registeraction(&#39;version')

commandline.registerflag(&#39;test')
commandline.registerflag(&#39;collect')

commandline.expand

Commands.new(commandline,logger,banner).send(commandline.action || &#39;help')