#!/usr/bin/ruby1.8 -w

=begin
    Documentation generator for dhelp

    Copyright (C) 2005-2007  Esteban Manchado Velzquez

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
=end

require 'dhelp'
include Dhelp

require 'pathname'
require 'fileutils'
require 'erb'

require 'commandline'


# Keep the (non-existent) indentation in next line (the "PREFIX" one)
PREFIX = "/usr"
INDEX_ROOT                = "#{PREFIX}/share/doc/HTML"
DEFAULT_CATEGORY_TEMPLATE = "#{PREFIX}/share/dhelp/category.rhtml"
DEFAULT_INDEX_TEMPLATE    = "#{PREFIX}/share/dhelp/index.rhtml"


# dhelp_parse application class
class DhelpParseApp < CommandLine::Application
    DOC_DIR           = '/usr/share/doc'
    DOC_DIR_REAL      = File.expand_path(DOC_DIR)
    DHELP_FILE_REGEXP = Regexp.new("#{DOC_DIR_REAL}/(.+)/\\.dhelp")
    require 'gettext'

    # Exception for invalid dhelp files (wrong path, wrong name, ...)
    class InvalidDhelpFileError < RuntimeError; end

    # Proxy, to avoid problems with binding and ERB templates (bindtextdomain
    # doesn't work on the templates when called from the main program)
    def _(msg)
        GetText._(msg)
    end

    def initialize
        version           "0.1.0"
        author            "Esteban Manchado Velzquez"
        copyright         "Copyright (c) 2005-2007, Esteban Manchado Velzquez"
        synopsis          "[-hadr] directory1 directory2 ..."
        short_description "Debian online help system parser"
        long_description  "Dhelp parser to add/remove/reindex dhelp files"

        option :help
        option :names => %w(-a), :arity => [0,-1],
               :opt_found => lambda {|opt, name, value| @action = :add;
                                                        @directories = value },
               :opt_description => "add dhelp file in <directories>"
        option :names => %w(-d), :arity => [0,-1],
               :opt_found => lambda {|opt, name, value| @action = :delete;
                                                        @directories = value },
               :opt_description => "del dhelp file in <directories>"
        option :names => %w(-r), :arity => [0,0],
               :opt_found => lambda { @action = :reindex },
               :opt_description => "index all dhelp files in /usr/share/doc"

        expected_args [0,0]

        @action = nil
    end


    def get_items(path)
        basePath = path.sub(Regexp.new("#{DOC_DIR_REAL}/(.+)/\\.dhelp"), '\1')
        begun, descriptionMode = false, false
        attrs = {}
        itemList = []
        File.readlines(path).each do |line|
            line.chomp!

            case line
            when /<item>/
                begun = true
                attrs = {:dtitle => '', :descrip => '', :file => ''}
            when /<directory>/
                begun or raise("Malformed file: <directory> found before <item>")
                attrs[:dir] = line.sub(/^<directory>/, '').strip
            when /<linkname>/
                begun or raise("Malformed file: <linkname> found before <item>")
                attrs[:name] = line.sub(/^<linkname>/, '').strip
            when /<filename>/
                begun or raise("Malformed file: <filename> found before <item>")
                attrs[:file] = File.join(basePath, line.sub(/^<filename>/, '').strip)
            when /<dirtitle>/
                begun or raise("Malformed file: <dirtitle> found before <item>")
                attrs[:dtitle] = line.sub(/^<dirtitle>/, '').strip
            when /<description>/
                descriptionMode = true
            when /<\/description>/
                descriptionMode = false
            when /<\/item>/
                itemList << ItemData.new(attrs)
            else
                if descriptionMode
                    attrs[:descrip] ||= ''
                    attrs[:descrip] += line + "\n"
                end
            end
        end
        itemList
    end


    def dhelp_add(entry, db, titleDb)
        each_item(entry) do |entryData|
            if entryData.file.to_s   != '' then db.write(entryData)      end
            if entryData.dtitle.to_s != '' then titleDb.write(entryData) end
        end
    end


    def dhelp_del(entry, db, titleDb)
        each_item(entry) do |entryData|
            db.del(entryData)
        end
    end


    # Executes the block for each data item in the specified .dhelp file,
    # passing an ItemData object
    def each_item(path)
        begin
           entry = Pathname.new(path).realpath.to_s
        rescue Errno::ENOENT => e
           raise InvalidDhelpFileError, "#{path}: no such file or directory"
        end
        if entry =~ DHELP_FILE_REGEXP
            get_items(entry).each do |entryData|
                yield entryData
            end
        else
            raise InvalidDhelpFileError, "Dhelp files must reside in #{DOC_DIR_REAL} and be named .dhelp"
        end
    end


    def dhelp_add_rec(dir, db, titleDb)
        Dir.foreach(dir) do |entry|
            entryPath = File.join(dir, entry)
            case entry
            when '.', '..'
                # Ignore
            when '.dhelp'
                dhelp_add(entryPath, db, titleDb)
            else
                if FileTest.directory? entryPath and not FileTest.symlink? entryPath
                    dhelp_add_rec(entryPath, db, titleDb)
                end
            end
        end
    end


    def html_w_item_links(outputDir, category, itemList, titleDb)
        categoryDir = File.join(outputDir, category)
        FileUtils.mkdir_p(categoryDir, :mode => 0755)
        # Need to change the current directory, because the ERB template needs
        # it
        origDir = FileUtils.pwd
        FileUtils.chdir(categoryDir)

        # Variables for the template
        @category      = category
        @itemList      = itemList
        @docPathPrefix = "../" *
                         # also count "HTML/"
                         (category.split('/').size + 1)
        @categoryTitle = titleDb.title_for(category)
        # Set-up gettext environment
        GetText.bindtextdomain('dhelp')
        # Get HTML for the given category
        tmpl = ERB.new(File.read(DEFAULT_CATEGORY_TEMPLATE))
        html = tmpl.result(binding)

        File.open("index.html", "w") do |f|
            f.print html
        end
	File.chmod 0644, 'index.html'

        # Set the cwd back
        FileUtils.chdir(origDir)
    end

    def html_write(outputDir = INDEX_ROOT)
        db = Database.open
        titleDb = TitleDatabase.open

        # Re-create output directory
        FileUtils.rm_r(outputDir)
        FileUtils.mkdir_p(outputDir, :mode => 0755)
        FileUtils.chdir(outputDir)

        # Create README file
        File.open("README", "w") do |f|
            f.puts <<EOREADME
