#!/usr/bin/perl
#****************************************************************
# $Id: ChmDoc.pl,v 1.2 2006-08-11 06:16:56 dale Exp $
#****************************************************************
# Requires the following environment variables to be set:
# CHM_API_FILE_PREFIX
# HELP_COMPILER
#
use strict;
use locale;
# Declare constants
my $usr_short_name = 'DKLang API';
my $src_path = 'C:/Delphi/CVSpro~1/dale/DKLang';
my $out_path = 'C:/Delphi/CVSpro~1/dale/DKLang/Help';
my $file_prefix = $ENV{'CHM_API_FILE_PREFIX'};
my $css_file = 'main.css';
my $toc_file = $file_prefix.'index.html';
my $typeidx_file = $file_prefix.'types.html';
my $source_hhp_file = 'dklang.hhp';
my $hhp_file = $file_prefix.'project.hhp';
my $source_hhc_file = 'dklang.hhc';
my $hhc_file = $file_prefix.'contents.hhc';
my $hhk_file = $file_prefix.'keywords.hhk';
my $hh_compiler = $ENV{'HELP_COMPILER'};
my $insert_contents_after = 'DKLang API Reference'; # Раздел, после которого всовывать пункты в файл оглавления
# Validate external variables
die("ERROR: CHM_API_FILE_PREFIX environment variable should be set.\n") unless ($file_prefix);
die("ERROR: HELP_COMPILER environment variable should be set.\n") unless ($hh_compiler);
my %units; # Модули -> Объекты -> Атрибуты объектов
my %allobjs; # Список ссылок на все объекты
my @htmlfiles; # Полный список HTML-файлов
# Типы
my @types = (
{ CHAR => 'I',
NAME => 'Interfaces',
TITLE => 'interface',
PRINTER => sub { qq|$_[0]->{NAME} = interface($_[0]->{DECL})|; }
},
{ CHAR => 'C',
NAME => 'Classes',
TITLE => 'class',
PRINTER => sub { qq|$_[0]->{NAME} = class($_[0]->{DECL})|; }
},
{ CHAR => 'R',
NAME => 'Records',
TITLE => 'record',
PRINTER => sub { qq|$_[0]->{NAME} = $_[0]->{DECL}|; }
},
{ CHAR => 'P',
NAME => 'Pointers',
TITLE => 'pointer',
PRINTER => sub { qq|$_[0]->{NAME} = ^$_[0]->{DECL}|; }
},
{ CHAR => 'E',
NAME => 'Enumerations',
TITLE => 'enumeration',
PRINTER => sub { qq|$_[0]->{NAME} = $_[0]->{DECL}|; }
}
);
# Генерируем хэш ссылок на типы по typechar
my %typebychar;
foreach(@types) { $typebychar{$_->{CHAR}} = $_; }
# Генерируем список ключевых слов Delphi
my %keywords;
foreach(
split ' ',
'and array as asm at automated begin case class const constructor destructor dispinterface div '.
'do downto else end except exports file finalization finally for function goto if implementation '.
'in inherited initialization inline interface is label library mod nil not object of on or out '.
'packed private procedure program property protected public published raise read record repeat '.
'resourcestring set shl shr string then threadvar to try type unit until uses var while with write xor'
) { $keywords{$_} = 1; }
# Обрабатываем файлы
foreach(glob "$src_path/*.pas") { processFileCallback($_); }
if (%units){
# Создаём выходной каталог
mkdir $out_path, 0777;
# Генерируем файлы
print "Writing HTML files...\n";
writeHTML();
# Выводим индекс типов
print "Writing type index...\n";
writeTypeIndex();
# Создаём проект HTML Help
print "Writing HTML Help Project...\n";
writeHHP();
print "Writing HTML Help Project Contents...\n";
writeHHC();
print "Writing HTML Help Project Keywords...\n";
writeHHK();
print "Generating HTML Help...\n";
my $dos_hhp = "$out_path/$hhp_file";
$dos_hhp =~ s|/|\\|g;
print `"$hh_compiler" $dos_hhp`;
# Стираем сгенерированные файлы
print "Removing generated files...\n";
my $dos_remove_pattern = "$out_path/$file_prefix*.*";
$dos_remove_pattern =~ s|/|\\|g;
`del $dos_remove_pattern`;
print "Done\n";
exit 0;
} else {
print "No files found\n";
exit 1;
}
######################################################################################################################
sub hiliteKeyword {
my ($word, $pre) = @_;
if ($pre !~ /.*<[^>]*$/) {
if ($keywords{lc($word)}) {
$word = "$word";
} elsif (my $refobj = $allobjs{$word}) {
$word = qq|$word|;
}
}
return $word;
}
sub hiliteSymbol {
my ($sym, $pre) = @_;
return ($pre !~ /.*<[^>]*$/)?"$sym":$sym;
}
# Расцвечивает синтаксис: (String): String
sub highlight($) {
my $str = shift;
$str =~ s|(\w+)|hiliteKeyword($1, $`)|ge; # ключевые слова
$str =~ s|([^\w<> ]+)|hiliteSymbol($1, $`)|ge; # символы
return $str;
}
# Регистрирует объект: (RefUnit, ObjName, ObjTypeChar, ObjDecl, ObjComment)
sub regObj {
my $refobj = {
REFUNIT => $_[0],
NAME => $_[1],
HTMLFILE => "$file_prefix$_[0]->{NAME}-$_[1].html",
TYPE => $_[2],
DECL => $_[3],
COMMENT => $_[4],
ATTRS => {}
};
$_[0]->{OBJECTS}->{$_[1]} = $refobj;
$allobjs{$_[1]} = $refobj;
return $refobj;
}
# Callback-процедура, получающая имя файла. Обрабатывает исходный файл, разбирая код. Данные заносит в %units
sub processFileCallback {
if (-s && /\.pas$/i) {
my $file = shift; #$File::Find::name;
# Parse the input file
open(FI, $file) or die "Cannot open $file for reading: $!";
my $intf_clause = 0;
my $type_clause = 0;
my $header_processed = 0;
my $comment = '';
my $refunit;
my $refobject;
while(){
chomp;
# Если строка содержит только комментарий, запоминаем его
if (m|^\s*//\s*(?:--)?\s*(.*[A-Za-zА-я].*)| && !m(\$Id:|///|Props|Prop handlers|Prop storage|Message handlers|Events)i) {
$comment .= ($comment?' ':'').$1;
# Заголовок модуля: unit XXXXX;
} elsif (/^\s*unit\s+(\w+)\;/i) {
$refunit = {
NAME => $1,
HTMLFILE => $file_prefix."unit-$1.html",
SIZE => -s $file,
COMMENT => $comment,
OBJECTS => {}
};
$units{$1} = $refunit;
# 'interface' section
} elsif ($refunit && /^\s*interface\s*$/i) {
$intf_clause = 1;
# 'type' section
} elsif ($intf_clause && /^\s*type\s*$/i) {
$type_clause = 1;
# end of 'type' section
} elsif ($type_clause && /^\s*(?:const|var|resourcestring|threadvar)\s*$/i) {
$type_clause = 0;
# 'implementation' section
} elsif (/^\s*implementation\s*$/i) {
last;
# Декларация объекта
} elsif ($type_clause && /\b(\w+)\s*=\s*(class|interface)\(\s*([\w, ]+)\s*\)/i) {
$refobject = regObj($refunit, $1, uc(substr($2, 0, 1)), $3, $comment);
$comment = '';
# Декларация записи
} elsif ($type_clause && /\b(\w+)\s*=\s*((?:packed\s*)?record)/i) {
$refobject = regObj($refunit, $1, 'R', $2, $comment);
$comment = '';
# Декларация указателя
} elsif ($type_clause && /\b(\w+)\s*=\s*\^\s*(\w+)/) {
$refobject = regObj($refunit, $1, 'P', $2, $comment);
$comment = '';
# Декларация перечисления
} elsif ($type_clause && /\b(\w+)\s*=\s*(\([\s\w.,]+\))/) {
$refobject = regObj($refunit, $1, 'E', $2, $comment);
$comment = '';
# Атрибут объекта
} elsif ($refobject && /^\s*(property|function|procedure)\s*(\w+)\s*(.*;)/i) {
$refobject->{ATTRS}->{$2} = {
NAME => $2,
KIND => $1,
DECL => $3,
COMMENT => $comment
};
$comment = '';
# Поле записи
} elsif ($refobject && $refobject->{TYPE} eq 'R' && /^\s*(\w+)\s*:\s*(\w+)\s*;\s*(?:\/\/\s*)?(.*)/) {
$refobject->{ATTRS}->{$1} = {
NAME => $1,
KIND => '',
DECL => ': '.$2,
COMMENT => $3
};
$comment = '';
# Конец описания объекта
} elsif ($refobject && /\bend\s*;/) {
undef $refobject;
$comment = '';
} elsif (/\w+/ && !/^\s*type\s*$/i) {
$comment = '';
}
}
close(FI);
}
}
# Пишет завершение HTML-файла (FileHandle)
sub writeFileFooter($) {
my $fh = shift;
print $fh <