#!/usr/bin/env ruby

# program   : textools
# copyright : PRAGMA Advanced Document Engineering
# version   : 1.1 - 2002/2004
# author    : Hans Hagen

# This script will harbor some handy manipulations on tex
# related files.

banner = ['TeXTools', 'version 1.2', '2002/2004', 'PRAGMA ADE/POD']

unless defined? ownpath
    ownpath = $0.sub(/[\\\/][a-z0-9\-]*?\.rb/i,'')
    $: << ownpath
end

require 'ftools'
require 'xmpl/switch'
require 'exa/logger'

# Remark
#
# The fixtexmftrees feature does not realy belong in textools, but
# since it looks like no measures will be taken to make texlive (and
# tetex) downward compatible with respect to fonts installed by
# users, we provide this fixer. This option also moves script files
# to their new location (only for context) in the TDS. Beware: when
# locating  scripts, the --format switch in kpsewhich should now use
# 'texmfscripts' instead of  'other text files' (texmfstart is already
# aware of this). Files will only be moved when --force is given. Let
# me know if more fixes need to be made.

class Commands

    include CommandBase

    def hidemapnames
        report('hiding FontNames in map files')
        xidemapnames(true)
    end

    def videmapnames
        report('unhiding FontNames in map files')
        xidemapnames(false)
    end

    def removemapnames

        report('removing FontNames from map files')

        if files = findfiles('map') then
            report
            files.sort.each do |fn|
                gn = fn # + '.nonames'
                hn = fn + '.original'
                begin
                    if FileTest.file?(fn) && ! FileTest.file?(hn) then
                        if File.rename(fn,hn) then
                            if (fh = File.open(hn,'r')) && (gh = File.open(gn,'w')) then
                                report("processing #{fn}")
                                while str = fh.gets do
                                    str.sub!(/^([^\%]+?)(\s+)([^\"\<\s]*?)(\s)/) do
                                        $1 + $2 + " "*$3.length + $4
                                    end
                                    gh.puts(str)
                                end
                                fh.close
                                gh.close
                            else
                                report("no permissions to handle #{fn}")
                            end
                        else
                            report("unable to rename #{fn} to #{hn}")
                        end
                    else
                        report("not processing #{fn} due to presence of #{hn}")
                    end
                rescue
                    report("error in handling #{fn}")
                end
            end
        end

    end

    def restoremapnames

        report('restoring FontNames in map files')

        if files = findfiles('map') then
            report
            files.sort.each do |fn|
                hn = fn + '.original'
                begin
                    if FileTest.file?(hn) then
                        File.delete(fn) if FileTest.file?(fn)
                        report("#{fn} restored") if File.rename(hn,fn)
                    else
                        report("no original found for #{fn}")
                    end
                rescue
                    report("error in restoring #{fn}")
                end
            end
        end

    end

    def findfile

        report('locating file in texmf tree')

        # ! not in tree
        # ? fuzzy
        # . in tree
        # > in tree and used

        if filename = @commandline.argument('first') then
            if filename && ! filename.empty? then
                report
                used = kpsefile(filename) || pathfile(filename)
                if paths = texmfroots then
                    found, prefered = false, false
                    paths.each do |p|
                        if files = texmffiles(p,filename) then
                            found = true
                            files.each do |f|
                                # unreadable: report("#{if f == used then '>' else '.' end} #{f}")
                                if f == used then
                                    prefered = true
                                    report("> #{f}")
                                else
                                    report(". #{f}")
                                end
                            end
                        end
                    end
                    if prefered then
                        report("! #{used}") unless found
                    else
                        report("> #{used}")
                    end
                elsif used then
                    report("? #{used}")
                else
                    report('no file found')
                end
            else
                report('no file specified')
            end
        else
            report('no file specified')
        end

    end

    def unzipfiles

        report('g-unzipping files')

        if files = findfiles('gz') then
            report
            files.each do |f|
                begin
                    system("gunzip -d #{f}")
                rescue
                    report("unable to unzip file #{f}")
                else
                    report("file #{f} is unzipped")
                end
            end
        end

    end

    def fixafmfiles

        report('fixing afm files')

        if files = findfiles('afm') then
            report
            ok = false
            files.each do |filename|
                if filename =~ /\.afm$/io then
                    if f = File.open(filename) then
                        result = ''
                        done = false
                        while str = f.gets do
                            str.chomp!
                            str.strip!
                            if str.empty? then
                                # skip
                            elsif (str.length > 200) && (str =~ /^(comment|notice)\s(.*)\s*$/io) then
                                done = true
                                tag, words, len = $1, $2.split(' '), 0
                                result += tag
                                while words.size > 0 do
                                    str = words.shift
                                    len += str.length + 1
                                    result += ' ' + str
                                    if len > (70 - tag.length) then
                                        result += "\n"
                                        result += tag if words.size > 0
                                        len = 0
                                    end
                                end
                                result += "\n" if len>0
                            else
                                result += str + "\n"
                            end
                        end
                        f.close
                        if done then
                            ok = true
                            begin
                                if File.rename(filename,filename+'.original') then
                                    if FileTest.file?(filename) then
                                        report("something to fix in #{filename} but error in renaming (3)")
                                    elsif f = File.open(filename,'w') then
                                        f.puts(result)
                                        f.close
                                        report('file', filename, 'has been fixed')
                                    else
                                        report("something to fix in #{filename} but error in opening (4)")
                                        File.rename(filename+'.original',filename) # gamble
                                    end
                                else
                                    report("something to fix in #{filename} but error in renaming (2)")
                                end
                            rescue
                                report("something to fix in #{filename} but error in renaming (1)")
                            end
                        else
                            report("nothing to fix in #{filename}")
                        end
                    else
                        report("error in opening #{filename}")
                    end
                end
            end
            report('no files match the pattern') unless ok
        end

    end

    def mactodos

        report('fixing mac newlines')

        if files = findfiles('tex') then
            report
            files.each do |filename|
                begin
                    report("converting file #{filename}")
                    tmpfilename = filename + '.tmp'
                    if f = File.open(filename) then
                        if g = File.open(tmpfilename, 'w')
                            while str = f.gets do
                                g.puts(str.gsub(/\r/,"\n"))
                            end
                            if f.close && g.close && FileTest.file?(tmpfilename) then
                                File.delete(filename)
                                File.rename(tmpfilename,filename)
                            end
                        else
                            report("unable to open temporary file #{tmpfilename}")
                        end
                    else
                        report("unable to open #{filename}")
                    end
                rescue
                    report("problems with fixing #{filename}")
                end
            end
        end

    end

    def fixtexmftrees

        if paths = @commandline.argument('first') then
            paths = [paths] if ! paths.empty?
        end
        paths = texmfroots if paths.empty?

        if paths then

            moved = 0
            force = @commandline.option('force')

            report
            report("checking TDS 2003 => TDS 2004 : map files")
            # report

            # move [map,enc]  files from /texmf/[dvips,pdftex,dvipdfmx] -> /texmf/fonts/[*]

            ['map','enc'].each do |suffix|
                paths.each do |path|
                    ['dvips','pdftex','dvipdfmx'].each do |program|
                        report
                        report("checking #{suffix} files for #{program} on #{path}")
                        report
                        moved += movefiles("#{path}/#{program}","#{path}/fonts/#{suffix}/#{program}",suffix) do
                            # nothing
                        end
                    end
                end
            end

            report
            report("checking TDS 2003 => TDS 2004 : scripts")
            # report

            # move [rb,pl,py] files from /texmf/someplace -> /texmf/scripts/someplace

            ['rb','pl','py'].each do |suffix|
                paths.each do |path|
                    ['context'].each do |program|
                        report
                        report("checking #{suffix} files for #{program} on #{path}")
                        report
                        moved += movefiles("#{path}/#{program}","#{path}/scripts/#{program}",suffix) do |f|
                            f.gsub!(/\/(perl|ruby|python)tk\//o) do
                                "/#{$1}/"
                            end
                        end
                    end
                end
            end

            begin
                if moved>0 then
                    report
                    if force then
                        system('mktexlsr')
                        report
                        report("#{moved} files moved")
                    else
                        report("#{moved} files will be moved")
                    end
                else
                    report('no files need to be moved')
                end
            rescue
                report('you need to run mktexlsr')
            end

        end

    end

    def replace

        report('replace file')

        if newname = @commandline.argument('first') then
            if newname && ! newname.empty? then
                report
                report("replacing #{newname}")
                report
                oldname = kpsefile(File.basename(newname))
                force = @commandline.option('force')
                if oldname && ! oldname.empty? then
                    oldname = File.expand_path(oldname)
                    newname = File.expand_path(newname)
                    report("old: #{oldname}")
                    report("new: #{newname}")
                    report
                    if newname == oldname then
                        report('unable to replace itself')
                    elsif force then
                        begin
                            File.copy(newname,oldname)
                        rescue
                            report('error in replacing the old file')
                        end
                    else
                        report('the old file will be replaced (use --force)')
                    end
                else
                    report('nothing to replace')
                end
            else
                report('no file specified')
            end
        else
            report('no file specified')
        end

    end

    private # general

    def texmfroots
        begin
            paths = `kpsewhich -expand-path=\$TEXMF`.chomp
        rescue
        else
            return paths.split(/#{File::PATH_SEPARATOR}/) if paths && ! paths.empty?
        end
        return nil
    end

    def texmffiles(root, filename)
        begin
            files = Dir.glob("#{root}/**/#{filename}")
        rescue
        else
            return files if files && files.length>0
        end
        return nil
    end

    def pathfile(filename)
        used = nil
        begin
            if ! filename || filename.empty? then
                return nil
            else
                ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
                    if FileTest.file?(File.join(path,filename)) then
                        used = File.join(path,filename)
                        break
                    end
                end
            end
        rescue
            used = nil
        else
            used = nil if used && used.empty?
        end
        return used
    end

    def kpsefile(filename)
        used = nil
        begin
            if ! filename || filename.empty? then
                return nil
            else
                used = `kpsewhich #{filename}`.chomp
            end
            if used && used.empty? then
                used = `kpsewhich -progname=context #{filename}`.chomp
            end
            if used && used.empty? then
                used = `kpsewhich -format=texmfscripts #{filename}`.chomp
            end
            if used && used.empty? then
                used = `kpsewhich -progname=context -format=texmfscripts #{filename}`.chomp
            end
            if used && used.empty? then
                used = `kpsewhich -format="other text files" #{filename}`.chomp
            end
            if used && used.empty? then
                used = `kpsewhich -progname=context -format="other text files" #{filename}`.chomp
            end
        rescue
            used = nil
        else
            used = nil if used && used.empty?
        end
        return used
    end

    def downcasefilenames

        report('downcase filenames')

        force = @commandline.option('force')

        # if @commandline.option('recurse') then
            # files = Dir.glob('**/*')
        # else
            # files = Dir.glob('*')
        # end
        # if files && files.length>0 then

        if files = findfiles() then
            files.each do |oldname|
                if FileTest.file?(oldname) then
                    newname = oldname.downcase
                    if oldname != newname then
                        if force then
                            begin
                                File.rename(oldname,newname)
                            rescue
                                report("#{oldname} == #{oldname}\n")
                            else
                                report("#{oldname} => #{newname}\n")
                            end
                        else
                            report("(#{oldname} => #{newname})\n")
                        end
                    end
                end
            end
        end
    end

    def stripformfeeds

        report('strip formfeeds')

        force = @commandline.option('force')

        if files = findfiles() then
            files.each do |filename|
                if FileTest.file?(filename) then
                    begin
                        data = IO.readlines(filename).join('')
                    rescue
                    else
                        if data.gsub!(/\n*\f\n*/io,"\n\n") then
                            if force then
                                if f = open(filename,'w') then
                                    report("#{filename} is stripped\n")
                                    f.puts(data)
                                    f.close
                                else
                                    report("#{filename} cannot be stripped\n")
                                end
                            else
                                report("#{filename} will be stripped\n")
                            end
                        end
                    end
                end
            end
        end
    end

    private # specific

    def movefiles(from_path,to_path,suffix,&block)
        obsolete = 'obsolete'
        force = @commandline.option('force')
        moved = 0
        if files = texmffiles(from_path, "*.#{suffix}") then
            files.each do |filename|
                newfilename = filename.sub(/^#{from_path}/, to_path)
                yield(newfilename) if block
                if FileTest.file?(newfilename) then
                    begin
                        File.rename(filename,filename+'.obsolete') if force
                    rescue
                        report("#{filename} cannot be made obsolete") if force
                    else
                        if force then
                            report("#{filename} is made obsolete")
                        else
                            report("#{filename} will become obsolete")
                        end
                    end
                else
                    begin
                        File.makedirs(File.dirname(newfilename)) if force
                    rescue
                    end
                    begin
                        File.copy(filename,newfilename) if force
                    rescue
                        report("#{filename} cannot be copied to #{newfilename}")
                    else
                        begin
                            File.delete(filename) if force
                        rescue
                            report("#{filename} cannot be deleted") if force
                        else
                            if force then
                                report("#{filename} is moved to #{newfilename}")
                                moved += 1
                            else
                                report("#{filename} will be moved to #{newfilename}")
                            end
                        end
                    end
                end
            end
        else
            report('no matches found')
        end
        return moved
    end

    def xidemapnames(hide)

        filter  = /^([^\%]+?)(\s+)([^\"\<\s]*?)(\s)/
        banner  = '% textools:nn '

        if files = findfiles('map') then
            report
            files.sort.each do |fn|
                if fn.has_suffix?('map') then
                    begin
                        lines = IO.read(fn)
                        report("processing #{fn}")
                        if f = File.open(fn,'w') then
                            skip = false
                            if hide then
                                lines.each do |str|
                                    if skip then
                                        skip = false
                                    elsif str =~ /#{banner}/ then
                                        skip = true
                                    elsif str =~ filter then
                                        f.puts(banner+str)
                                        str.sub!(filter) do
                                            $1 + $2 + " "*$3.length + $4
                                        end
                                    end
                                    f.puts(str)
                                end
                            else
                                lines.each do |str|
                                    if skip then
                                        skip = false
                                    elsif str.sub!(/#{banner}/, '') then
                                        f.puts(str)
                                        skip = true
                                    else
                                        f.puts(str)
                                    end
                                end
                            end
                            f.close
                        end
                    rescue
                        report("error in handling #{fn}")
                    end
                end
            end
        end

    end

end

logger      = EXA::ExaLogger.new(banner.shift)
commandline = CommandLine.new

commandline.registeraction('removemapnames'   , '[pattern]   [--recurse]')
commandline.registeraction('restoremapnames'  , '[pattern]   [--recurse]')
commandline.registeraction('hidemapnames'     , '[pattern]   [--recurse]')
commandline.registeraction('videmapnames'     , '[pattern]   [--recurse]')
commandline.registeraction('findfile'         , 'filename    [--recurse]')
commandline.registeraction('unzipfiles'       , '[pattern]   [--recurse]')
commandline.registeraction('fixafmfiles'      , '[pattern]   [--recurse]')
commandline.registeraction('mactodos'         , '[pattern]   [--recurse]')
commandline.registeraction('fixtexmftrees'    , '[texmfroot] [--force]')
commandline.registeraction('replace'          , 'filename    [--force]')
commandline.registeraction('downcasefilenames', '[--recurse] [--force]') # not yet documented
commandline.registeraction('stripformfeeds'   , '[--recurse] [--force]') # not yet documented

commandline.registeraction('help')
commandline.registeraction('version')

commandline.registerflag('recurse')
commandline.registerflag('force')

commandline.expand

Commands.new(commandline,logger,banner).send(commandline.action || 'help')