Don't put files in this directory!
dhelp will delete *all* files in this directory when creating a new index.

Marco Budde (budde@debian.org)
EOREADME
        end

        # Write category pages, and collect the categories for the index
        indexCategories = []
        db.each_category do |category, itemList|
            html_w_item_links(outputDir, category, itemList, titleDb)
            indexCategories << category
        end
        # Remove subcategories *if* the parent is already there
        indexCategories.delete_if {|c| indexCategories.include? c.sub(/\/?[^\/]+$/, '') }.sort!
        # @categories is the variable for the template
        @categories = indexCategories

        # Create general index file, now that the categories are already there
        tmpl = ERB.new(File.read(DEFAULT_INDEX_TEMPLATE))
        html = tmpl.result(binding)
        File.open("index.html", "w") do |f|
            f.print html
        end

        titleDb.close
        db.close
    rescue Errno::EACCES => e
        $stderr.puts "Don't have permissions to regenerate the HTML help"
        exit 1
    end


    def main
        case @action
        when :add
            db = Database.open("a")
            titleDb = TitleDatabase.open("a")
            @directories.each do |arg|
                dhelp_add(File.join(arg, ".dhelp"), db, titleDb)
            end
            titleDb.close
            db.close
        when :delete
            db = Database.open("a")
            titleDb = TitleDatabase.open("a")
            @directories.each do |arg|
                dhelp_del(File.join(arg, ".dhelp"), db, titleDb)
            end
            titleDb.close
            db.close
        when :reindex
            # Delete and re-create both databases
            db = Database.open(BDB::CREATE|BDB::TRUNCATE)
            titleDb = TitleDatabase.open(BDB::CREATE|BDB::TRUNCATE)
            dhelp_add_rec(DOC_DIR_REAL, db, titleDb)
            titleDb.close
            db.close
        else
            $stderr.puts man
            return 1
        end

        # Always executed
        html_write
    rescue => e
        puts "#{e.class}: #{e} (#{e.backtrace.join("\n")})"
    end
end
