WINGs: Set widget background pixmaps before realization.
[wmaker-crm.git] / script / generate-txt-from-texi.sh
blob3800c72cbac0dbcfbfd0274a8215d9c79087784a
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 # - when making cross-references, generate a decent-looking string with
64 # the target line number, instead of the crypting "*Note:" label (inherited
65 # from "info" format) that looks out of place
67 # - the line length is set to 76 instead of 72
69 # - there are some difference in what characters are added when a style is
70 # used for a string (@emph, @file, @sc, ...) because it assumes the text
71 # will be read in a plain text tool with no special "smart" highlighting
73 # - it does not check that the syntax is valid; there are some simple
74 # checks but it may misbehave, it does not replace a quality check with
75 # texi2any
77 # - not all commands are implemented, some because they were not needed
78 # so far, probably a few because they would be too complex to implement
81 # There are a few limitations due to Texinfo being a cheesy format:
83 # - the @node should be followed immediately by its @chapter/@section
84 # command, otherwise you may have mismatch on the line number if you make a
85 # cross-reference to that @node
87 ###########################################################################
89 # Please note that this script is writen in sh+awk on purpose: this script
90 # is gonna be run on the machine of the person who is trying to compile
91 # WindowMaker, and as such we cannot be sure to find any scripting language
92 # in a known version and that works (python/ruby/tcl/perl/php/you-name-it).
94 # So for portability, we stick to the same sh+awk constraint as Autotools
95 # to limit the problem, see for example:
96 # http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
98 ###########################################################################
100 # Report an error on stderr and exit with status 1 to tell make could not work
101 arg_error() {
102 echo "$0: $@" >&2
103 exit 1
106 # print help and exit with success status
107 print_help() {
108 echo "$0: convert a Texinfo file into a plain text file"
109 echo "Usage: $0 [options...] file.texi"
110 echo "valid options are:"
111 echo " -Dvar=val : set variable 'var' to value 'val'"
112 echo " -e email : set email address in variable 'emailsupport'"
113 echo " -v version : version of the project"
114 echo " -o file : name of text file to create"
115 exit 0
118 # Extract command line arguments
119 while [ $# -gt 0 ]; do
120 case $1 in
122 -D*)
123 echo "$1" | grep '^-D[a-zA-Z][a-zA-Z]*=' > /dev/null || arg_error "syntax error for '$1', expected -Dname=value"
124 var_defs="$var_defs
125 `echo "$1" | sed -e 's/^-D/ variable["/ ; s/=/"] = "/ ; s/$/";/' `"
129 shift
130 var_defs="$var_defs
131 variable[\"emailsupport\"] = \"@email{`echo "$1" | sed -e 's/@/@@/g' `}\";"
134 -h|-help|--help) print_help ;;
137 shift
138 output_file="$1"
142 shift
143 project_version="$1"
146 -*) arg_error "unknow option '$1'" ;;
149 [ "x$input_file" = "x" ] || arg_error "only 1 input file can be specified, not \"$input_file\" and \"$1\""
150 input_file="$1"
152 esac
153 shift
154 done
156 # Check consistency of command-line
157 [ "x$input_file" != "x" ] || arg_error "no input texi file given"
158 [ "x$output_file" != "x" ] || arg_error "no output file given"
160 ###########################################################################
161 # The script works in 2 passes, in the first pass we generate an almost
162 # complete text file in the temporary file $temp_file, it also generates
163 # the table of content in $toc_file as a sed script.
164 # The second pass generate the $output_file from that $temp_file and the
165 # $toc_file
166 ###########################################################################
168 # Create the temp file in the current directory
169 temp_file="`echo "$input_file" | sed -e 's,^.*/\([^/]*\)$,\1, ; s,\.[^.]*$,,' `.tmp"
170 toc_file="`echo "$temp_file" | sed -e 's,\.[^.]*$,,' `.toc"
171 xref_file="`echo "$temp_file" | sed -e 's,\.[^.]*$,,' `.xrf"
173 # Run awk for 1st pass, but if it fails stop now without deleting temp files
174 awk '
175 # Stop processing everything, print the message for user and return error code
176 # to tell "make" to not go further
177 function report_error(message) {
178 print "Error: " message > "/dev/stderr";
180 # When we call "exit", the block "END" is still called, we falsely set
181 # this variable to skip a spurious second error message
182 bye_marker_found = 1;
184 # Code 1 is used when the script is invoked with incorrect arguments
185 # Code 2 is used by awk to report problems
186 exit 3;
189 # Conditionals for @ifXXX and @ifnotXXX commands
190 # stored in a stack to allow embedding conditionals inside other conditionals
191 # the global variable "cond_state" contains the current condition (0 or 1)
192 function start_conditional(name, value, local_i) {
193 cond_level++;
194 cond_names[cond_level] = name;
195 cond_value[cond_level] = value;
196 cond_state = value;
197 for (local_i = 1; local_i < cond_level; local_i++) {
198 cond_state = cond_state && cond_value[local_i];
202 function end_conditional(name, local_i) {
203 cond_level--;
204 cond_state = 1;
205 for (local_i = 1; local_i < cond_level; local_i++) {
206 cond_state = cond_state && cond_value[local_i];
210 # Texinfo Variables
211 # the texinfo standard allows to have variables set with @set and used
212 # with @value; they can also be defined from command-line (-D)
213 # they are stored in the global array "variable[name]"
214 function set_variable(line, local_idx, local_name, local_value) {
215 gsub(/^[ \t]+/, "", line);
216 local_idx = match(line, /[ \t]/);
217 if (local_idx > 0) {
218 local_name = substr(line, 1, local_idx - 1);
219 local_value = substr(line, local_idx + 1);
220 gsub(/^[ \t]+/, "", local_value);
221 } else {
222 local_name = line;
223 local_value = "";
225 variable[ local_name ] = local_value;
228 # Write a single line to the output
229 function write_line(line) {
230 if (!cond_state) { return; }
232 if (redirect_out == "no") {
233 print line;
234 line_number++;
236 } else if (redirect_out == "copyright") {
237 copyright_lines[copyright_count++] = line;
239 } else {
240 report_error("redirect output mode \"" redirect_out "\" is not supported (line " NR ")");
244 # Paragraph modes
245 # the current mode for paragraph handling is a Stack to allow embedding
246 # modes inside other modes
247 # the global variable "par_mode" contains the active mode
248 function par_mode_push(mode, local_i) {
249 par_mode_count++;
250 par_mode_save_previous[par_mode_count] = par_mode;
251 par_mode_save_length[par_mode_count] = line_length;
252 par_mode_save_prefix[par_mode_count] = line_prefix;
253 par_mode_save_justify[par_mode_count] = par_justify;
254 par_mode_save_itemmark[par_mode_count] = item_list_mark;
255 par_mode = mode;
257 # Check for quality of output
258 if (length(line_prefix) + 25 > line_length) {
259 print "Warning: too many paragraph modes imbricated at line " NR " for " mode > "/dev/stderr";
260 line_length = length(line_prefix) + 25;
264 function par_mode_pop(mode, local_i) {
265 if ((par_mode != mode) || (par_mode_count <= 0)) {
266 report_error("found @end " mode " at line " NR " but not in @" mode " (current state is @" par_mode ")");
268 par_mode = par_mode_save_previous[par_mode_count];
269 line_length = par_mode_save_length[par_mode_count];
270 line_prefix = par_mode_save_prefix[par_mode_count];
271 par_justify = par_mode_save_justify[par_mode_count];
272 item_list_mark = par_mode_save_itemmark[par_mode_count];
273 par_mode_count--;
276 # Discard all the lines in the file until the specified "@end" is found on a line by itself
277 function discard_block(name, local_start_line) {
278 local_start_line = NR;
279 while (1) {
280 if (getline == 0) { report_error("end of file reached while searching \"@end " name "\", started at line " local_start_line); }
281 if ($0 == "@end " name) { break; }
285 # Title Page generation
286 function generate_title_page() {
287 if (!cond_state) { return; }
289 if (par_nb_words > 0) {
290 generate_paragraph();
291 write_line(gen_underline(0, 76));
294 # Title page start with 5 blank lines so the "title" coming after will
295 # stand out a little bit
296 write_line("");
297 write_line("");
298 write_line("");
299 par_mode_push("titlepage");
300 line_prefix = " ";
301 line_length = 76 - 4;
304 function generate_title_page_title(title, local_array, local_count, local_i) {
305 if (!cond_state) { return; }
307 if (par_mode != "titlepage") {
308 report_error("command @title used outside @titlepage, at line " NR);
310 generate_paragraph();
312 # Title deserves more space
313 write_line("");
314 write_line("");
316 # Split long title
317 if (length(title) < 60) {
318 local_count = 1;
319 local_array[1] = title;
320 } else {
321 local_count = int((length(title) + 59 ) / 60);
322 sub_length = int((length(title) + local_count - 1) / local_count);
324 local_count = 0;
325 while (length(title) > 0) {
326 if (length(title) > sub_length) {
327 # Cut at first space before the length
328 local_i = sub_length + 1;
329 while (local_i > 0) {
330 if (substr(title, local_i, 1) == " ") { break; }
331 local_i--;
333 if (local_i == 0) {
334 # Can not break first word, break at first possible place
335 local_i = index(title, " ");
336 if (local_i == 0) { local_i = length(title) + 1; }
338 } else {
339 local_i = length(title) + 1;
342 local_count++;
343 local_array[local_count] = substr(title, 1, local_i - 1);
345 title = substr(title, local_i + 1);
349 # Center the title
350 for (local_i = 1; local_i <= local_count; local_i++) {
351 write_line(gen_underline(-1, int((76 - length(local_array[local_i])) / 2)) local_array[local_i]);
354 write_line("");
355 write_line("");
358 function generate_title_page_subtitle(title, local_array, local_count, local_i) {
359 if (!cond_state) { return; }
361 if (par_mode != "titlepage") {
362 report_error("command @subtitle used outside @titlepage, at line " NR);
364 generate_paragraph();
366 # Split long lines
367 if (length(title) < 65) {
368 local_count = 1;
369 local_array[1] = title;
370 } else {
371 local_count = int((length(title) + 64) / 65);
372 sub_length = int((length(title) + local_count - 1) / local_count);
374 local_count = 0;
375 while (length(title) > 0) {
376 if (length(title) > sub_length) {
377 # Cut at first space before the length
378 local_i = sub_length + 1;
379 while (local_i > 0) {
380 if (substr(title, local_i, 1) == " ") { break; }
381 local_i--;
383 if (local_i == 0) {
384 # Can not break first word, break at first possible place
385 local_i = index(title, " ");
386 if (local_i == 0) { local_i = length(title) + 1; }
388 } else {
389 local_i = length(title) + 1;
392 local_count++;
393 local_array[local_count] = substr(title, 1, local_i - 1);
395 title = substr(title, local_i + 1);
399 # Center the title
400 for (local_i = 1; local_i <= local_count; local_i++) {
401 write_line(gen_underline(-1, int((76 - length(local_array[local_i]) - 4) / 2)) "~ " local_array[local_i] " ~");
405 # Generate separation line to simulate page breaks in plain text file
406 function generate_page_break() {
407 if (!cond_state) { return; }
409 generate_paragraph();
410 if (par_mode = "titlepage") {
411 write_line("");
413 write_line("");
414 write_line(gen_underline(0, 76));
415 par_indent = 1;
418 # Handle chapter and section declaration
419 # take care of the automatic numbering and to put the line in the table of
420 # content file, then generate the underlined line in output
421 function new_section(level, title, is_numbered, local_i, local_line) {
422 if (!cond_state) { return; }
424 # Dump the current paragraph now
425 generate_paragraph();
427 # Update the counters
428 if (is_numbered) {
429 section[level]++;
430 for (local_i = level + 1; local_i <= 4; local_i++) {
431 section[local_i] = 0;
435 # Generate the line to be displayed
436 if (is_numbered) {
437 local_line = section[1];
438 for (local_i = 2; local_i <= level; local_i++) {
439 local_line = local_line "." section[local_i];
441 local_line = local_line " " title;
442 } else {
443 local_line = title;
446 # Add the entry to the ToC
447 toc_count++;
448 toc_entry_level[toc_count] = level;
449 toc_entry_name[toc_count] = local_line;
450 for (local_i = 1; local_i < level; local_i++) {
451 toc_entry_name[toc_count] = " " toc_entry_name[toc_count];
453 toc_entry_line[toc_count] = line_number + 3;
455 # Print the section description
456 write_line("");
457 write_line("");
458 write_line(local_line);
459 write_line(gen_underline(level, length(local_line)));
460 par_indent = 0;
463 # Do not generate anything for Node command, but keep the line information so
464 # the nodes can be cross-referenced
465 function new_node(args, local_nb, local_arr, local_i) {
466 if (!cond_state) { return; }
468 # Dump the current paragraph now
469 generate_paragraph();
471 # The command takes many arguments, separate them because we care only for the 1st
472 local_nb = split(args, local_arr, ",");
473 if ((local_nb < 1) || (local_nb > 4)) {
474 report_error("bad number of argument " local_nb " for @node at line " NR);
476 gsub(/^[ \t]+/, "", local_arr[1]);
477 gsub(/[ \t]*$/, "", local_arr[1]);
478 if (local_arr[1] == "") {
479 report_error("missing node name for @node at line " NR);
482 # Consistency check
483 if (node_address[local_arr[1]] != "") {
484 report_error("node \"" local_arr[1] "\" is redefined at line " NR ", previous definition at line " node_defline[local_arr[1]]);
487 # Add a +3 offset to compensate for the position of the real location that will be the
488 # chapter/section that should be following
489 node_address[local_arr[1]] = line_number + 3;
490 node_defline[local_arr[1]] = NR;
493 # List of Items
494 function start_item_list(mark, type, default_mark) {
495 par_mode_push(type);
496 list_is_first_item = 1;
497 list_item_wants_sepline = 0;
498 par_indent = 1;
499 if (line_prefix == "") {
500 # First level of enumeration get one mode indentation space
501 line_prefix = " ";
502 } else {
503 line_prefix = line_prefix " ";
505 if (mark == "") {
506 item_list_mark = default_mark;
507 } else {
508 item_list_mark = execute_commands(mark);
510 write_line("");
513 # One item in a Table
514 function generate_item_in_table(line) {
515 if (line !~ /^[ \t]*@itemx?[ \t]/) {
516 report_error("bas usage for @item inside a @table, should be at start of line and followed by its value");
519 generate_paragraph();
520 if (list_item_wants_sepline && !list_is_first_item) {
521 write_line("");
524 # Apply the global table style to this item
525 gsub(/^[ \t]*@itemx?[ \t]*/, "", line);
526 line = execute_commands(item_list_mark "{" line "}");
528 # Cancel the indentation added for the 2nd column for that line
529 line = substr(line_prefix, 1, length(line_prefix)-5) line;
530 write_line(line);
532 list_item_wants_sepline = 0;
535 # Generate Underline string with the specified length
536 function gen_underline(id, len, local) {
537 if (id == -1) { local = " "; } else
538 if (id == 1) { local = "**********"; } else
539 if (id == 2) { local = "=========="; } else
540 if (id == 3) { local = "----------"; } else
541 if (id == 4) { local = ".........."; }
542 else { local = "~~~~~~~~~~"; }
543 while (length(local) < len) {
544 local = local local;
546 return substr(local, 1, len);
549 # Generate text for an URL link
550 function generate_url_reference(args, local_nb, local_arr) {
551 local_nb = split(args, local_arr, ",");
552 if (local_nb == 1) {
553 return local_arr[1];
555 } else if (local_nb == 2) {
556 return execute_commands(local_arr[2]) " (" local_arr[1] ")";
558 } else if (local_nb == 3) {
559 return execute_commands(local_arr[3]);
561 } else {
562 report_error("bad number of argument " local_nb " for @uref at line " NR);
566 # Generate text for a Cross-Reference into the document
567 function generate_cross_reference(args, cmd, local_nb, local_arr, local_i) {
568 local_nb = split(args, local_arr, ",");
569 if ((local_nb < 1) || (local_nb > 5)) {
570 report_error("bad number of argument " local_nb " for @" cmd " at line " NR);
573 local_arr[1] = execute_commands(local_arr[1]);
574 for (local_i = 1; local_i <= local_nb; local_i++) {
575 gsub(/^[ \t]+/, "", local_arr[local_i]);
576 gsub(/[ \t]*$/, "", local_arr[local_i]);
579 if (local_arr[1] == "") {
580 report_error("no node name specified in @" cmd " at line " NR);
583 if (local_arr[4] != "") {
584 # If it is a link to an external documentation, do not create an xref_table entry
585 local_i = "section [" local_arr[1] "]";
586 if (local_arr[5] != "") {
587 local_i = local_i " in " execute_commands("@cite{" local_arr[5] "}");
588 } else {
589 local_i = local_i " from " execute_commands("@file{" local_arr[4] "}");
591 return local_i;
594 # Build the string to be displayed
595 if (local_arr[3] != "") {
596 local_i = "section [" execute_commands(local_arr[3]) "]";
597 } else {
598 local_i = "section [" local_arr[1] "]";
601 xref_idcount++;
602 xref_table[xref_idcount] = local_arr[1];
603 xref_defline[xref_idcount] = NR;
604 local_i = local_i sprintf(", at line @x%02X@", xref_idcount);
605 return local_i;
608 # Generate a line with the name of an author
609 # note, we assume the name(s) always fit on a line
610 function generate_author_line(name, local_offset, local_attach_to_par) {
611 if (!cond_state) { return; }
613 local_attach_to_par = (par_nb_words > 0);
615 generate_paragraph();
617 if (par_mode == "titlepage") {
618 name = "-- " name " --";
619 local_offset = int((76 - length(name)) / 2);
620 if (local_offset < 2) { local_offset = 2; }
621 write_line("");
622 write_line(gen_underline(-1, local_offset) name);
624 } else if (par_mode == "quotation") {
625 name = "-- " name;
626 local_offset = int((line_length - length(line_prefix) - length(name)) * 2/3);
627 if (local_offset < length(line_prefix) + 2) { local_offset = length(line_prefix) + 2; }
628 if (!local_attach_to_par) { write_line(""); }
629 write_line(line_prefix gen_underline(-1, local_offset) name);
631 } else {
632 report_error("command @author used in an inappropriate mode (" par_mode ") at line " NR);
636 # Add the specified line to the curren paragraph being built, do not print anything yet
637 function add_text_to_paragraph(line) {
638 nb = split(line, words, /[ \t]+/);
639 for (i = 1; i <= nb; i++) {
640 if (words[i] != "") {
641 par_word[par_nb_words++] = words[i];
646 # Print the paragraph from all the lines read so far
647 function generate_paragraph( local_prefix, local_line, local_length,
648 idx_word_start, idx_word_end, local_i) {
649 if (par_nb_words <= 0) { return; }
651 local_line = line_prefix;
653 if (par_mode == "list") {
654 if (list_item_wants_sepline && !list_is_first_item) {
655 write_line("");
657 list_is_first_item = 0;
658 list_item_wants_sepline = 0;
659 if (!par_indent) {
660 local_prefix = item_list_mark " ";
661 while (length(local_prefix) < 5) { local_prefix = " " local_prefix; }
662 local_line = substr(local_line, 1, length(local_line) - 5) local_prefix;
665 } else if (par_mode == "enum") {
666 if (list_item_wants_sepline && !list_is_first_item) {
667 write_line("");
669 list_is_first_item = 0;
670 list_item_wants_sepline = 0;
671 if (!par_indent) {
672 local_prefix = " " item_list_mark ". ";
673 local_line = substr(local_line, 1, length(local_line) - 5) local_prefix;
675 # Increment the enumeration counter for the next item now
676 if (item_list_mark + 0 == item_list_mark) {
677 item_list_mark++;
678 } else {
679 local_i = index("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", item_list_mark);
680 if (local_i == 0) {
681 report_error("value \"" item_list_mark "\" is not supported for enumerated list - invalid @enumerate argument or list too long?");
683 item_list_mark = substr("BCDEFGHIJKLMNOPQRSTUVWXYZ!bcdefghijklmnopqrstuvwxyz!", local_i, 1);
687 } else if (par_mode == "table") {
688 if (list_item_wants_sepline && !list_is_first_item) {
689 write_line("");
691 list_is_first_item = 0;
692 list_item_wants_sepline = 0;
694 } else if (par_mode == "titlepage") {
695 write_line("");
697 } else if (par_mode == "par") {
698 write_line("");
699 if (par_indent) {
700 local_line = local_line " ";
703 } else if (par_mode == "quotation") {
704 write_line("");
705 # There is no extra indentation of paragraphs in this mode
707 } else {
708 report_error("paragraph mode \"" par_mode "\" is not supported in generate_paragraph (line " NR ")");
711 # Split the paragraph in lines
712 idx_word_start = 0;
713 while (idx_word_start < par_nb_words) {
714 # First word is always printed, this makes sure that words too long for a line will
715 # always be printed, very likely on a line by themselfs
716 idx_word_end = idx_word_start;
717 local_length = length(local_line) + length(par_word[idx_word_start]);
718 idx_word_start++;
720 # See how many word we can fit on the line
721 while (idx_word_end < par_nb_words - 1) {
722 if (local_length + 1 + length(par_word[idx_word_end + 1]) > line_length) { break; }
723 idx_word_end++;
724 local_length = local_length + 1 + length(par_word[idx_word_end]);
727 # Put all those words on the current line with the appropriate justification
728 if (par_justify == "right") {
729 local_line = local_line gen_underline(-1, line_length - local_length) par_word[idx_word_start - 1];
730 while (idx_word_start <= idx_word_end) {
731 local_line = local_line " " par_word[idx_word_start++];
733 } else {
734 if ((par_justify == "left") || (idx_word_end == par_nb_words - 1) ||
735 (local_length >= line_length) || (idx_word_end < idx_word_start)) {
736 local_line = local_line par_word[idx_word_start - 1];
737 while (idx_word_start <= idx_word_end) {
738 local_line = local_line " " par_word[idx_word_start++];
740 } else {
741 # We calculate the ideal size of a space (as a real number) which would
742 # make all the words perfectly fill the line, the formula being
743 # ideal size = 1 + needed_extra_spaces / number_of_spaces_in_line
744 ideal_space_length = 1 + (line_length - local_length) / (idx_word_end - idx_word_start + 1);
745 count_spaces = 0;
746 for (local_i = idx_word_start; local_i <= idx_word_end; local_i++) {
747 count_spaces = count_spaces + ideal_space_length;
748 word_space[local_i] = gen_underline(-1, int(count_spaces + 0.5));
749 count_spaces = count_spaces - length(word_space[local_i]);
752 local_line = local_line par_word[idx_word_start - 1];
753 while (idx_word_start <= idx_word_end) {
754 local_line = local_line word_space[idx_word_start] par_word[idx_word_start++];
759 write_line(local_line);
761 # Reset for next line
762 local_line = line_prefix;
764 par_nb_words = 0;
765 par_indent = 1;
768 # Replace commands by text in the line, return the result
769 function execute_commands(line, replaced_line, command) {
770 replaced_line = "";
771 while (1) {
772 idx = match(line, /@([a-zA-Z]+|.)/);
773 if (idx == 0) { break; }
775 # Separate the command and its arguments from the rest of the line
776 replaced_line = replaced_line substr(line, 1, idx - 1);
777 command = substr(line, idx + 1, RLENGTH - 1);
778 line = substr(line, idx + RLENGTH);
780 if (line ~ /^\{/) {
781 # Command has argument(s), extract them
782 brace_count = 0;
783 for (i = 1; i <= length(line); i++) {
784 if (substr(line, i, 1) == "{") {
785 brace_count++;
787 if (substr(line, i, 1) == "}") {
788 brace_count--;
789 if (brace_count == 0) { break; }
792 if (brace_count != 0) {
793 report_error("closing brace not found for command \"@" command "\", at line " NR);
796 cmdargs = substr(line, 2, i-2);
797 line = substr(line, i + 1);
799 } else {
800 # Command does not have arguments, discard the spaces used to separate it
801 # from the next text
802 cmdargs = "";
803 sub(/^[ \t]+/, "", line);
806 # Commands generating "special" characters #################################
807 if (command == "@") {
808 replaced_line = replaced_line "@";
810 } else if (command == "bullet") {
811 replaced_line = replaced_line "*";
813 } else if (command == "copyright") {
814 replaced_line = replaced_line "(c)";
816 } else if (command == "minus") {
817 replaced_line = replaced_line "-";
819 } else if (command == "registeredsymbol") {
820 replaced_line = replaced_line "(r)";
822 } else if (command == "today") {
823 # Make sure the date will be in english (we use "C" because it not certain
824 # that the English locale is enabled on the machine of the user)
825 replaced_line = replaced_line "'"`LANG=C date '+%d %B %Y' | sed -e 's,^0,,' `"'";
827 # Commands to display text in a special style ##############################
828 } else if (command == "asis") {
829 line = cmdargs line;
831 } else if (command == "b") { # bold
832 line = "*" cmdargs "*" line;
834 } else if (command == "emph") {
835 line = cmdargs line;
837 } else if ((command == "code") ||
838 (command == "command") ||
839 (command == "env") ||
840 (command == "option") ||
841 (command == "var")) {
842 # Should be in fixed-spacing font; printed with single-quotes
843 line = "'\''" cmdargs "'\''" line;
845 } else if (command == "i") { # italic
846 line = "_" cmdargs "_" line;
848 } else if (command == "email") {
849 line = "<" cmdargs ">" line;
851 } else if (command == "file") {
852 line = "\"" cmdargs "\"" line;
854 } else if (command == "key") {
855 line = "<" cmdargs ">" line;
857 } else if (command == "r") { # roman font
858 line = cmdargs line;
860 } else if (command == "sc") {
861 # Small-Caps, keep as-is in plain text
862 line = cmdargs line;
864 } else if (command == "t") { # typewriter-like
865 line = cmdargs line;
867 # References to other places ###############################################
868 } else if (command == "anchor") {
869 if (anchor_address[cmdargs] != "") {
870 report_error("anchor \"" cmdargs "\" is redefined at line " NR ", previous definition at line " anchor_defline[cmdargs]);
872 # Set a -1 offset to compensate for the anchor being after the actual target
873 anchor_address[cmdargs] = line_number - 1;
874 anchor_defline[cmdargs] = NR;
876 } else if (command == "cite") {
877 # Reference to external document, we cannot do much
878 line = cmdargs line;
880 } else if (command == "pxref") {
881 replaced_line = replaced_line "see " generate_cross_reference(cmdargs, command);
883 } else if (command == "ref") {
884 replaced_line = replaced_line generate_cross_reference(cmdargs, command);
886 } else if (command == "uref") {
887 replaced_line = replaced_line generate_url_reference(cmdargs);
889 } else if (command == "xref") {
890 replaced_line = replaced_line " See " generate_cross_reference(cmdargs, command);
892 # Variable and Conditional commands ########################################
893 } else if (command == "value") {
894 if (variable[cmdargs] == "") {
895 report_error("variable '" cmdargs "' is unknow, for @value at line " NR);
897 line = variable[cmdargs] line;
899 # Miscelleanous commands ###################################################
900 } else if (command == "c") {
901 # Comments: ignore everything to the end of line
902 line = "";
904 } else {
905 report_error("unknow command @" command " at line " NR);
909 return (replaced_line line);
912 # Handle appropriately the "@end xxx"
913 function process_end(line) {
914 if (line == cond_names[cond_level]) {
915 end_conditional(line);
916 return;
918 if (line == "copying") {
919 generate_paragraph();
920 redirect_out = "no";
922 } else if (line == "enumerate") {
923 generate_paragraph();
924 par_mode_pop("enum");
925 par_indent = 1;
927 } else if (line == "example") {
928 generate_paragraph();
929 par_mode_pop("example");
930 par_indent = 1;
932 } else if (line == "flushleft") {
933 generate_paragraph();
934 par_mode_pop(par_mode);
935 par_indent = 1;
937 } else if (line == "flushright") {
938 generate_paragraph();
939 par_mode_pop(par_mode);
940 par_indent = 1;
942 } else if (line == "itemize") {
943 generate_paragraph();
944 par_mode_pop("list");
945 par_indent = 1;
947 } else if (line == "quotation") {
948 generate_paragraph();
949 par_mode_pop("quotation");
950 par_indent = 1;
952 } else if ((line == "table") || (line == "ftable") || (line == "vtable")) {
953 generate_paragraph();
954 par_mode_pop("table");
955 par_indent = 1;
957 } else if (line == "titlepage") {
958 generate_page_break();
959 par_mode_pop("titlepage");
960 par_indent = 0;
962 } else {
963 report_error("unknow command @end " line " at line " NR);
967 BEGIN {
968 # Count the lines generated for the Table of Content
969 line_number = 0;
971 # To perform some basic checks on the file
972 top_was_found = 0;
973 bye_marker_found = 0;
975 # Paragraph generation parameters
976 par_mode_count = 0;
977 par_mode = "par";
978 par_nb_words = 0;
979 par_indent = 1;
980 par_justify = "justify";
981 redirect_out = "no";
982 line_length = 76;
983 line_prefix = "";
985 # To handle conditional code
986 cond_level = 0;
987 cond_state = 1;
989 # Number of entries in the Table of Content
990 toc_count = 0;
991 toc_file = "'"$toc_file"'";
993 # File to generate for the Cross-Reference tracking
994 xref_file= "'"$xref_file"'";
996 # Define a custom variable so it is possible to differentiate between
997 # texi2any and this script
998 variable["cctexi2txt"] = "1.0";
1000 # Variables inherited from the command line'"$var_defs"'
1003 # First line is special, we always ignore it
1004 (NR == 1) { next; }
1006 /^[ \t]*@/ {
1007 # Treat the special commands that are supposed to be on a line by themselves
1008 idx = match($0, /^@([a-zA-Z]+)/);
1009 if (idx != 0) {
1010 # Remove the command from current line
1011 command = substr($0, idx + 1, RLENGTH - 1);
1012 line = substr($0, idx + 1 + RLENGTH);
1013 sub(/^[ \t]+/, "", line);
1015 # Commands for structuring the document ####################################
1016 if (command == "chapter") {
1017 new_section(1, execute_commands(line), 1);
1018 next;
1020 } else if (command == "section") {
1021 new_section(2, execute_commands(line), 1);
1022 next;
1024 } else if (command == "subsection") {
1025 new_section(3, execute_commands(line), 1);
1026 next;
1028 } else if (command == "subsubsection") {
1029 new_section(4, execute_commands(line), 1);
1030 next;
1032 } else if (command == "node") {
1033 new_node(execute_commands(line));
1034 next;
1036 } else if (command == "top") {
1037 # This is mandatory for "info" format, but useless for plain text
1038 if (top_was_found > 0) {
1039 report_error("command @top at line " NR " but was already found at line " top_was_found);
1041 top_was_found = NR;
1042 next;
1044 } else if (command == "unnumbered") {
1045 new_section(1, execute_commands(line), 0);
1046 next;
1048 # Commands for content in the Title Page ###################################
1049 } else if (command == "author") {
1050 generate_author_line(execute_commands(line));
1051 next;
1053 } else if (command == "subtitle") {
1054 generate_title_page_subtitle(execute_commands(line));
1055 next;
1057 } else if (command == "title") {
1058 generate_title_page_title(execute_commands(line));
1059 next;
1061 # Commands changing the way paragraph are displayed ########################
1062 } else if (command == "copying") {
1063 generate_paragraph();
1064 redirect_out = "copyright";
1065 copyright_count = 0;
1066 next;
1068 } else if (command == "end") {
1069 process_end(line);
1070 next;
1072 } else if (command == "enumerate") {
1073 if (cond_state) {
1074 generate_paragraph();
1075 start_item_list(line, "enum", "1");
1077 next;
1079 } else if (command == "example") {
1080 if (cond_state) {
1081 generate_paragraph();
1082 write_line("");
1083 par_mode_push("example");
1084 line_prefix = line_prefix " ";
1086 next;
1088 } else if (command == "flushleft") {
1089 if (cond_state) {
1090 generate_paragraph();
1091 par_mode_push(par_mode);
1092 par_justify = "left";
1093 par_indent = 0;
1095 next;
1097 } else if (command == "flushright") {
1098 if (cond_state) {
1099 generate_paragraph();
1100 par_mode_push(par_mode);
1101 par_justify = "right";
1102 par_indent = 0;
1104 next;
1106 } else if (command == "itemize") {
1107 if (cond_state) {
1108 generate_paragraph();
1109 start_item_list(line, "list", "*");
1111 next;
1113 } else if (command == "menu") {
1114 generate_paragraph();
1115 discard_block(command);
1116 next;
1118 } else if (command == "quotation") {
1119 if (cond_state) {
1120 generate_paragraph();
1121 par_mode_push("quotation");
1122 line_prefix = line_prefix " ";
1123 line_length = line_length - 4;
1124 if (line != "") {
1125 add_text_to_paragraph(execute_commands(line));
1126 # We add the ":" to the last word because we count on the function
1127 # "add_text_to_paragraph" to remove the trailing spaces on the line
1128 # first, which would not have happened if we just had appended the ":"
1129 # to the argument in the function call
1130 par_word[par_nb_words - 1] = par_word[par_nb_words - 1] ":";
1131 line = "";
1134 next;
1136 } else if ((command == "table") ||
1137 (command == "ftable") ||
1138 (command == "vtable")) {
1139 # "ftable" and "vtable" are the same as "table" except they are adding automatically
1140 # the item to the appropriate Index (respectively Function and Variable indexes).
1141 # As we do not generate index in the text file, we just treat them identically
1142 if (cond_state) {
1143 generate_paragraph();
1144 par_mode_push("table");
1145 list_is_first_item = 1;
1146 list_item_wants_sepline = 0;
1147 par_indent = 1;
1148 line_prefix = line_prefix " ";
1149 gsub(/[ \t]/, "", line);
1150 if (line !~ /^@[a-z][a-z]*$/) {
1151 report_error("invalid usage of @table, expecting a single style-changing command");
1153 item_list_mark = line;
1154 write_line("");
1156 next;
1158 } else if (command == "titlepage") {
1159 generate_title_page();
1160 next;
1162 # Commands generating text automacitally ###################################
1163 } else if (command == "contents") {
1164 if (cond_state) {
1165 generate_paragraph();
1166 write_line("");
1167 write_line("");
1168 print "@table_of_content@";
1170 next;
1172 } else if (command == "insertcopying") {
1173 if (cond_state) {
1174 generate_paragraph();
1175 # The copying block was already formatted, we just have to print it as-is
1176 for (i = 0; i < copyright_count; i++) {
1177 write_line(copyright_lines[i]);
1180 next;
1182 } else if (command == "page") {
1183 generate_page_break();
1184 next;
1186 } else if (command == "sp") {
1187 if (cond_state) {
1188 generate_paragraph();
1189 while (line > 0) {
1190 write_line("");
1191 line--;
1194 next;
1196 } else if (command == "vskip") {
1197 # Silently ignore, this is just for TeX
1198 if (cond_state) {
1199 generate_paragraph();
1201 next;
1203 # Variable and Conditional commands ########################################
1204 } else if (command == "ifdocbook") { start_conditional(command, 0); line = ""; next;
1205 } else if (command == "ifhtml") { start_conditional(command, 0); line = ""; next;
1206 } else if (command == "ifinfo") { start_conditional(command, 1); line = ""; next; # "for historical compatibility"
1207 } else if (command == "ifplaintext") { start_conditional(command, 1); line = ""; next;
1208 } else if (command == "iftex") { start_conditional(command, 0); line = ""; next;
1209 } else if (command == "ifxml") { start_conditional(command, 0); line = ""; next;
1211 } else if (command == "ifnotdocbook") { start_conditional(command, 1); line = ""; next;
1212 } else if (command == "ifnothtml") { start_conditional(command, 1); line = ""; next;
1213 } else if (command == "ifnotinfo") { start_conditional(command, 0); line = ""; next; # "for historical compatibility"
1214 } else if (command == "ifnotplaintext") { start_conditional(command, 0); line = ""; next;
1215 } else if (command == "ifnottex") { start_conditional(command, 1); line = ""; next;
1216 } else if (command == "ifnotxml") { start_conditional(command, 1); line = ""; next;
1218 } else if (command == "ifclear") { start_conditional(command, (variable[line] == "")); next;
1219 } else if (command == "ifset") { start_conditional(command, (variable[line] != "")); next;
1221 } else if (command == "clear") {
1222 if (cond_state) {
1223 variable[ execute_commands(line) ] = "";
1225 next;
1227 } else if (command == "set") {
1228 if (cond_state) {
1229 set_variable(line);
1231 next;
1233 # Miscelleanous commands ###################################################
1234 } else if (command == "bye") {
1235 # Mark the end of file, we are supposed to ignore everything after
1236 if (cond_state) {
1237 generate_paragraph();
1238 while (getline != 0) { }
1239 bye_marker_found = 1;
1241 next;
1243 } else if (command == "c") {
1244 # Comments: ignore everything to the end of line
1245 next;
1247 } else if (command == "errormsg") {
1248 print "Error: " execute_commands(cmdargs) > "/dev/stderr";
1249 print " (from \"'"$input_file"'\", line " NR ")" > "/dev/stderr";
1250 bye_marker_found = 1;
1251 exit 4;
1253 } else if (command == "finalout") {
1254 # Nothing to do, we are not generating anything in output file about long lines
1255 next;
1257 } else if (command == "ignore") {
1258 # These are multi-lines comments
1259 discard_block(command);
1260 next;
1262 } else if (command == "indent") {
1263 par_indent = 1;
1264 if (line == "") { next; }
1265 $0 = line;
1267 } else if (command == "noindent") {
1268 par_indent = 0;
1269 if (line == "") { next; }
1270 $0 = line;
1272 } else if (command == "setfilename") {
1273 # Should set the output file name automatically
1274 # at current time, we just ignore it
1275 next;
1277 } else if (command == "settitle") {
1278 # This is used for page headers
1279 # in a plain text file, it is useless
1280 next;
1283 # Commands that were not recognised here may be commands that can be used
1284 # anywhere in a line but happenned to be at the beginning of the line this
1285 # time, we do nothing so they will be processed by "execute_commands"
1289 /@item/ {
1290 # We treat @item specially because it may generate more than 1 paragraph
1291 if (!cond_state) { next; }
1293 if (par_mode == "table") {
1294 generate_item_in_table($0);
1295 next;
1296 } else if ((par_mode != "list") && (par_mode != "enum")) {
1297 report_error("found @item at line " NR " but not inside an @itemize");
1300 while (1) {
1301 idx = match($0, /@item/);
1302 if (idx == 0) { break; }
1304 # We generate paragraph with all the text seen so far, which is part of
1305 # the previous item
1306 add_text_to_paragraph(substr($0, 1, idx - 1));
1307 generate_paragraph();
1308 $0 = substr($0, idx + 5);
1310 # When an item is found, we clear "par_ident" to actually place the item
1311 # mark on the next paragragh
1312 par_indent = 0;
1315 # If the item is on a line by itself, stop processing the line to avoid
1316 # skipping lines more than necessary
1317 if (/^[ \t]*$/) { next; }
1320 # Non-empty lines are added to the current paragraph
1322 if (!cond_state) { next; }
1324 if ((par_mode == "list") ||
1325 (par_mode == "enum") ||
1326 (par_mode == "par") ||
1327 (par_mode == "table") ||
1328 (par_mode == "titlepage") ||
1329 (par_mode == "quotation")) {
1330 if (/^[ \t]*$/) {
1331 # Empty lines separate paragraphs
1332 generate_paragraph();
1333 # in list of items, they also tell us that user prefers an aerated list
1334 list_item_wants_sepline = 1;
1335 } else {
1336 add_text_to_paragraph(execute_commands($0));
1339 } else if (par_mode == "example") {
1340 # Line is printed unmodified, not split and not merged, but with an indentation
1341 $0 = line_prefix execute_commands($0);
1342 sub(/[ \t]*$/, "");
1343 write_line($0);
1345 } else {
1346 report_error("paragraph mode \"" par_mode "\" is not supported for line processing (line " NR ")");
1350 END {
1351 if (!bye_marker_found) {
1352 report_error("command \"@bye\" missing at end of file");
1354 if (!top_was_found) {
1355 report_error("command \"@top\" was not found in the file");
1358 # Count the number of lines that the ToC will occupy
1359 # we assume the ToC is at the beginning, so all sections will be shifted
1360 # by this number of lines down
1361 toc_nb_lines = 0;
1362 for (i = 1; i <= toc_count; i++) {
1363 if ((i > 1) && (toc_entry_level[i] == 1)) {
1364 toc_nb_lines++;
1366 toc_nb_lines++;
1369 # Generate the ToC
1370 for (i = 1; i <= toc_count; i++) {
1371 if ((i > 1) && (toc_entry_level[i] == 1)) {
1372 print "" > toc_file;
1375 $0 = " " toc_entry_name[i] " ";
1376 if (length($0) % 2) { $0 = $0 " "; }
1377 while (length($0) < 76 - 4) {
1378 $0 = $0 " .";
1381 target_line = toc_entry_line[i] + toc_nb_lines;
1383 $0 = substr($0, 1, (76 - 5) - length(target_line)) " " target_line;
1384 print > toc_file;
1387 # Generate the Cross-Reference line number update script
1388 print "# Generated Cross-Reference script for SED" > xref_file;
1389 for (i = 1; i <= xref_idcount; i++) {
1390 if (anchor_address[xref_table[i]] != "") {
1391 target_line = anchor_address[xref_table[i]] + toc_nb_lines;
1392 } else if (node_address[xref_table[i]] != "") {
1393 target_line = node_address[xref_table[i]] + toc_nb_lines;
1394 } else {
1395 report_error("cross reference to undefined node/anchor \"" xref_table[i] "\" found at line " xref_defline[i]);
1397 printf "s/@x%02X@/%5d/g\n", i, target_line > xref_file;
1400 ' "$input_file" > "$temp_file" || exit $?
1402 # Run awk for 2nd pass, if it fails also stop now without deleting temp files
1403 awk '
1404 /@table_of_content@/ {
1405 while (getline < "'"$toc_file"'") {
1406 print;
1408 next;
1410 { print }
1411 ' "$temp_file" | sed -f "$xref_file" > "$output_file" || exit $?
1413 # If all worked, remove the temp files
1414 rm -f "$temp_file"
1415 rm -f "$toc_file"
1416 rm -f "$xref_file"