diff --git a/.gitignore b/.gitignore index 5d91ad6e..2646fb98 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ /info/Transportation.t /info/Updates.t /info/TOP.t +/info/stamp-subj # $(distclean) /config.h /config.log diff --git a/Make.mk b/Make.mk index 6b14d04e..d34ad884 100644 --- a/Make.mk +++ b/Make.mk @@ -130,7 +130,7 @@ ttop := info/TOP.t # Formatted info: info.nr := $(addprefix info.nr/, $(info)) info.html := $(addprefix info.html/, $(addsuffix .html, $(info))) -info.all := $(info.nr) $(info.html) info.ps +info.all := $(info.nr) $(info.html) info.ps info/stamp-subj # Tests # sandbox @@ -322,16 +322,19 @@ $(libs) $(empth_lib): # Info formatting -# FIXME Remaking subjects doesn't work correctly when info sources get -# removed or subjects get dropped. +# mksubj.pl reads $(tsrc) and writes subjects.mk $(tsubj). The naive +# rule +# subjects.mk $(ttop) $(tsubj): $(tsrc) +# COMMAND +# runs COMMAND once for each target. That's because multiple targets +# in an explicit rule is just a shorthand for one rule per target, +# each with the same prerequisites and commands. Use a stamp file. +subjects.mk $(tsubj): info/stamp-subj ; +info/stamp-subj: info/mksubj.pl $(tsrc) + $(call quiet-command,perl $(srcdir)/info/mksubj.pl $(filter %.t, $^),GEN '$$(subjects)') + >$@ -subjects.mk: info/findsubj.pl $(tsrc) - perl $(srcdir)/info/findsubj.pl $(filter %.t, $^) - -$(tsubj): info/mksubj.pl - perl $(srcdir)/info/mksubj.pl $@ $(filter %.t, $^) - -$(ttop): info/mktop.pl +$(ttop): info/mktop.pl $(tsrc) $(call quiet-command,perl $(srcdir)/info/mktop.pl $@ $(subjects),GEN $@) info.nr/all: $(filter-out info.nr/all, $(info.nr)) diff --git a/info/findsubj.pl b/info/findsubj.pl deleted file mode 100644 index 66275189..00000000 --- a/info/findsubj.pl +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/perl -# -# Empire - A multi-player, client/server Internet based war game. -# Copyright (C) 1986-2013, Dave Pare, Jeff Bailey, Thomas Ruschak, -# Ken Stevens, Steve McClure, Markus Armbruster -# -# Empire 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 3 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, see . -# -# --- -# -# See files README, COPYING and CREDITS in the root of the source -# tree for related information and legal notices. It is expected -# that future projects/authors will amend these files as needed. -# -# --- -# -# findsubj.pl: Find info subjects, update subjects.mk -# -# Known contributors to this file: -# Ken Stevens (when it was still info.pl) -# Markus Armbruster, 2006-2008 -# - -# Usage: findsubj.pl INFO-FILE... -# Run it at the root of the build tree. This updates the make include -# file subjects.mk, which guides the remaking of info index files. -# -# --- Global variables --- -# @Subjects Existing subjects -# $filename The name of the current info file -# $filename{TOPIC} -# TOPIC's info file name -# $chapter{TOPIC} TOPIC's chapter (first arg to .TH) -# $see_also{TOPIC} -# TOPIC's SEE ALSO items (.SA argument) -# $sanr{TOPIC} Line number of TOPIC's .SA request -# $subjfil{SUBJECT} -# info files for SUBJECT separated by space -# -# --- File handles --- -# F Filehandle for info page sources and makefiles -# -# --- Functions --- -# -# read_make_var Read a variable value from a makefile -# parse_file Read an info file -# parse_see_also Create %subjfil from %see_also -# error Print an integrity error to STDERR and exit with code 1. - -use strict; -use warnings; - -use Errno qw(ENOENT); - -my (%filename, %chapter, %see_also, %sanr); -my ($filename, %subjfil); - -# Get known subjects -my @Subjects = split(' ', read_make_var("subjects", "subjects.mk", "")); - -# Parse the .t files -for my $f (@ARGV) { - parse_file("$f"); -} - -# Create %subjfil from %see_also -for my $t (sort keys %see_also) { - parse_see_also($t); -} - -# Update @Subjects from %subjfil -for my $t (@Subjects) { - print STDERR "WARNING: The subject $t has been removed.\n" - unless exists $subjfil{$t}; -} -for my $t (keys %subjfil) { - unless (grep(/^$t$/, @Subjects)) { - print STDERR "WARNING: $t is a NEW subject\n"; - my $fname = "info/$t.t"; - if (-e $fname) { - print STDERR "File $fname exists\n"; - exit 1; - } - } -} -@Subjects = sort keys %subjfil; - -# Update subjects.mk -open(F, ">subjects.mk") - or die "Can't open subjects.mk for writing: $!"; -print F "# DO NOT EDIT THIS FILE. It was automatically generated by findsubj.pl\n"; -print F "subjects := " . join(' ', @Subjects) . "\n"; -for my $t (@Subjects) { - print F "info/$t.t:$subjfil{$t}\n"; -} -close(F); - -exit 0; - -# Read a variable value from a makefile -sub read_make_var { - my ($var, $fname, $dflt) = @_; - my $val; - - unless (open(F, "<$fname")) { - return $dflt if $! == ENOENT and defined $dflt; - die "Can't open $fname: $!"; - } - while () { - if (/^[ \t]*\Q$var\E[ \t]:?=*(.*)/) { - $val = $1; - last; - } - } - close(F); - $val or die "Can't find $var in $fname"; - return $val; -} - -# Read an info file -# Parse .TH into %chapter and .SA into %see_also, %sanr -sub parse_file { - ($filename) = @_; - my $topic; - - $topic = $filename; - $topic =~ s,.*/([^/]*)\.t$,$1,; - $filename{$topic} = $filename; - - open(F, "<$filename") - or die "Can't open $filename: $!"; - - $_ = ; - if (/^\.TH (\S+) (\S.+\S)$/) { - $chapter{$topic} = $1; - } else { - error("The first line in the file must be a .TH request"); - } - - while () { - last if /^\.SA/; - } - - if ($_) { - if (/^\.SA "([^\"]*)"/) { - $see_also{$topic} = $1; - $sanr{$topic} = $.; - } else { - error("Incorrect .SA Syntax. Syntax should be '.SA \"item1, item2\"'"); - } - - while () { - error("Multiple .SA requests. Each file may contain at most one.") if /^\.SA/; - } - } else { - error(".SA request is missing"); - } - - close F; -} - -# Create %subjfil from %see_also -sub parse_see_also { - my ($topic) = @_; - my @see_also = split(/, /, $see_also{$topic}); - my $wanted = $chapter{$topic}; - my $found; # found a subject? - - $wanted = undef if $wanted eq 'Concept' or $wanted eq 'Command'; - $filename = $filename{$topic}; - - for (@see_also) { - if (!exists $see_also{$_}) { # is this entry a subject? - $subjfil{$_} .= " info/$topic.t"; - $found = 1; - } - if ($wanted && $_ eq $wanted) { - $wanted = undef; - } - } - - $. = $sanr{$topic}; - error("No subject listed in .SA") unless $found; - error("Chapter $wanted not listed in .SA") if $wanted; -} - -# Print an integrity error message and exit with code 1 -sub error { - my ($error) = @_; - - print STDERR "findsubj.pl:$filename:$.: $error\n"; - exit 1; -} diff --git a/info/mksubj.pl b/info/mksubj.pl index 05d846ac..48e589e0 100644 --- a/info/mksubj.pl +++ b/info/mksubj.pl @@ -25,98 +25,108 @@ # # --- # -# mksubj.pl: Create the index for a subject +# mksubj.pl: Create the subject index pages # # Known contributors to this file: # Ken Stevens (when it was still info.pl) -# Markus Armbruster, 2006 +# Markus Armbruster, 2006-2013 # - -# Usage: mksubj.pl OUTFILE INFILE... -# The INFILE... contain all the topics belonging to a subject. Read -# and check the information required for the index from them, write -# the index to OUTFILE. +# Usage: mksubj.pl INFO-FILE... +# +# Read the INFO-FILE..., read and update subjects.mk, create +# info/SUBJECT.t for each SUBJECT. use strict; use warnings; use File::stat; +use Errno qw(ENOENT); +use Fcntl qw(O_WRONLY O_EXCL O_CREAT); + # The chapters, in order my @Chapters = qw/Introduction Concept Command Server/; my @Levels = qw/Basic Expert Obsolete/; +my @Subjects; +# $filename{TOPIC} is TOPIC's file name +my %filename; +# $long{TOPIC} is true when TOPIC's page is "long" +my %long; +# $chapter{TOPIC} is TOPIC's chapter (first arg to .TH) +my %chapter; +# $desc{TOPIC} is a one line description of TOPIC (second arg to .NA) +my %desc; +# $level{TOPIC} is TOPIC's difficulty level (arg to .LV) +my %level; +# $see_also{TOPIC} is TOPIC's list of SEE ALSO items (.SA argument) +my %see_also; +# $sanr{TOPIC} is the line number of TOPIC's .SA request +my %sanr; + +# current info file my $filename; -my (%subject, %level, %desc, %long, %cnt); -my $largest = ""; -my $out = shift @ARGV; -$out =~ /([^\/]*)\.t$/ - or die "Strange subject file name $out"; -my $subj = $1; +# $subject{$subj}{$chap} = "item1\nitem2\n..." +# Topics in that subject organized by chapter. +my %subject; +# $largest{$sub} The largest topic name in that subject (used for +# column formatting) +my %largest; + +@Subjects = split(' ', read_make_var("subjects", "subjects.mk", "")); for (@ARGV) { - my ($topic, $chap, $lvl, $desc, $long) = parse_file($_); - $largest = $topic if length $topic > length $largest; - $subject{$chap} .= "$topic\n"; - $level{$topic} = $lvl; - $cnt{$lvl}++; - $desc{$topic} = $desc; - $long{$topic} = $long; - $cnt{'long'}++ if $long; + parse_file($_); } -open(SUBJ, ">$out") - or die "Can't open $out for writing: $!"; +for my $t (sort keys %desc) { + parse_see_also($t); +} -print SUBJ '.\" DO NOT EDIT THIS FILE. It was automatically generated by mksubj.pl'."\n"; -print SUBJ ".TH Subject \U$subj\n"; -$largest =~ s/-/M/g; -print SUBJ ".in \\w'$largest", "XX\\0\\0\\0\\0'u\n"; +@Subjects = create_subjects(); -for my $chap (@Chapters) { - next unless exists $subject{$chap}; - print SUBJ ".s1\n"; - for (split(/\n/, $subject{$chap})) { - my $flags = ""; - $flags .= "*" if $level{$_} eq 'Basic'; - $flags .= "+" if $level{$_} eq 'Obsolete'; - $flags .= "!" if $long{$_}; - $flags = sprintf("%-2s", $flags); - print SUBJ ".L \"$_ $flags\"\n"; - print SUBJ "$desc{$_}\n"; +open(F, ">subjects.mk") + or die "Can't open subjects.mk for writing: $!"; +print F "subjects := " . join(' ', @Subjects) . "\n"; +close(F); + +exit 0; + +# Read a variable value from a makefile +sub read_make_var { + my ($var, $fname, $dflt) = @_; + my $val; + + unless (open(F, "<$fname")) { + return $dflt if $! == ENOENT and defined $dflt; + die "Can't open $fname: $!"; } + while () { + if (/^[ \t]*\Q$var\E[ \t]*:?=[ \t]*(.*)/) { + $val = $1; + last; + } + } + close(F); + defined($val) or die "Can't find $var in $fname"; + return $val; } -print SUBJ <" where is -one of the subjects listed above. -EOF -print SUBJ <size > 9999; + $long{$topic} = $st->size > 9999; open(F, "<$filename") or die "Can't open $filename: $!"; @@ -126,7 +136,7 @@ sub parse_file { if (!grep(/^$1$/, @Chapters)) { error("First argument to .TH was '$1', which is not a known chapter"); } - $chap = $1; + $chapter{$topic} = $1; if ($1 eq "Command" && $2 ne "\U$topic") { error("Second argument to .TH was '$2' but it should be '\U$topic'"); } @@ -139,7 +149,7 @@ sub parse_file { if ($topic ne $1) { error("First argument to .NA was '$1' but it should be '$topic'"); } - $desc = $2; + $desc{$topic} = $2; } else { error("The second line in the file must be a .NA request"); } @@ -149,14 +159,136 @@ sub parse_file { if (!grep(/^$1$/, @Levels)) { error("The argument to .LV was '$1', which is not a known level"); } - $lvl = $1; + $level{$topic} = $1; } else { error("The third line in the file must be a .LV request"); } - close F; + while () { + last if /^\.SA/; + } - return ($topic, $chap, $lvl, $desc, $long); + if ($_) { + if (/^\.SA "([^\"]*)"/) { + $see_also{$topic} = $1; + $sanr{$topic} = $.; + } else { + error("Incorrect .SA Syntax. Syntax should be '.SA \"item1, item2\"'"); + } + + while () { + error("Multiple .SA requests. Each file may contain at most one.") if /^\.SA/; + } + } else { + error(".SA request is missing"); + } + + close F; +} + +# Create %subject and %largest from %see_also +sub parse_see_also { + my ($topic) = @_; + my @see_also = split(/, /, $see_also{$topic}); + my $wanted = $chapter{$topic}; + my $found; # found a subject? + + $wanted = undef if $wanted eq 'Concept' or $wanted eq 'Command'; + + for (@see_also) { + if (!exists $desc{$_}) { # is this entry a subject? + set_subject($_, $topic); + $found = 1; + } + if ($wanted && $_ eq $wanted) { + $wanted = undef; + } + } + + $filename = $filename{$topic}; + $. = $sanr{$topic}; + error("No subject listed in .SA") unless $found; + error("Chapter $wanted not listed in .SA") if $wanted; +} + +# Add a new entry to %subject and possibly to %largest +sub set_subject { + my ($sub, $topic) = @_; + my $chap = $chapter{$topic}; + $subject{$sub}{$chap} .= "$topic\n"; + $largest{$sub} = "" unless defined $largest{$_}; + $largest{$sub} = $topic if length $topic > length $largest{$sub}; +} + +# Create a Subject.t file +sub create_subj { + my ($subj) = @_; + my $fname = "info/$subj.t"; + my ($any_basic, $any_obsolete, $any_long); + + print "WARNING: $subj is a NEW subject\n" + unless grep(/^$subj$/, @Subjects); + sysopen(SUBJ, $fname, O_WRONLY | O_EXCL | O_CREAT) + or die "Unable to create $fname: $!\n"; + + print SUBJ '.\" DO NOT EDIT THIS FILE. It was automatically generated by mksubj.pl'."\n"; + print SUBJ ".TH Subject \U$subj\n"; + $largest{$subj} =~ s/-/M/g; + print SUBJ ".in \\w'$largest{$subj}XX\\0\\0\\0\\0'u\n"; + for my $chap (@Chapters) { + next unless exists $subject{$subj}{$chap}; + print SUBJ ".s1\n"; + for my $topic (split(/\n/, $subject{$subj}{$chap})) { + my $flags = ""; + if ($level{$topic} eq 'Basic') { + $flags .= "*"; + $any_basic = 1; + } + if ($level{$topic} eq 'Obsolete') { + $flags .= "+"; + $any_obsolete = 1; + } + if ($long{$topic}) { + $flags .= "!"; + $any_long = 1; + } + $flags = sprintf("%-2s", $flags); + print SUBJ ".L \"$topic $flags\"\n"; + print SUBJ "$desc{$topic}\n"; + } + } + print SUBJ ".s1\n" + . ".in 0\n" + . "For info on a particular subject, type \"info \" where is\n" + . "one of the subjects listed above.\n"; + print SUBJ "Subjects marked by * are the most important and should be read by new players.\n" + if $any_basic; + print SUBJ "Subjects marked by + are obsolete.\n" + if $any_obsolete; + print SUBJ "Unusually long subjects are marked with a !.\n" + if $any_long; + close SUBJ; +} + +# Remove the old Subject.t files and create new ones +sub create_subjects { + my (@subj); + + for (@Subjects) { + unlink "info/$_.t"; + } + + @subj = sort keys %subject; + + for my $subj (@Subjects) { + print "WARNING: The subject $subj has been removed.\n" + unless grep (/^$subj$/, @subj); + } + + for my $subj (@subj) { + create_subj($subj); + } + return @subj; } # Print an integrity error message and exit with code 1