1
0
mirror of https://github.com/gryf/wmaker.git synced 2025-12-19 12:28:22 +01:00
Files
wmaker/script/generate-txt-from-texi.sh
Christophe CURIS 58854cab9b txt-from-texi: add support for variables
The support for variables in the text-from-texi script is used to
automatically insert the current version of Window Maker, inherited from
the definition in the AC_INIT command, so it will be always correctly
updated without the need for hand work.

Having this version information is recommended by GNU texinfo because if
the document happens to get distributed outside a package it can help user
avoid problems.

The second use is to define a variable 'emailsupport' which contains the
email address of the development team, so we can also inherit it from the
AC_INIT setting.

The third use is for a special variable 'cctexi2txt' to differentiate
between texi2any and our script, because when using texi2any the title
page is not generated, so we need to duplicate some information in the
source, but we do not want it to appear twice in our generated doc.

Signed-off-by: Christophe CURIS <christophe.curis@free.fr>
2015-01-20 21:29:24 +00:00

1233 lines
35 KiB
Bash
Executable File

#!/bin/sh
###########################################################################
#
# Window Maker window manager
#
# Copyright (c) 2014-2015 Christophe CURIS
# Copyright (c) 2015 Window Maker Team
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
###########################################################################
#
# generate-txt-from-texi.sh:
# generate a plain text file from a texinfo documentation
#
# The goal is to achieve a result similar to:
# texi2any --plaintext --no-split <filename>
#
# The reason for this script is that we do not want to add a strict
# dependancy on the 'makeinfo' tool suite (which has its own dependancies)
#
# There is also the problem that we use is to generate some documentations
# that should be available before the 'configure' script have been run
# (in the 'autogen.sh' script) because some of these documentation provide
# information for running the 'configure' script; We distribute these
# generated docs so normal user won't have the problem, but people that
# want to build from the Git repository.
#
# This script is not a reference for Texinfo syntax, so if you modified the
# texi source, you should really consider running texi2any at least once to
# make sure everything is still ok.
#
###########################################################################
#
# Despite trying to be close to the texi2any output, this script has a few
# known differences:
#
# - texi2any does not generate a proper title header, it satisfy itself
# with a rudimentary single line; this script generates a better looking
# header with all the information provided
#
# - the table of content contains the line number for the section to ease
# navigation contrary to texi2any that satisfy itself with a simplist toc
#
# - the paragraphs are properly justified, contrary to texi2any which only
# flush left them in text outputs
#
# - There are 2 blank lines instead of 1 before chapter/section to make
# them stand out more
#
# - the line length is set to 76 instead of 72
#
# - there are some difference in what characters are added when a style is
# used for a string (@emph, @file, @sc, ...) because it assumes the text
# will be read in a plain text tool with no special "smart" highlighting
#
# - it does not check that the syntax is valid; there are some simple
# checks but it may misbehave, it does not replace a quality check with
# texi2any
#
# - not all commands are implemented, some because they were not needed
# so far, probably a few because they would be too complex to implement
#
###########################################################################
#
# Please note that this script is writen in sh+awk on purpose: this script
# is gonna be run on the machine of the person who is trying to compile
# WindowMaker, and as such we cannot be sure to find any scripting language
# in a known version and that works (python/ruby/tcl/perl/php/you-name-it).
#
# So for portability, we stick to the same sh+awk constraint as Autotools
# to limit the problem, see for example:
# http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
#
###########################################################################
# Report an error on stderr and exit with status 1 to tell make could not work
arg_error() {
echo "$0: $@" >&2
exit 1
}
# print help and exit with success status
print_help() {
echo "$0: convert a Texinfo file into a plain text file"
echo "Usage: $0 [options...] file.texi"
echo "valid options are:"
echo " -Dvar=val : set variable 'var' to value 'val'"
echo " -e email : set email address in variable 'emailsupport'"
echo " -v version : version of the project"
echo " -o file : name of text file to create"
exit 0
}
# Extract command line arguments
while [ $# -gt 0 ]; do
case $1 in
-D*)
echo "$1" | grep '^-D[a-zA-Z][a-zA-Z]*=' > /dev/null || arg_error "syntax error for '$1', expected -Dname=value"
var_defs="$var_defs
`echo "$1" | sed -e 's/^-D/ variable["/ ; s/=/"] = "/ ; s/$/";/' `"
;;
-e)
shift
var_defs="$var_defs
variable[\"emailsupport\"] = \"@email{`echo "$1" | sed -e 's/@/@@/g' `}\";"
;;
-h|-help|--help) print_help ;;
-o)
shift
output_file="$1"
;;
-v)
shift
project_version="$1"
;;
-*) arg_error "unknow option '$1'" ;;
*)
[ "x$input_file" = "x" ] || arg_error "only 1 input file can be specified, not \"$input_file\" and \"$1\""
input_file="$1"
;;
esac
shift
done
# Check consistency of command-line
[ "x$input_file" != "x" ] || arg_error "no input texi file given"
[ "x$output_file" != "x" ] || arg_error "no output file given"
###########################################################################
# The script works in 2 passes, in the first pass we generate an almost
# complete text file in the temporary file $temp_file, it also generates
# the table of content in $toc_file as a sed script.
# The second pass generate the $output_file from that $temp_file and the
# $toc_file
###########################################################################
# Create the temp file in the current directory
temp_file="`echo "$input_file" | sed -e 's,^.*/\([^/]*\)$,\1, ; s,\.[^.]*$,,' `.tmp"
toc_file="`echo "$temp_file" | sed -e 's,\.[^.]*$,,' `.toc"
# Run awk for 1st pass, but if it fails stop now without deleting temp files
awk '
# Stop processing everything, print the message for user and return error code
# to tell "make" to not go further
function report_error(message) {
print "Error: " message > "/dev/stderr";
# When we call "exit", the block "END" is still called, we falsely set
# this variable to skip a spurious second error message
bye_marker_found = 1;
# Code 1 is used when the script is invoked with incorrect arguments
# Code 2 is used by awk to report problems
exit 3;
}
# Conditionals for @ifXXX and @ifnotXXX commands
# stored in a stack to allow embedding conditionals inside other conditionals
# the global variable "cond_state" contains the current condition (0 or 1)
function start_conditional(name, value, local_i) {
cond_level++;
cond_names[cond_level] = name;
cond_value[cond_level] = value;
cond_state = value;
for (local_i = 1; local_i < cond_level; local_i++) {
cond_state = cond_state && cond_value[local_i];
}
}
function end_conditional(name, local_i) {
cond_level--;
cond_state = 1;
for (local_i = 1; local_i < cond_level; local_i++) {
cond_state = cond_state && cond_value[local_i];
}
}
# Texinfo Variables
# the texinfo standard allows to have variables set with @set and used
# with @value; they can also be defined from command-line (-D)
# they are stored in the global array "variable[name]"
function set_variable(line, local_split, local_idx) {
local_idx = match(line, /^([^ \t]+)([ \t]*)(.*)$/, local_split);
if (local_idx > 0) {
variable[ local_split[1] ] = local_split[3];
}
}
# Write a single line to the output
function write_line(line) {
if (!cond_state) { return; }
switch (redirect_out) {
case "no":
print line;
line_number++;
break;
case "copyright":
copyright_lines[copyright_count++] = line;
break;
default:
report_error("redirect output mode \"" redirect_out "\" is not supported (line " NR ")");
}
}
# Paragraph modes
# the current mode for paragraph handling is a Stack to allow embedding
# modes inside other modes
# the global variable "par_mode" contains the active mode
function par_mode_push(mode, local_i) {
par_mode_count++;
par_mode_save_previous[par_mode_count] = par_mode;
par_mode_save_length[par_mode_count] = line_length;
par_mode_save_prefix[par_mode_count] = line_prefix;
par_mode_save_justify[par_mode_count] = par_justify;
par_mode = mode;
# Check for quality of output
if (length(line_prefix) + 25 > line_length) {
print "Warning: too many paragraph modes imbricated at line " NR " for " mode > "/dev/stderr";
line_length = length(line_prefix) + 25;
}
}
function par_mode_pop(mode, local_i) {
if ((par_mode != mode) || (par_mode_count <= 0)) {
report_error("found @end " mode " at line " NR " but not in @" mode " (current state is @" par_mode ")");
}
par_mode = par_mode_save_previous[par_mode_count];
line_length = par_mode_save_length[par_mode_count];
line_prefix = par_mode_save_prefix[par_mode_count];
par_justify = par_mode_save_justify[par_mode_count];
par_mode_count--;
}
# Discard all the lines in the file until the specified "@end" is found on a line by itself
function discard_block(name, local_start_line) {
local_start_line = NR;
while (1) {
if (getline == 0) { report_error("end of file reached while searching \"@end " name "\", started at line " local_start_line); }
if ($0 == "@end " name) { break; }
}
}
# Title Page generation
function generate_title_page() {
if (!cond_state) { return; }
if (par_nb_words > 0) {
generate_paragraph();
write_line(gen_underline(0, 76));
}
# Title page start with 5 blank lines so the "title" coming after will
# stand out a little bit
write_line("");
write_line("");
write_line("");
par_mode_push("titlepage");
line_prefix = " ";
line_length = 76 - 4;
}
function generate_title_page_title(title, local_array, local_count, local_i) {
if (!cond_state) { return; }
if (par_mode != "titlepage") {
report_error("command @title used outside @titlepage, at line " NR);
}
generate_paragraph();
# Title deserves more space
write_line("");
write_line("");
# Split long title
if (length(title) < 60) {
local_count = 1;
local_array[1] = title;
} else {
local_count = int((length(title) + 59 ) / 60);
sub_length = int((length(title) + local_count - 1) / local_count);
local_count = 0;
while (length(title) > 0) {
if (length(title) > sub_length) {
# Cut at first space before the length
local_i = sub_length + 1;
while (local_i > 0) {
if (substr(title, local_i, 1) == " ") { break; }
local_i--;
}
if (local_i == 0) {
# Can not break first word, break at first possible place
local_i = index(title, " ");
if (local_i == 0) { local_i = length(title) + 1; }
}
} else {
local_i = length(title) + 1;
}
local_count++;
local_array[local_count] = substr(title, 1, local_i - 1);
title = substr(title, local_i + 1);
}
}
# Center the title
for (local_i = 1; local_i <= local_count; local_i++) {
write_line(gen_underline(-1, int((76 - length(local_array[local_i])) / 2)) local_array[local_i]);
}
write_line("");
write_line("");
}
function generate_title_page_subtitle(title, local_array, local_count, local_i) {
if (!cond_state) { return; }
if (par_mode != "titlepage") {
report_error("command @subtitle used outside @titlepage, at line " NR);
}
generate_paragraph();
# Split long lines
if (length(title) < 65) {
local_count = 1;
local_array[1] = title;
} else {
local_count = int((length(title) + 64) / 65);
sub_length = int((length(title) + local_count - 1) / local_count);
local_count = 0;
while (length(title) > 0) {
if (length(title) > sub_length) {
# Cut at first space before the length
local_i = sub_length + 1;
while (local_i > 0) {
if (substr(title, local_i, 1) == " ") { break; }
local_i--;
}
if (local_i == 0) {
# Can not break first word, break at first possible place
local_i = index(title, " ");
if (local_i == 0) { local_i = length(title) + 1; }
}
} else {
local_i = length(title) + 1;
}
local_count++;
local_array[local_count] = substr(title, 1, local_i - 1);
title = substr(title, local_i + 1);
}
}
# Center the title
for (local_i = 1; local_i <= local_count; local_i++) {
write_line(gen_underline(-1, int((76 - length(local_array[local_i]) - 4) / 2)) "~ " local_array[local_i] " ~");
}
}
# Generate separation line to simulate page breaks in plain text file
function generate_page_break() {
if (!cond_state) { return; }
generate_paragraph();
if (par_mode = "titlepage") {
write_line("");
}
write_line("");
write_line(gen_underline(0, 76));
par_indent = 1;
}
# Handle chapter and section declaration
# take care of the automatic numbering and to put the line in the table of
# content file, then generate the underlined line in output
function new_section(level, title, is_numbered, local_i, local_line) {
if (!cond_state) { return; }
# Dump the current paragraph now
generate_paragraph();
# Update the counters
if (is_numbered) {
section[level]++;
for (local_i = level + 1; local_i <= 4; local_i++) {
section[local_i] = 0;
}
}
# Generate the line to be displayed
if (is_numbered) {
local_line = section[1];
for (local_i = 2; local_i <= level; local_i++) {
local_line = local_line "." section[local_i];
}
local_line = local_line " " title;
} else {
local_line = title;
}
# Add the entry to the ToC
toc_count++;
toc_entry_level[toc_count] = level;
toc_entry_name[toc_count] = local_line;
for (local_i = 1; local_i < level; local_i++) {
toc_entry_name[toc_count] = " " toc_entry_name[toc_count];
}
toc_entry_line[toc_count] = line_number + 3;
# Print the section description
write_line("");
write_line("");
write_line(local_line);
write_line(gen_underline(level, length(local_line)));
par_indent = 0;
}
# List of Items
function start_item_list(mark) {
par_mode_push("list");
list_is_first_item = 1;
list_item_wants_sepline = 0;
par_indent = 1;
line_prefix = " ";
if (mark == "") {
item_list_mark = "*";
} else {
item_list_mark = execute_commands(mark);
}
write_line("");
}
# Generate Underline string with the specified length
function gen_underline(id, len, local) {
switch (id) {
case -1: local = " "; break;
case 1: local = "**********"; break;
case 2: local = "=========="; break;
case 3: local = "----------"; break;
case 4: local = ".........."; break;
default: local = "~~~~~~~~~~"; break;
}
while (length(local) < len) {
local = local local;
}
return substr(local, 1, len);
}
# Generate text for an URL link
function generate_url_reference(args, local_nb, local_arr) {
local_nb = split(args, local_arr, ",");
switch (local_nb) {
case 1:
return local_arr[1];
case 2:
return execute_commands(local_arr[2]) " (" local_arr[1] ")";
case 3:
return execute_commands(local_arr[3]);
default:
report_error("bad number of argument " local_nb " for @uref at line " NR);
}
}
# Generate a line with the name of an author
# note, we assume the name(s) always fit on a line
function generate_author_line(name, local_offset, local_attach_to_par) {
if (!cond_state) { return; }
local_attach_to_par = (par_nb_words > 0);
generate_paragraph();
switch (par_mode) {
case "titlepage":
name = "-- " name " --";
local_offset = int((76 - length(name)) / 2);
if (local_offset < 2) { local_offset = 2; }
write_line("");
write_line(gen_underline(-1, local_offset) name);
break;
case "quotation":
name = "-- " name;
local_offset = int((line_length - length(line_prefix) - length(name)) * 2/3);
if (local_offset < length(line_prefix) + 2) { local_offset = length(line_prefix) + 2; }
if (!local_attach_to_par) { write_line(""); }
write_line(line_prefix gen_underline(-1, local_offset) name);
break;
default:
report_error("command @author used in an inappropriate mode (" par_mode ") at line " NR);
}
}
# Add the specified line to the curren paragraph being built, do not print anything yet
function add_text_to_paragraph(line) {
nb = split(line, words, /[ \t]+/);
for (i = 1; i <= nb; i++) {
if (words[i] != "") {
par_word[par_nb_words++] = words[i];
}
}
}
# Print the paragraph from all the lines read so far
function generate_paragraph( local_prefix, local_line, local_length,
idx_word_start, idx_word_end, local_i) {
if (par_nb_words <= 0) { return; }
local_line = line_prefix;
switch (par_mode) {
case "list":
if (list_item_wants_sepline && !list_is_first_item) {
write_line("");
}
list_is_first_item = 0;
list_item_wants_sepline = 0;
if (!par_indent) {
local_prefix = item_list_mark " ";
while (length(local_prefix) < 5) { local_prefix = " " local_prefix; }
local_line = substr(local_line, 1, length(local_line) - 5) local_prefix;
}
break;
case "titlepage":
write_line("");
break;
case "par":
write_line("");
if (par_indent) {
local_line = local_line " ";
}
break;
case "quotation":
write_line("");
# There is no extra indentation of paragraphs in this mode
break;
default:
report_error("paragraph mode \"" par_mode "\" is not supported in generate_paragraph (line " NR ")");
}
# Split the paragraph in lines
idx_word_start = 0;
while (idx_word_start < par_nb_words) {
# First word is always printed, this makes sure that words too long for a line will
# always be printed, very likely on a line by themselfs
idx_word_end = idx_word_start;
local_length = length(local_line) + length(par_word[idx_word_start]);
idx_word_start++;
# See how many word we can fit on the line
while (idx_word_end < par_nb_words - 1) {
if (local_length + 1 + length(par_word[idx_word_end + 1]) > line_length) { break; }
idx_word_end++;
local_length = local_length + 1 + length(par_word[idx_word_end]);
}
# Put all those words on the current line with the appropriate justification
if (par_justify == "right") {
local_line = local_line gen_underline(-1, line_length - local_length) par_word[idx_word_start - 1];
while (idx_word_start <= idx_word_end) {
local_line = local_line " " par_word[idx_word_start++];
}
} else {
if ((par_justify == "left") || (idx_word_end == par_nb_words - 1) ||
(local_length >= line_length) || (idx_word_end < idx_word_start)) {
local_line = local_line par_word[idx_word_start - 1];
while (idx_word_start <= idx_word_end) {
local_line = local_line " " par_word[idx_word_start++];
}
} else {
# We calculate the ideal size of a space (as a real number) which would
# make all the words perfectly fill the line, the formula being
# ideal size = 1 + needed_extra_spaces / number_of_spaces_in_line
ideal_space_length = 1 + (line_length - local_length) / (idx_word_end - idx_word_start + 1);
count_spaces = 0;
for (local_i = idx_word_start; local_i <= idx_word_end; local_i++) {
count_spaces = count_spaces + ideal_space_length;
word_space[local_i] = gen_underline(-1, int(count_spaces + 0.5));
count_spaces = count_spaces - length(word_space[local_i]);
}
local_line = local_line par_word[idx_word_start - 1];
while (idx_word_start <= idx_word_end) {
local_line = local_line word_space[idx_word_start] par_word[idx_word_start++];
}
}
}
write_line(local_line);
# Reset for next line
local_line = line_prefix;
}
par_nb_words = 0;
par_indent = 1;
}
# Replace commands by text in the line, return the result
function execute_commands(line, replaced_line) {
replaced_line = "";
while (1) {
idx = match(line, /@([a-zA-Z]+|.)/, command)
if (idx == 0) { break; }
# Separate the command and its arguments from the rest of the line
replaced_line = replaced_line substr(line, 1, idx - 1);
line = substr(line, idx + 1 + length(command[1]));
if (line ~ /^\{/) {
# Command has argument(s), extract them
brace_count = 0;
for (i = 1; i <= length(line); i++) {
if (substr(line, i, 1) == "{") {
brace_count++;
}
if (substr(line, i, 1) == "}") {
brace_count--;
if (brace_count == 0) { break; }
}
}
if (brace_count != 0) {
report_error("closing brace not found for command \"" command[1] "\", at line " NR);
}
cmdargs = substr(line, 2, i-2);
line = substr(line, i + 1);
} else {
# Command does not have arguments, discard the spaces used to separate it
# from the next text
cmdargs = "";
sub(/^[ \t]+/, "", line);
}
# Process the command
switch (command[1]) {
# Commands generating "special" characters #################################
case "@":
replaced_line = replaced_line "@";
break;
case "bullet":
replaced_line = replaced_line "*";
break;
case "copyright":
replaced_line = replaced_line "(c)";
break;
case "minus":
replaced_line = replaced_line "-";
break;
case "registeredsymbol":
replaced_line = replaced_line "(r)";
break;
case "today":
# Make sure the date will be in english (we use "C" because it not certain
# that the English locale is enabled on the machine of the user)
replaced_line = replaced_line "'"`LANG=C date '+%d %B %Y' | sed -e 's,^0,,' `"'";
break;
# Commands to display text in a special style ##############################
case "b": # bold
line = "*" cmdargs "*" line;
break;
case "cite":
case "emph":
line = cmdargs line;
break;
case "code":
case "command":
case "env":
case "option":
case "var":
# Should be in fixed-spacing font; printed with single-quotes
line = "'\''" cmdargs "'\''" line;
break;
case "i": # italic
line = "_" cmdargs "_" line;
break;
case "email":
line = "<" cmdargs ">" line;
break;
case "file":
line = "\"" cmdargs "\"" line;
break;
case "key":
line = "<" cmdargs ">" line;
break;
case "r": # roman font
line = cmdargs line;
break;
case "sc":
# Small-Caps, keep as-is in plain text
line = cmdargs line;
break;
case "t": # typewriter-like
line = cmdargs line;
break;
case "uref":
replaced_line = replaced_line generate_url_reference(cmdargs);
break;
# Variable and Conditional commands ########################################
case "value":
if (variable[cmdargs] == "") {
report_error("variable '" cmdargs "' is unknow, for @value at line " NR);
}
line = variable[cmdargs] line;
break;
# Miscelleanous commands ###################################################
case "c":
# Comments: ignore everything to the end of line
line = "";
break;
default:
report_error("unknow command @" command[1] " at line " NR);
}
}
return (replaced_line line);
}
# Handle appropriately the "@end xxx"
function process_end(line) {
if (line == cond_names[cond_level]) {
end_conditional(line);
return;
}
switch (line) {
case "copying":
generate_paragraph();
redirect_out = "no";
break;
case "example":
generate_paragraph();
par_mode_pop("example");
par_indent = 1;
break;
case "flushleft":
generate_paragraph();
par_mode_pop(par_mode);
par_indent = 1;
break;
case "flushright":
generate_paragraph();
par_mode_pop(par_mode);
par_indent = 1;
break;
case "itemize":
generate_paragraph();
par_mode_pop("list");
par_indent = 1;
break;
case "quotation":
generate_paragraph();
par_mode_pop("quotation");
par_indent = 1;
break;
case "titlepage":
generate_page_break();
par_mode_pop("titlepage");
par_indent = 0;
break;
default:
report_error("unknow command @end " line " at line " NR);
}
}
BEGIN {
# Count the lines generated for the Table of Content
line_number = 0;
# To perform some basic checks on the file
top_was_found = 0;
bye_marker_found = 0;
# Paragraph generation parameters
par_mode_count = 0;
par_mode = "par";
par_nb_words = 0;
par_indent = 1;
par_justify = "justify";
redirect_out = "no";
line_length = 76;
line_prefix = "";
# To handle conditional code
cond_level = 0;
cond_state = 1;
# Number of entries in the Table of Content
toc_count = 0;
toc_file = "'"$toc_file"'";
# Define a custom variable so it is possible to differentiate between
# texi2any and this script
variable["cctexi2txt"] = "1.0";
# Variables inherited from the command line'"$var_defs"'
}
# First line is special, we always ignore it
(NR == 1) { next; }
/^[ \t]*@/ {
# Treat the special commands that are supposed to be on a line by themselves
idx = match($0, /^@([a-zA-Z]+)/, command);
if (idx != 0) {
# Remove the command from current line
line = substr($0, idx + 1 + length(command[1]));
sub(/^[ \t]+/, "", line);
switch (command[1]) {
# Commands for structuring the document ####################################
case "chapter":
new_section(1, execute_commands(line), 1);
next;
case "section":
new_section(2, execute_commands(line), 1);
next;
case "subsection":
new_section(3, execute_commands(line), 1);
next;
case "subsubsection":
new_section(4, execute_commands(line), 1);
next;
case "node":
# We ignore nodes completely, this is for the "info" format only
next;
case "top":
# This is mandatory for "info" format, but useless for plain text
if (top_was_found > 0) {
report_error("command @top at line " NR " but was already found at line " top_was_found);
}
top_was_found = NR;
next;
case "unnumbered":
new_section(1, execute_commands(line), 0);
next;
# Commands for content in the Title Page ###################################
case "author":
generate_author_line(execute_commands(line));
next;
case "subtitle":
generate_title_page_subtitle(execute_commands(line));
next;
case "title":
generate_title_page_title(execute_commands(line));
next;
# Commands changing the way paragraph are displayed ########################
case "copying":
generate_paragraph();
redirect_out = "copyright";
copyright_count = 0;
next;
case "end":
process_end(line);
next;
case "example":
if (cond_state) {
generate_paragraph();
write_line("");
par_mode_push("example");
line_prefix = line_prefix " ";
}
next;
case "flushleft":
if (cond_state) {
generate_paragraph();
par_mode_push(par_mode);
par_justify = "left";
par_indent = 0;
}
next;
case "flushright":
if (cond_state) {
generate_paragraph();
par_mode_push(par_mode);
par_justify = "right";
par_indent = 0;
}
next;
case "itemize":
if (cond_state) {
generate_paragraph();
start_item_list(line);
}
next;
case "menu":
generate_paragraph();
discard_block(command[1]);
next;
case "quotation":
if (cond_state) {
generate_paragraph();
par_mode_push("quotation");
line_prefix = line_prefix " ";
line_length = line_length - 4;
if (line != "") {
add_text_to_paragraph(execute_commands(line));
# We add the ":" to the last word because we count on the function
# "add_text_to_paragraph" to remove the trailing spaces on the line
# first, which would not have happened if we just had appended the ":"
# to the argument in the function call
par_word[par_nb_words - 1] = par_word[par_nb_words - 1] ":";
line = "";
}
}
next;
case "titlepage":
generate_title_page();
next;
# Commands generating text automacitally ###################################
case "contents":
if (cond_state) {
generate_paragraph();
write_line("");
write_line("");
print "@table_of_content@";
}
next;
case "insertcopying":
if (cond_state) {
generate_paragraph();
# The copying block was already formatted, we just have to print it as-is
for (i = 0; i < copyright_count; i++) {
write_line(copyright_lines[i]);
}
}
next;
case "page":
generate_page_break();
next;
case "sp":
if (cond_state) {
generate_paragraph();
while (line > 0) {
write_line("");
line--;
}
}
next;
case "vskip":
# Silently ignore, this is just for TeX
if (cond_state) {
generate_paragraph();
}
next;
# Variable and Conditional commands ########################################
case "ifdocbook": start_conditional(command[1], 0); line = ""; next;
case "ifhtml": start_conditional(command[1], 0); line = ""; next;
case "ifinfo": start_conditional(command[1], 1); line = ""; next; # "for historical compatibility"
case "ifplaintext": start_conditional(command[1], 1); line = ""; next;
case "iftex": start_conditional(command[1], 0); line = ""; next;
case "ifxml": start_conditional(command[1], 0); line = ""; next;
case "ifnotdocbook": start_conditional(command[1], 1); line = ""; next;
case "ifnothtml": start_conditional(command[1], 1); line = ""; next;
case "ifnotinfo": start_conditional(command[1], 0); line = ""; next; # "for historical compatibility"
case "ifnotplaintext": start_conditional(command[1], 0); line = ""; next;
case "ifnottex": start_conditional(command[1], 1); line = ""; next;
case "ifnotxml": start_conditional(command[1], 1); line = ""; next;
case "ifclear": start_conditional(command[1], (variable[line] == "")); next;
case "ifset": start_conditional(command[1], (variable[line] != "")); next;
case "clear":
if (cond_state) {
variable[ execute_commands(line) ] = "";
}
next;
case "set":
if (cond_state) {
set_variable(execute_commands(line));
}
next;
# Miscelleanous commands ###################################################
case "bye":
# Mark the end of file, we are supposed to ignore everything after
if (cond_state) {
generate_paragraph();
while (getline != 0) { }
bye_marker_found = 1;
}
next;
case "c":
# Comments: ignore everything to the end of line
next;
case "errormsg":
print "Error: " execute_commands(cmdargs) > "/dev/stderr";
print " (from \"'"$input_file"'\", line " NR ")" > "/dev/stderr";
bye_marker_found = 1;
exit 4;
case "finalout":
# Nothing to do, we are not generating anything in output file about long lines
next;
case "ignore":
# These are multi-lines comments
discard_block(command[1]);
next;
case "indent":
par_indent = 1;
if (line == "") { next; }
$0 = line;
break;
case "noindent":
par_indent = 0;
if (line == "") { next; }
$0 = line;
break;
case "setfilename":
# Should set the output file name automatically
# at current time, we just ignore it
next;
case "settitle":
# This is used for page headers
# in a plain text file, it is useless
next;
}
# Commands that were not recognised here may be commands that can be used
# anywhere in a line but happenned to be at the beginning of the line this
# time, we do nothing so they will be processed by "execute_commands"
}
}
/@item/ {
# We treat @item specially because it may generate more than 1 paragraph
if (!cond_state) { next; }
if (par_mode != "list") {
report_error("found @item at line " NR " but not inside an @itemize");
}
while (1) {
idx = match($0, /@item/);
if (idx == 0) { break; }
# We generate paragraph with all the text seen so far, which is part of
# the previous item
add_text_to_paragraph(substr($0, 1, idx - 1));
generate_paragraph();
$0 = substr($0, idx + 5);
# When an item is found, we clear "par_ident" to actually place the item
# mark on the next paragragh
par_indent = 0;
}
# If the item is on a line by itself, stop processing the line to avoid
# skipping lines more than necessary
if (/^[ \t]*$/) { next; }
}
# Non-empty lines are added to the current paragraph
{
if (!cond_state) { next; }
switch (par_mode) {
case "list":
case "par":
case "titlepage":
case "quotation":
if (/^[ \t]*$/) {
# Empty lines separate paragraphs
generate_paragraph();
# in list of items, they also tell us that user prefers an aerated list
list_item_wants_sepline = 1;
} else {
add_text_to_paragraph(execute_commands($0));
}
break;
case "example":
# Line is printed unmodified, not split and not merged, but with an indentation
$0 = line_prefix execute_commands($0);
sub(/[ \t]*$/, "");
write_line($0);
break;
default:
report_error("paragraph mode \"" par_mode "\" is not supported for line processing (line " NR ")");
}
}
END {
if (!bye_marker_found) {
report_error("command \"@bye\" missing at end of file");
}
if (!top_was_found) {
report_error("command \"@top\" was not found in the file");
}
# Count the number of lines that the ToC will occupy
# we assume the ToC is at the beginning, so all sections will be shifted
# by this number of lines down
toc_nb_lines = 0;
for (i = 1; i <= toc_count; i++) {
if ((i > 1) && (toc_entry_level[i] == 1)) {
toc_nb_lines++;
}
toc_nb_lines++;
}
# Generate the ToC
for (i = 1; i <= toc_count; i++) {
if ((i > 1) && (toc_entry_level[i] == 1)) {
print "" > toc_file;
}
$0 = " " toc_entry_name[i] " ";
if (length($0) % 2) { $0 = $0 " "; }
while (length($0) < 76 - 4) {
$0 = $0 " .";
}
target_line = toc_entry_line[i] + toc_nb_lines;
$0 = substr($0, 1, (76 - 5) - length(target_line)) " " target_line;
print > toc_file;
}
}
' "$input_file" > "$temp_file" || exit $?
# Run awk for 2nd pass, if it fails also stop now without deleting temp files
awk '
/@table_of_content@/ {
while (getline < "'"$toc_file"'") {
print;
}
next;
}
{ print }
' "$temp_file" > "$output_file" || exit $?
# If all worked, remove the temp files
rm -f "$temp_file"
rm -f "$toc_file"