doc: create the skeleton to have a documentation for Internationalisation
[wmaker-crm.git] / script / generate-txt-from-texi.sh
blob7a8eef7fa43bc8a0cc2949522b18a6a891b9508e
1 #!/bin/sh
2 ###########################################################################
4 # Window Maker window manager
6 # Copyright (c) 2014-2015 Christophe CURIS
7 # Copyright (c) 2015 Window Maker Team
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with this program; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 ###########################################################################
25 # generate-txt-from-texi.sh:
26 # generate a plain text file from a texinfo documentation
28 # The goal is to achieve a result similar to:
29 # texi2any --plaintext --no-split <filename>
31 # The reason for this script is that we do not want to add a strict
32 # dependancy on the 'makeinfo' tool suite (which has its own dependancies)
34 # There is also the problem that we use is to generate some documentations
35 # that should be available before the 'configure' script have been run
36 # (in the 'autogen.sh' script) because some of these documentation provide
37 # information for running the 'configure' script; We distribute these
38 # generated docs so normal user won't have the problem, but people that
39 # want to build from the Git repository.
41 # This script is not a reference for Texinfo syntax, so if you modified the
42 # texi source, you should really consider running texi2any at least once to
43 # make sure everything is still ok.
45 ###########################################################################
47 # Despite trying to be close to the texi2any output, this script has a few
48 # known differences:
50 # - texi2any does not generate a proper title header, it satisfy itself
51 # with a rudimentary single line; this script generates a better looking
52 # header with all the information provided
54 # - the table of content contains the line number for the section to ease
55 # navigation contrary to texi2any that satisfy itself with a simplist toc
57 # - the paragraphs are properly justified, contrary to texi2any which only
58 # flush left them in text outputs
60 # - There are 2 blank lines instead of 1 before chapter/section to make
61 # them stand out more
63 # - the line length is set to 76 instead of 72
65 # - there are some difference in what characters are added when a style is
66 # used for a string (@emph, @file, @sc, ...) because it assumes the text
67 # will be read in a plain text tool with no special "smart" highlighting
69 # - it does not check that the syntax is valid; there are some simple
70 # checks but it may misbehave, it does not replace a quality check with
71 # texi2any
73 # - not all commands are implemented, some because they were not needed
74 # so far, probably a few because they would be too complex to implement
76 ###########################################################################
78 # Please note that this script is writen in sh+awk on purpose: this script
79 # is gonna be run on the machine of the person who is trying to compile
80 # WindowMaker, and as such we cannot be sure to find any scripting language
81 # in a known version and that works (python/ruby/tcl/perl/php/you-name-it).
83 # So for portability, we stick to the same sh+awk constraint as Autotools
84 # to limit the problem, see for example:
85 # http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
87 ###########################################################################
89 # Report an error on stderr and exit with status 1 to tell make could not work
90 arg_error() {
91 echo "$0: $@" >&2
92 exit 1
95 # print help and exit with success status
96 print_help() {
97 echo "$0: convert a Texinfo file into a plain text file"
98 echo "Usage: $0 [options...] file.texi"
99 echo "valid options are:"
100 echo " -v version : version of the project"
101 echo " -o file : name of text file to create"
102 exit 0
105 # Extract command line arguments
106 while [ $# -gt 0 ]; do
107 case $1 in
109 -h|-help|--help) print_help ;;
112 shift
113 output_file="$1"
117 shift
118 project_version="$1"
121 -*) arg_error "unknow option '$1'" ;;
124 [ "x$input_file" = "x" ] || arg_error "only 1 input file can be specified, not \"$input_file\" and \"$1\""
125 input_file="$1"
127 esac
128 shift
129 done
131 # Check consistency of command-line
132 [ "x$input_file" != "x" ] || arg_error "no input texi file given"
133 [ "x$output_file" != "x" ] || arg_error "no output file given"
135 ###########################################################################
136 # The script works in 2 passes, in the first pass we generate an almost
137 # complete text file in the temporary file $temp_file, it also generates
138 # the table of content in $toc_file as a sed script.
139 # The second pass generate the $output_file from that $temp_file and the
140 # $toc_file
141 ###########################################################################
143 # Create the temp file in the current directory
144 temp_file="`echo "$input_file" | sed -e 's,^.*/\([^/]*\)$,\1, ; s,\.[^.]*$,,' `.tmp"
145 toc_file="`echo "$temp_file" | sed -e 's,\.[^.]*$,,' `.toc"
147 # Run awk for 1st pass, but if it fails stop now without deleting temp files
148 awk '
149 # Stop processing everything, print the message for user and return error code
150 # to tell "make" to not go further
151 function report_error(message) {
152 print "Error: " message > "/dev/stderr";
154 # When we call "exit", the block "END" is still called, we falsely set
155 # this variable to skip a spurious second error message
156 bye_marker_found = 1;
158 # Code 1 is used when the script is invoked with incorrect arguments
159 # Code 2 is used by awk to report problems
160 exit 3;
163 # Conditionals for @ifXXX and @ifnotXXX commands
164 # stored in a stack to allow embedding conditionals inside other conditionals
165 # the global variable "cond_state" contains the current condition (0 or 1)
166 function start_conditional(name, value, local_i) {
167 cond_level++;
168 cond_names[cond_level] = name;
169 cond_value[cond_level] = value;
170 cond_state = value;
171 for (local_i = 1; local_i < cond_level; local_i++) {
172 cond_state = cond_state && cond_value[local_i];
176 function end_conditional(name, local_i) {
177 cond_level--;
178 cond_state = 1;
179 for (local_i = 1; local_i < cond_level; local_i++) {
180 cond_state = cond_state && cond_value[local_i];
184 # Write a single line to the output
185 function write_line(line) {
186 if (!cond_state) { return; }
188 switch (redirect_out) {
189 case "no":
190 print line;
191 line_number++;
192 break;
194 case "copyright":
195 copyright_lines[copyright_count++] = line;
196 break;
198 default:
199 report_error("redirect output mode \"" redirect_out "\" is not supported (line " NR ")");
203 # Paragraph modes
204 # the current mode for paragraph handling is a Stack to allow embedding
205 # modes inside other modes
206 # the global variable "par_mode" contains the active mode
207 function par_mode_push(mode, local_i) {
208 par_mode_count++;
209 par_mode_save_previous[par_mode_count] = par_mode;
210 par_mode_save_length[par_mode_count] = line_length;
211 par_mode_save_prefix[par_mode_count] = line_prefix;
212 par_mode_save_justify[par_mode_count] = par_justify;
213 par_mode = mode;
215 # Check for quality of output
216 if (length(line_prefix) + 25 > line_length) {
217 print "Warning: too many paragraph modes imbricated at line " NR " for " mode > "/dev/stderr";
218 line_length = length(line_prefix) + 25;
222 function par_mode_pop(mode, local_i) {
223 if ((par_mode != mode) || (par_mode_count <= 0)) {
224 report_error("found @end " mode " at line " NR " but not in @" mode " (current state is @" par_mode ")");
226 par_mode = par_mode_save_previous[par_mode_count];
227 line_length = par_mode_save_length[par_mode_count];
228 line_prefix = par_mode_save_prefix[par_mode_count];
229 par_justify = par_mode_save_justify[par_mode_count];
230 par_mode_count--;
233 # Discard all the lines in the file until the specified "@end" is found on a line by itself
234 function discard_block(name, local_start_line) {
235 local_start_line = NR;
236 while (1) {
237 if (getline == 0) { report_error("end of file reached while searching \"@end " name "\", started at line " local_start_line); }
238 if ($0 == "@end " name) { break; }
242 # Title Page generation
243 function generate_title_page() {
244 if (!cond_state) { return; }
246 if (par_nb_words > 0) {
247 generate_paragraph();
248 write_line(gen_underline(0, 76));
251 # Title page start with 5 blank lines so the "title" coming after will
252 # stand out a little bit
253 write_line("");
254 write_line("");
255 write_line("");
256 par_mode_push("titlepage");
257 line_prefix = " ";
258 line_length = 76 - 4;
261 function generate_title_page_title(title, local_array, local_count, local_i) {
262 if (!cond_state) { return; }
264 if (par_mode != "titlepage") {
265 report_error("command @title used outside @titlepage, at line " NR);
267 generate_paragraph();
269 # Title deserves more space
270 write_line("");
271 write_line("");
273 # Split long title
274 if (length(title) < 60) {
275 local_count = 1;
276 local_array[1] = title;
277 } else {
278 local_count = int((length(title) + 59 ) / 60);
279 sub_length = int((length(title) + local_count - 1) / local_count);
281 local_count = 0;
282 while (length(title) > 0) {
283 if (length(title) > sub_length) {
284 # Cut at first space before the length
285 local_i = sub_length + 1;
286 while (local_i > 0) {
287 if (substr(title, local_i, 1) == " ") { break; }
288 local_i--;
290 if (local_i == 0) {
291 # Can not break first word, break at first possible place
292 local_i = index(title, " ");
293 if (local_i == 0) { local_i = length(title) + 1; }
295 } else {
296 local_i = length(title) + 1;
299 local_count++;
300 local_array[local_count] = substr(title, 1, local_i - 1);
302 title = substr(title, local_i + 1);
306 # Center the title
307 for (local_i = 1; local_i <= local_count; local_i++) {
308 write_line(gen_underline(-1, int((76 - length(local_array[local_i])) / 2)) local_array[local_i]);
311 write_line("");
312 write_line("");
315 function generate_title_page_subtitle(title, local_array, local_count, local_i) {
316 if (!cond_state) { return; }
318 if (par_mode != "titlepage") {
319 report_error("command @subtitle used outside @titlepage, at line " NR);
321 generate_paragraph();
323 # Split long lines
324 if (length(title) < 65) {
325 local_count = 1;
326 local_array[1] = title;
327 } else {
328 local_count = int((length(title) + 64) / 65);
329 sub_length = int((length(title) + local_count - 1) / local_count);
331 local_count = 0;
332 while (length(title) > 0) {
333 if (length(title) > sub_length) {
334 # Cut at first space before the length
335 local_i = sub_length + 1;
336 while (local_i > 0) {
337 if (substr(title, local_i, 1) == " ") { break; }
338 local_i--;
340 if (local_i == 0) {
341 # Can not break first word, break at first possible place
342 local_i = index(title, " ");
343 if (local_i == 0) { local_i = length(title) + 1; }
345 } else {
346 local_i = length(title) + 1;
349 local_count++;
350 local_array[local_count] = substr(title, 1, local_i - 1);
352 title = substr(title, local_i + 1);
356 # Center the title
357 for (local_i = 1; local_i <= local_count; local_i++) {
358 write_line(gen_underline(-1, int((76 - length(local_array[local_i]) - 4) / 2)) "~ " local_array[local_i] " ~");
362 # Generate separation line to simulate page breaks in plain text file
363 function generate_page_break() {
364 if (!cond_state) { return; }
366 generate_paragraph();
367 if (par_mode = "titlepage") {
368 write_line("");
370 write_line("");
371 write_line(gen_underline(0, 76));
372 par_indent = 1;
375 # Handle chapter and section declaration
376 # take care of the automatic numbering and to put the line in the table of
377 # content file, then generate the underlined line in output
378 function new_section(level, title, is_numbered, local_i, local_line) {
379 if (!cond_state) { return; }
381 # Dump the current paragraph now
382 generate_paragraph();
384 # Update the counters
385 if (is_numbered) {
386 section[level]++;
387 for (local_i = level + 1; local_i <= 4; local_i++) {
388 section[local_i] = 0;
392 # Generate the line to be displayed
393 if (is_numbered) {
394 local_line = section[1];
395 for (local_i = 2; local_i <= level; local_i++) {
396 local_line = local_line "." section[local_i];
398 local_line = local_line " " title;
399 } else {
400 local_line = title;
403 # Add the entry to the ToC
404 toc_count++;
405 toc_entry_level[toc_count] = level;
406 toc_entry_name[toc_count] = local_line;
407 for (local_i = 1; local_i < level; local_i++) {
408 toc_entry_name[toc_count] = " " toc_entry_name[toc_count];
410 toc_entry_line[toc_count] = line_number + 3;
412 # Print the section description
413 write_line("");
414 write_line("");
415 write_line(local_line);
416 write_line(gen_underline(level, length(local_line)));
417 par_indent = 0;
420 # List of Items
421 function start_item_list(mark) {
422 par_mode_push("list");
423 list_is_first_item = 1;
424 list_item_wants_sepline = 0;
425 par_indent = 1;
426 line_prefix = " ";
427 if (mark == "") {
428 item_list_mark = "*";
429 } else {
430 item_list_mark = execute_commands(mark);
432 write_line("");
435 # Generate Underline string with the specified length
436 function gen_underline(id, len, local) {
437 switch (id) {
438 case -1: local = " "; break;
439 case 1: local = "**********"; break;
440 case 2: local = "=========="; break;
441 case 3: local = "----------"; break;
442 case 4: local = ".........."; break;
443 default: local = "~~~~~~~~~~"; break;
445 while (length(local) < len) {
446 local = local local;
448 return substr(local, 1, len);
451 # Generate text for an URL link
452 function generate_url_reference(args, local_nb, local_arr) {
453 local_nb = split(args, local_arr, ",");
454 switch (local_nb) {
455 case 1:
456 return local_arr[1];
458 case 2:
459 return execute_commands(local_arr[2]) " (" local_arr[1] ")";
461 case 3:
462 return execute_commands(local_arr[3]);
464 default:
465 report_error("bad number of argument " local_nb " for @uref at line " NR);
469 # Generate a line with the name of an author
470 # note, we assume the name(s) always fit on a line
471 function generate_author_line(name, local_offset, local_attach_to_par) {
472 if (!cond_state) { return; }
474 local_attach_to_par = (par_nb_words > 0);
476 generate_paragraph();
478 switch (par_mode) {
479 case "titlepage":
480 name = "-- " name " --";
481 local_offset = int((76 - length(name)) / 2);
482 if (local_offset < 2) { local_offset = 2; }
483 write_line("");
484 write_line(gen_underline(-1, local_offset) name);
485 break;
487 case "quotation":
488 name = "-- " name;
489 local_offset = int((line_length - length(line_prefix) - length(name)) * 2/3);
490 if (local_offset < length(line_prefix) + 2) { local_offset = length(line_prefix) + 2; }
491 if (!local_attach_to_par) { write_line(""); }
492 write_line(line_prefix gen_underline(-1, local_offset) name);
493 break;
495 default:
496 report_error("command @author used in an inappropriate mode (" par_mode ") at line " NR);
500 # Add the specified line to the curren paragraph being built, do not print anything yet
501 function add_text_to_paragraph(line) {
502 nb = split(line, words, /[ \t]+/);
503 for (i = 1; i <= nb; i++) {
504 if (words[i] != "") {
505 par_word[par_nb_words++] = words[i];
510 # Print the paragraph from all the lines read so far
511 function generate_paragraph( local_prefix, local_line, local_length,
512 idx_word_start, idx_word_end, local_i) {
513 if (par_nb_words <= 0) { return; }
515 local_line = line_prefix;
517 switch (par_mode) {
518 case "list":
519 if (list_item_wants_sepline && !list_is_first_item) {
520 write_line("");
522 list_is_first_item = 0;
523 list_item_wants_sepline = 0;
524 if (!par_indent) {
525 local_prefix = item_list_mark " ";
526 while (length(local_prefix) < 5) { local_prefix = " " local_prefix; }
527 local_line = substr(local_line, 1, length(local_line) - 5) local_prefix;
529 break;
531 case "titlepage":
532 write_line("");
533 break;
535 case "par":
536 write_line("");
537 if (par_indent) {
538 local_line = local_line " ";
540 break;
542 case "quotation":
543 write_line("");
544 # There is no extra indentation of paragraphs in this mode
545 break;
547 default:
548 report_error("paragraph mode \"" par_mode "\" is not supported in generate_paragraph (line " NR ")");
551 # Split the paragraph in lines
552 idx_word_start = 0;
553 while (idx_word_start < par_nb_words) {
554 # First word is always printed, this makes sure that words too long for a line will
555 # always be printed, very likely on a line by themselfs
556 idx_word_end = idx_word_start;
557 local_length = length(local_line) + length(par_word[idx_word_start]);
558 idx_word_start++;
560 # See how many word we can fit on the line
561 while (idx_word_end < par_nb_words - 1) {
562 if (local_length + 1 + length(par_word[idx_word_end + 1]) > line_length) { break; }
563 idx_word_end++;
564 local_length = local_length + 1 + length(par_word[idx_word_end]);
567 # Put all those words on the current line with the appropriate justification
568 if (par_justify == "right") {
569 local_line = local_line gen_underline(-1, line_length - local_length) par_word[idx_word_start - 1];
570 while (idx_word_start <= idx_word_end) {
571 local_line = local_line " " par_word[idx_word_start++];
573 } else {
574 if ((par_justify == "left") || (idx_word_end == par_nb_words - 1) ||
575 (local_length >= line_length) || (idx_word_end < idx_word_start)) {
576 local_line = local_line par_word[idx_word_start - 1];
577 while (idx_word_start <= idx_word_end) {
578 local_line = local_line " " par_word[idx_word_start++];
580 } else {
581 # We calculate the ideal size of a space (as a real number) which would
582 # make all the words perfectly fill the line, the formula being
583 # ideal size = 1 + needed_extra_spaces / number_of_spaces_in_line
584 ideal_space_length = 1 + (line_length - local_length) / (idx_word_end - idx_word_start + 1);
585 count_spaces = 0;
586 for (local_i = idx_word_start; local_i <= idx_word_end; local_i++) {
587 count_spaces = count_spaces + ideal_space_length;
588 word_space[local_i] = gen_underline(-1, int(count_spaces + 0.5));
589 count_spaces = count_spaces - length(word_space[local_i]);
592 local_line = local_line par_word[idx_word_start - 1];
593 while (idx_word_start <= idx_word_end) {
594 local_line = local_line word_space[idx_word_start] par_word[idx_word_start++];
599 write_line(local_line);
601 # Reset for next line
602 local_line = line_prefix;
604 par_nb_words = 0;
605 par_indent = 1;
608 # Replace commands by text in the line, return the result
609 function execute_commands(line, replaced_line) {
610 replaced_line = "";
611 while (1) {
612 idx = match(line, /@([a-zA-Z]+|.)/, command)
613 if (idx == 0) { break; }
615 # Separate the command and its arguments from the rest of the line
616 replaced_line = replaced_line substr(line, 1, idx - 1);
617 line = substr(line, idx + 1 + length(command[1]));
619 if (line ~ /^\{/) {
620 # Command has argument(s), extract them
621 brace_count = 0;
622 for (i = 1; i <= length(line); i++) {
623 if (substr(line, i, 1) == "{") {
624 brace_count++;
626 if (substr(line, i, 1) == "}") {
627 brace_count--;
628 if (brace_count == 0) { break; }
631 if (brace_count != 0) {
632 report_error("closing brace not found for command \"" command[1] "\", at line " NR);
635 cmdargs = substr(line, 2, i-2);
636 line = substr(line, i + 1);
638 } else {
639 # Command does not have arguments, discard the spaces used to separate it
640 # from the next text
641 cmdargs = "";
642 sub(/^[ \t]+/, "", line);
645 # Process the command
646 switch (command[1]) {
648 # Commands generating "special" characters #################################
649 case "@":
650 replaced_line = replaced_line "@";
651 break;
653 case "bullet":
654 replaced_line = replaced_line "*";
655 break;
657 case "copyright":
658 replaced_line = replaced_line "(c)";
659 break;
661 case "minus":
662 replaced_line = replaced_line "-";
663 break;
665 case "registeredsymbol":
666 replaced_line = replaced_line "(r)";
667 break;
669 case "today":
670 # Make sure the date will be in english (we use "C" because it not certain
671 # that the English locale is enabled on the machine of the user)
672 replaced_line = replaced_line "'"`LANG=C date '+%d %B %Y' | sed -e 's,^0,,' `"'";
673 break;
675 # Commands to display text in a special style ##############################
676 case "b": # bold
677 line = "*" cmdargs "*" line;
678 break;
680 case "cite":
681 case "emph":
682 line = cmdargs line;
683 break;
685 case "code":
686 case "command":
687 case "env":
688 case "option":
689 case "var":
690 # Should be in fixed-spacing font; printed with single-quotes
691 line = "'\''" cmdargs "'\''" line;
692 break;
694 case "i": # italic
695 line = "_" cmdargs "_" line;
696 break;
698 case "email":
699 line = "<" cmdargs ">" line;
700 break;
702 case "file":
703 line = "\"" cmdargs "\"" line;
704 break;
706 case "key":
707 line = "<" cmdargs ">" line;
708 break;
710 case "r": # roman font
711 line = cmdargs line;
712 break;
714 case "sc":
715 # Small-Caps, keep as-is in plain text
716 line = cmdargs line;
717 break;
719 case "t": # typewriter-like
720 line = cmdargs line;
721 break;
723 case "uref":
724 replaced_line = replaced_line generate_url_reference(cmdargs);
725 break;
727 # Miscelleanous commands ###################################################
728 case "c":
729 # Comments: ignore everything to the end of line
730 line = "";
731 break;
733 default:
734 report_error("unknow command @" command[1] " at line " NR);
738 return (replaced_line line);
741 # Handle appropriately the "@end xxx"
742 function process_end(line) {
743 if (line == cond_names[cond_level]) {
744 end_conditional(line);
745 return;
747 switch (line) {
748 case "copying":
749 generate_paragraph();
750 redirect_out = "no";
751 break;
753 case "example":
754 generate_paragraph();
755 par_mode_pop("example");
756 par_indent = 1;
757 break;
759 case "flushleft":
760 generate_paragraph();
761 par_mode_pop(par_mode);
762 par_indent = 1;
763 break;
765 case "flushright":
766 generate_paragraph();
767 par_mode_pop(par_mode);
768 par_indent = 1;
769 break;
771 case "itemize":
772 generate_paragraph();
773 par_mode_pop("list");
774 par_indent = 1;
775 break;
777 case "quotation":
778 generate_paragraph();
779 par_mode_pop("quotation");
780 par_indent = 1;
781 break;
783 case "titlepage":
784 generate_page_break();
785 par_mode_pop("titlepage");
786 par_indent = 0;
787 break;
789 default:
790 report_error("unknow command @end " line " at line " NR);
794 BEGIN {
795 # Count the lines generated for the Table of Content
796 line_number = 0;
798 # To perform some basic checks on the file
799 top_was_found = 0;
800 bye_marker_found = 0;
802 # Paragraph generation parameters
803 par_mode_count = 0;
804 par_mode = "par";
805 par_nb_words = 0;
806 par_indent = 1;
807 par_justify = "justify";
808 redirect_out = "no";
809 line_length = 76;
810 line_prefix = "";
812 # To handle conditional code
813 cond_level = 0;
814 cond_state = 1;
816 # Number of entries in the Table of Content
817 toc_count = 0;
818 toc_file = "'"$toc_file"'";
821 # First line is special, we always ignore it
822 (NR == 1) { next; }
824 /^[ \t]*@/ {
825 # Treat the special commands that are supposed to be on a line by themselves
826 idx = match($0, /^@([a-zA-Z]+)/, command);
827 if (idx != 0) {
828 # Remove the command from current line
829 line = substr($0, idx + 1 + length(command[1]));
830 sub(/^[ \t]+/, "", line);
832 switch (command[1]) {
834 # Commands for structuring the document ####################################
835 case "chapter":
836 new_section(1, execute_commands(line), 1);
837 next;
839 case "section":
840 new_section(2, execute_commands(line), 1);
841 next;
843 case "subsection":
844 new_section(3, execute_commands(line), 1);
845 next;
847 case "subsubsection":
848 new_section(4, execute_commands(line), 1);
849 next;
851 case "node":
852 # We ignore nodes completely, this is for the "info" format only
853 next;
855 case "top":
856 # This is mandatory for "info" format, but useless for plain text
857 if (top_was_found > 0) {
858 report_error("command @top at line " NR " but was already found at line " top_was_found);
860 top_was_found = NR;
861 next;
863 case "unnumbered":
864 new_section(1, execute_commands(line), 0);
865 next;
867 # Commands for content in the Title Page ###################################
868 case "author":
869 generate_author_line(execute_commands(line));
870 next;
872 case "subtitle":
873 generate_title_page_subtitle(execute_commands(line));
874 next;
876 case "title":
877 generate_title_page_title(execute_commands(line));
878 next;
880 # Commands changing the way paragraph are displayed ########################
881 case "copying":
882 generate_paragraph();
883 redirect_out = "copyright";
884 copyright_count = 0;
885 next;
887 case "end":
888 process_end(line);
889 next;
891 case "example":
892 if (cond_state) {
893 generate_paragraph();
894 write_line("");
895 par_mode_push("example");
896 line_prefix = line_prefix " ";
898 next;
900 case "flushleft":
901 if (cond_state) {
902 generate_paragraph();
903 par_mode_push(par_mode);
904 par_justify = "left";
905 par_indent = 0;
907 next;
909 case "flushright":
910 if (cond_state) {
911 generate_paragraph();
912 par_mode_push(par_mode);
913 par_justify = "right";
914 par_indent = 0;
916 next;
918 case "itemize":
919 if (cond_state) {
920 generate_paragraph();
921 start_item_list(line);
923 next;
925 case "menu":
926 generate_paragraph();
927 discard_block(command[1]);
928 next;
930 case "quotation":
931 if (cond_state) {
932 generate_paragraph();
933 par_mode_push("quotation");
934 line_prefix = line_prefix " ";
935 line_length = line_length - 4;
936 if (line != "") {
937 add_text_to_paragraph(execute_commands(line));
938 # We add the ":" to the last word because we count on the function
939 # "add_text_to_paragraph" to remove the trailing spaces on the line
940 # first, which would not have happened if we just had appended the ":"
941 # to the argument in the function call
942 par_word[par_nb_words - 1] = par_word[par_nb_words - 1] ":";
943 line = "";
946 next;
948 case "titlepage":
949 generate_title_page();
950 next;
952 # Commands generating text automacitally ###################################
953 case "contents":
954 if (cond_state) {
955 generate_paragraph();
956 write_line("");
957 write_line("");
958 print "@table_of_content@";
960 next;
962 case "insertcopying":
963 if (cond_state) {
964 generate_paragraph();
965 # The copying block was already formatted, we just have to print it as-is
966 for (i = 0; i < copyright_count; i++) {
967 write_line(copyright_lines[i]);
970 next;
972 case "page":
973 generate_page_break();
974 next;
976 case "sp":
977 if (cond_state) {
978 generate_paragraph();
979 while (line > 0) {
980 write_line("");
981 line--;
984 next;
986 case "vskip":
987 # Silently ignore, this is just for TeX
988 if (cond_state) {
989 generate_paragraph();
991 next;
993 # Variable and Conditional commands ########################################
994 case "ifdocbook": start_conditional(command[1], 0); line = ""; next;
995 case "ifhtml": start_conditional(command[1], 0); line = ""; next;
996 case "ifinfo": start_conditional(command[1], 1); line = ""; next; # "for historical compatibility"
997 case "ifplaintext": start_conditional(command[1], 1); line = ""; next;
998 case "iftex": start_conditional(command[1], 0); line = ""; next;
999 case "ifxml": start_conditional(command[1], 0); line = ""; next;
1001 case "ifnotdocbook": start_conditional(command[1], 1); line = ""; next;
1002 case "ifnothtml": start_conditional(command[1], 1); line = ""; next;
1003 case "ifnotinfo": start_conditional(command[1], 0); line = ""; next; # "for historical compatibility"
1004 case "ifnotplaintext": start_conditional(command[1], 0); line = ""; next;
1005 case "ifnottex": start_conditional(command[1], 1); line = ""; next;
1006 case "ifnotxml": start_conditional(command[1], 1); line = ""; next;
1008 # Miscelleanous commands ###################################################
1009 case "bye":
1010 # Mark the end of file, we are supposed to ignore everything after
1011 if (cond_state) {
1012 generate_paragraph();
1013 while (getline != 0) { }
1014 bye_marker_found = 1;
1016 next;
1018 case "c":
1019 # Comments: ignore everything to the end of line
1020 next;
1022 case "errormsg":
1023 print "Error: " execute_commands(cmdargs) > "/dev/stderr";
1024 print " (from \"'"$input_file"'\", line " NR ")" > "/dev/stderr";
1025 bye_marker_found = 1;
1026 exit 4;
1028 case "finalout":
1029 # Nothing to do, we are not generating anything in output file about long lines
1030 next;
1032 case "ignore":
1033 # These are multi-lines comments
1034 discard_block(command[1]);
1035 next;
1037 case "indent":
1038 par_indent = 1;
1039 if (line == "") { next; }
1040 $0 = line;
1041 break;
1043 case "noindent":
1044 par_indent = 0;
1045 if (line == "") { next; }
1046 $0 = line;
1047 break;
1049 case "setfilename":
1050 # Should set the output file name automatically
1051 # at current time, we just ignore it
1052 next;
1054 case "settitle":
1055 # This is used for page headers
1056 # in a plain text file, it is useless
1057 next;
1060 # Commands that were not recognised here may be commands that can be used
1061 # anywhere in a line but happenned to be at the beginning of the line this
1062 # time, we do nothing so they will be processed by "execute_commands"
1066 /@item/ {
1067 # We treat @item specially because it may generate more than 1 paragraph
1068 if (!cond_state) { next; }
1070 if (par_mode != "list") {
1071 report_error("found @item at line " NR " but not inside an @itemize");
1074 while (1) {
1075 idx = match($0, /@item/);
1076 if (idx == 0) { break; }
1078 # We generate paragraph with all the text seen so far, which is part of
1079 # the previous item
1080 add_text_to_paragraph(substr($0, 1, idx - 1));
1081 generate_paragraph();
1082 $0 = substr($0, idx + 5);
1084 # When an item is found, we clear "par_ident" to actually place the item
1085 # mark on the next paragragh
1086 par_indent = 0;
1089 # If the item is on a line by itself, stop processing the line to avoid
1090 # skipping lines more than necessary
1091 if (/^[ \t]*$/) { next; }
1094 # Non-empty lines are added to the current paragraph
1096 if (!cond_state) { next; }
1098 switch (par_mode) {
1099 case "list":
1100 case "par":
1101 case "titlepage":
1102 case "quotation":
1103 if (/^[ \t]*$/) {
1104 # Empty lines separate paragraphs
1105 generate_paragraph();
1106 # in list of items, they also tell us that user prefers an aerated list
1107 list_item_wants_sepline = 1;
1108 } else {
1109 add_text_to_paragraph(execute_commands($0));
1111 break;
1113 case "example":
1114 # Line is printed unmodified, not split and not merged, but with an indentation
1115 $0 = line_prefix execute_commands($0);
1116 sub(/[ \t]*$/, "");
1117 write_line($0);
1118 break;
1120 default:
1121 report_error("paragraph mode \"" par_mode "\" is not supported for line processing (line " NR ")");
1125 END {
1126 if (!bye_marker_found) {
1127 report_error("command \"@bye\" missing at end of file");
1129 if (!top_was_found) {
1130 report_error("command \"@top\" was not found in the file");
1133 # Count the number of lines that the ToC will occupy
1134 # we assume the ToC is at the beginning, so all sections will be shifted
1135 # by this number of lines down
1136 toc_nb_lines = 0;
1137 for (i = 1; i <= toc_count; i++) {
1138 if ((i > 1) && (toc_entry_level[i] == 1)) {
1139 toc_nb_lines++;
1141 toc_nb_lines++;
1144 # Generate the ToC
1145 for (i = 1; i <= toc_count; i++) {
1146 if ((i > 1) && (toc_entry_level[i] == 1)) {
1147 print "" > toc_file;
1150 $0 = " " toc_entry_name[i] " ";
1151 if (length($0) % 2) { $0 = $0 " "; }
1152 while (length($0) < 76 - 4) {
1153 $0 = $0 " .";
1156 target_line = toc_entry_line[i] + toc_nb_lines;
1158 $0 = substr($0, 1, (76 - 5) - length(target_line)) " " target_line;
1159 print > toc_file;
1163 ' "$input_file" > "$temp_file" || exit $?
1165 # Run awk for 2nd pass, if it fails also stop now without deleting temp files
1166 awk '
1167 /@table_of_content@/ {
1168 while (getline < "'"$toc_file"'") {
1169 print;
1171 next;
1173 { print }
1174 ' "$temp_file" > "$output_file" || exit $?
1176 # If all worked, remove the temp files
1177 rm -f "$temp_file"
1178 rm -f "$toc_file"