diff --git a/.gitignore b/.gitignore index 384ef46c..79d7aff8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ m4/lt~obsolete.m4 src/wconfig.h +# These files are generated by scripts +README.i18n + # These files are compilation stuff *.lo *.o diff --git a/Makefile.am b/Makefile.am index 28a8fad6..e2807bfe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,12 +34,13 @@ SUBDIRS = wrlib WINGs src util po WindowMaker wmlib WPrefs.app doc DIST_SUBDIRS = $(SUBDIRS) test EXTRA_DIST = TODO BUGS BUGFORM FAQ FAQ.I18N INSTALL \ - INSTALL-WMAKER README.definable-cursor \ + INSTALL-WMAKER README.i18n README.definable-cursor \ The-perfect-Window-Maker-patch.txt \ README COPYING.WTFPL autogen.sh \ email-clients.txt checkpatch.pl update-changelog.pl \ script/check-translation-sources.sh \ script/generate-mapfile-from-header.sh \ + script/generate-txt-from-texi.sh \ script/nested-func-to-macro.sh if USE_LCOV diff --git a/autogen.sh b/autogen.sh index ea02ef23..46e56fcd 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Generate the documentation about compiling Window Maker +./script/generate-txt-from-texi.sh doc/build/Translations.texi > README.i18n + +# Generate the configure script from the 'configure.ac' autoreconf -vfi -I m4 exit 0 diff --git a/configure.ac b/configure.ac index 1cb4c4ca..f43b2684 100644 --- a/configure.ac +++ b/configure.ac @@ -971,7 +971,8 @@ AC_CONFIG_FILES( dnl Window Maker's core src/Makefile src/wconfig.h po/Makefile - doc/Makefile doc/sk/Makefile doc/cs/Makefile doc/ru/Makefile + doc/Makefile doc/build/Makefile + doc/sk/Makefile doc/cs/Makefile doc/ru/Makefile WindowMaker/Makefile WindowMaker/Backgrounds/Makefile WindowMaker/Defaults/Makefile WindowMaker/IconSets/Makefile WindowMaker/Icons/Makefile WindowMaker/Pixmaps/Makefile diff --git a/doc/Makefile.am b/doc/Makefile.am index 25493ead..f39079c2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to produce Makefile.in -SUBDIRS = sk cs ru +SUBDIRS = build sk cs ru man_MANS = \ geticonset.1x \ diff --git a/doc/build/Makefile.am b/doc/build/Makefile.am new file mode 100644 index 00000000..bbb75dd8 --- /dev/null +++ b/doc/build/Makefile.am @@ -0,0 +1,15 @@ +# The list of sources are distributed, but none are to be +# installed along with Window Maker: +EXTRA_DIST = Readme \ + Translations.texi + +# How to re-generate automatically the top-level text files +all-local: $(top_srcdir)/README.i18n + +$(top_srcdir)/README.i18n: $(srcdir)/Translations.texi $(top_srcdir)/script/generate-txt-from-texi.sh + $(AM_V_GEN)if test -w "$(top_srcdir)/README.i18n" ; then \ + $(top_srcdir)/script/generate-txt-from-texi.sh \ + $(srcdir)/Translations.texi -o $(top_srcdir)/README.i18n ; \ + else \ + echo "Warning: \"$(top_srcdir)/README.i18n\" is not writeable, not regenerated" ; \ + fi diff --git a/doc/build/Readme b/doc/build/Readme new file mode 100644 index 00000000..352d34f0 --- /dev/null +++ b/doc/build/Readme @@ -0,0 +1,8 @@ +This directory contains the sources for the documentation about building +the Window Maker project from sources. + +This documentation is meant to go into the distributed sources archive, but +it is not supposed to be installed as it contains no relevant information. + +The documentaion are written in GNU Texinfo format and a script converts +them into plain text for distribution. diff --git a/doc/build/Translations.texi b/doc/build/Translations.texi new file mode 100644 index 00000000..fc5e21e6 --- /dev/null +++ b/doc/build/Translations.texi @@ -0,0 +1,86 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename wmaker_i18n.info +@settitle Window Maker Internationalisation 1.0 +@c %**end of header + +@c This documentation is written in Texinfo format: +@c https://www.gnu.org/software/texinfo/manual/texinfo/ +@c +@c The reference checker is the GNU texi2any tool, which can be invoked like this: +@c texi2any --plaintext --no-split --verbose Translations.texi +@c +@c If you modify this file, you may want to spell-check it with: +@c aspell --lang=en_GB --mode=texinfo check Translations.texi +@c +@c The length of lines in this file is set to 100 because it tends to keep sentences together +@c despite the embedded @commands{}; +@c +@c It is generally considered good practice for Tex and Texinfo formats to keep sentences on +@c different lines, using the fact that in the end they will be merged in paragraph anyway, because +@c it makes the patchs clearer about where the changes actually are. + +@finalout + +@set version git#next + +@c ---------------------------------------------------------------------------------- Title Page --- + +@copying +@noindent +This manual is for @sc{Window Maker} window manager, version @value{version}. + +@noindent Copyright @copyright{} 2015 The Window Maker Team. + +@quotation +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, see file COPYING for details. +@end quotation +@end copying + +@titlepage +@title Window Maker Internationalisation +@subtitle A guide to enable support for language translations +@subtitle in @sc{Window Maker}. +@author Christophe CURIS + +@page +@vskip 0pt plus 1filll +@insertcopying + +@sp 1 +Published by The Window Maker team on @today{}. +@end titlepage + +@c ---------------------------------------------------------------------------- Table of Content --- +@node Top +@ifnottex +@top Window Maker Internationalisation + +A guide to enable support for language translations +in @sc{Window Maker}. +@end ifnottex + +@contents + +@ifnottex +@sp 1 +This manual is for Window Maker, version @value{version}. +@end ifnottex + +@menu +@end menu + + +@c ------------------------------------------------------------------------------------- The End --- +@bye diff --git a/script/generate-txt-from-texi.sh b/script/generate-txt-from-texi.sh new file mode 100755 index 00000000..7a8eef7f --- /dev/null +++ b/script/generate-txt-from-texi.sh @@ -0,0 +1,1178 @@ +#!/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 +# +# 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 " -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 + + -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]; + } +} + +# 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; + + # 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"'"; +} + +# 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; + + # 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"