1 /* Man page to help file converter
2 Copyright (C) 1994, 1995, 1998, 2000, 2001, 2002, 2003, 2004, 2005,
3 2007 Free Software Foundation, Inc.
4 2002 Andrew V. Samoilov
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
22 * \brief Source: man page to help file converter
35 #include "glibcompat.h"
37 #define BUFFER_SIZE 256
39 static int col
= 0; /* Current output column */
40 static int out_row
= 1; /* Current output row */
41 static int in_row
= 0; /* Current input row */
42 static int no_split_flag
= 0; /* Flag: Don't split section on next ".SH" */
43 static int skip_flag
= 0; /* Flag: Skip this section.
46 2 = title skipped, skipping text */
47 static int link_flag
= 0; /* Flag: Next line is a link */
48 static int verbatim_flag
= 0; /* Flag: Copy input to output verbatim */
49 static int node
= 0; /* Flag: This line is an original ".SH" */
51 static const char *c_out
; /* Output filename */
52 static FILE *f_out
; /* Output file */
54 static const char *c_in
; /* Current input filename */
56 static int indentation
; /* Indentation level, n spaces */
57 static int tp_flag
; /* Flag: .TP paragraph
58 1 = this line is .TP label,
59 2 = first line of label description. */
60 static char *topics
= NULL
;
63 char *node
; /* Section name */
64 char *lname
; /* Translated .SH, NULL if not translated */
69 static struct node nodes
;
70 static struct node
*cnode
; /* Current node */
72 #define MAX_STREAM_BLOCK 8192
75 * Read in blocks of reasonable size and make sure we read everything.
76 * Failure to read everything is an error, indicated by returning 0.
79 persistent_fread (void *data
, size_t len
, FILE *stream
)
82 size_t bytes_done
= 0;
83 char *ptr
= (char *) data
;
88 while (bytes_done
< len
) {
89 count
= len
- bytes_done
;
90 if (count
> MAX_STREAM_BLOCK
)
91 count
= MAX_STREAM_BLOCK
;
93 count
= fread (ptr
, 1, count
, stream
);
106 * Write in blocks of reasonable size and make sure we write everything.
107 * Failure to write everything is an error, indicated by returning 0.
110 persistent_fwrite (const void *data
, size_t len
, FILE *stream
)
113 size_t bytes_done
= 0;
114 const char *ptr
= (const char *) data
;
119 while (bytes_done
< len
) {
120 count
= len
- bytes_done
;
121 if (count
> MAX_STREAM_BLOCK
)
122 count
= MAX_STREAM_BLOCK
;
124 count
= fwrite (ptr
, 1, count
, stream
);
136 /* Report error in input */
138 print_error (const char *message
)
140 fprintf (stderr
, "man2hlp: %s in file \"%s\" on line %d\n", message
,
144 /* Do fopen(), exit if it fails */
146 fopen_check (const char *filename
, const char *flags
)
148 char tmp
[BUFFER_SIZE
];
151 f
= fopen (filename
, flags
);
153 g_snprintf (tmp
, sizeof (tmp
), "man2hlp: Cannot open file \"%s\"",
162 /* Do fclose(), exit if it fails */
164 fclose_check (FILE *f
)
167 perror ("man2hlp: File error");
172 perror ("man2hlp: Cannot close file");
177 /* Change output line */
183 fprintf (f_out
, "\n");
186 /* Calculate the length of string */
188 string_len (const char *buffer
)
190 static int anchor_flag
= 0; /* Flag: Inside hypertext anchor name */
191 static int link_flag
= 0; /* Flag: Inside hypertext link target name */
192 int backslash_flag
= 0; /* Flag: Backslash quoting */
193 int c
; /* Current character */
194 int len
= 0; /* Result: the length of the string */
198 if (c
== CHAR_LINK_POINTER
)
199 link_flag
= 1; /* Link target name starts */
200 else if (c
== CHAR_LINK_END
)
201 link_flag
= 0; /* Link target name ends */
202 else if (c
== CHAR_NODE_END
) {
203 /* Node anchor name starts */
205 /* Ugly hack to prevent loss of one space */
208 /* Don't add control characters to the length */
209 if (c
>= 0 && c
< 32)
211 /* Attempt to handle backslash quoting */
212 if (c
== '\\' && !backslash_flag
) {
217 /* Increase length if not inside anchor name or link target name */
218 if (!anchor_flag
&& !link_flag
)
220 if (anchor_flag
&& c
== ']') {
221 /* Node anchor name ends */
228 /* Output the string */
230 print_string (char *buffer
)
232 int len
; /* The length of current word */
233 int c
; /* Current character */
234 int backslash_flag
= 0;
236 /* Skipping lines? */
239 /* Copying verbatim? */
241 /* Attempt to handle backslash quoting */
244 if (c
== '\\' && !backslash_flag
) {
252 /* Split into words */
253 buffer
= strtok (buffer
, " \t\n");
254 /* Repeat for each word */
256 /* Skip empty strings */
258 len
= string_len (buffer
);
259 /* Change the line if about to break the right margin */
260 if (col
+ len
>= HELP_TEXT_WIDTH
)
262 /* Words are separated by spaces */
266 } else if (indentation
) {
267 while (col
++ < indentation
)
270 /* Attempt to handle backslash quoting */
273 if (c
== '\\' && !backslash_flag
) {
280 /* Increase column */
283 /* Get the next word */
284 buffer
= strtok (NULL
, " \t\n");
289 /* Like print_string but with printf-like syntax */
291 printf_string (const char *format
, ...)
294 char buffer
[BUFFER_SIZE
];
296 va_start (args
, format
);
297 g_vsnprintf (buffer
, sizeof (buffer
), format
, args
);
299 print_string (buffer
);
302 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
304 handle_node (char *buffer
, int is_sh
)
306 int len
, heading_level
;
308 /* If we already skipped a section, don't skip another */
309 if (skip_flag
== 2) {
312 /* Get the command parameters */
313 buffer
= strtok (NULL
, "");
314 if (buffer
== NULL
) {
315 print_error ("Syntax error: .SH: no title");
319 if (buffer
[0] == '"') {
321 len
= strlen (buffer
);
322 if (buffer
[len
- 1] == '"') {
327 /* Calculate heading level */
329 while (buffer
[heading_level
] == ' ')
331 /* Heading level must be even */
332 if (heading_level
& 1)
333 print_error ("Syntax error: .SH: odd heading level");
335 /* Don't start a new section */
337 print_string (buffer
);
341 } else if (skip_flag
) {
342 /* Skipping title and marking text for skipping */
345 buffer
+= heading_level
;
346 if (!is_sh
|| !node
) {
347 /* Start a new section, but omit empty section names */
349 fprintf (f_out
, "%c[%s]", CHAR_NODE_END
, buffer
);
354 /* Add section to the linked list */
358 cnode
->next
= malloc (sizeof (nodes
));
361 cnode
->node
= strdup (buffer
);
364 cnode
->heading_level
= heading_level
;
367 /* print_string() strtok()es buffer, so */
368 cnode
->lname
= strdup (buffer
);
369 print_string (buffer
);
373 } /* Start new section */
374 } /* Has parameters */
378 /* Convert character from the macro name to the font marker */
380 char_to_font (char c
)
384 return CHAR_FONT_NORMAL
;
386 return CHAR_FONT_BOLD
;
388 return CHAR_FONT_ITALIC
;
395 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
396 * Return 0 if the command wasn't recognized, 1 otherwise
399 handle_alt_font (char *buffer
)
407 if (strlen (buffer
) != 3)
410 if (buffer
[0] != '.')
413 font
[0] = char_to_font (buffer
[1]);
414 font
[1] = char_to_font (buffer
[2]);
416 /* Exclude names with unknown characters, .BB, .II and .RR */
417 if (font
[0] == 0 || font
[1] == 0 || font
[0] == font
[1])
420 p
= strtok (NULL
, "");
431 in_quotes
= !in_quotes
;
436 if (*p
== ' ' && !in_quotes
) {
438 /* Don't change font if we are at the end */
440 alt_state
= !alt_state
;
441 *w
++ = font
[alt_state
];
444 /* Skip more spaces */
454 /* Turn off attributes if necessary */
455 if (font
[alt_state
] != CHAR_FONT_NORMAL
)
456 *w
++ = CHAR_FONT_NORMAL
;
459 print_string (buffer
);
464 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
466 handle_tp_ip (int is_tp
)
478 /* Handle all the roff dot commands. See man groff_man for details */
480 handle_command (char *buffer
)
484 /* Get the command name */
485 strtok (buffer
, " \t");
487 if (strcmp (buffer
, ".SH") == 0) {
489 handle_node (buffer
, 1);
490 } else if (strcmp (buffer
, ".\\\"NODE") == 0) {
491 handle_node (buffer
, 0);
492 } else if (strcmp (buffer
, ".\\\"DONT_SPLIT\"") == 0) {
494 } else if (strcmp (buffer
, ".\\\"SKIP_SECTION\"") == 0) {
496 } else if (strcmp (buffer
, ".\\\"LINK2\"") == 0) {
497 /* Next two input lines form a link */
499 } else if ((strcmp (buffer
, ".PP") == 0)
500 || (strcmp (buffer
, ".P") == 0)
501 || (strcmp (buffer
, ".LP") == 0)) {
503 /* End of paragraph */
507 } else if (strcmp (buffer
, ".nf") == 0) {
508 /* Following input lines are to be handled verbatim */
512 } else if (strcmp (buffer
, ".I") == 0 || strcmp (buffer
, ".B") == 0
513 || strcmp (buffer
, ".SB") == 0) {
514 /* Bold text or italics text */
517 int backslash_flag
= 0;
520 * Causes the text on the same line or the text on the
521 * next line to appear in boldface font, one point
522 * size smaller than the default font.
525 /* FIXME: text is optional, so there is no error */
526 p
= strtok (NULL
, "");
528 print_error ("Syntax error: .I | .B | .SB : no text");
532 *buffer
= (buffer
[1] == 'I') ? CHAR_FONT_ITALIC
: CHAR_FONT_BOLD
;
534 /* Attempt to handle backslash quoting */
535 for (w
= &buffer
[1]; *p
; p
++) {
536 if (*p
== '\\' && !backslash_flag
) {
544 *w
++ = CHAR_FONT_NORMAL
;
546 print_string (buffer
);
547 } else if (strcmp (buffer
, ".TP") == 0) {
549 } else if (strcmp (buffer
, ".IP") == 0) {
551 } else if (strcmp (buffer
, ".\\\"TOPICS") == 0) {
554 ("Syntax error: .\\\"TOPICS must be first command");
557 buffer
= strtok (NULL
, "");
558 if (buffer
== NULL
) {
559 print_error ("Syntax error: .\\\"TOPICS: no text");
563 if (buffer
[0] == '"') {
565 len
= strlen (buffer
);
566 if (buffer
[len
- 1] == '"') {
571 topics
= strdup (buffer
);
572 } else if (strcmp (buffer
, ".br") == 0) {
575 } else if (strncmp (buffer
, ".\\\"", 3) == 0) {
577 } else if (strcmp (buffer
, ".TH") == 0) {
579 } else if (strcmp (buffer
, ".SM") == 0) {
580 /* Causes the text on the same line or the text on the
581 * next line to appear in a font that is one point
582 * size smaller than the default font. */
583 buffer
= strtok (NULL
, "");
585 print_string (buffer
);
586 } else if (handle_alt_font (buffer
) == 1) {
589 /* Other commands are ignored */
590 char warn_str
[BUFFER_SIZE
];
591 g_snprintf (warn_str
, sizeof (warn_str
),
592 "Warning: unsupported command %s", buffer
);
593 print_error (warn_str
);
598 static struct links
{
599 char *linkname
; /* Section name */
600 int line
; /* Input line in ... */
601 const char *filename
;
603 } links
, *current_link
;
606 handle_link (char *buffer
)
615 /* Old format link, not supported */
618 /* First part of new format link */
619 /* Bold text or italics text */
620 if (buffer
[0] == '.' && (buffer
[1] == 'I' || buffer
[1] == 'B'))
621 for (buffer
+= 2; *buffer
== ' ' || *buffer
== '\t'; buffer
++);
622 g_strlcpy (old
, buffer
, sizeof (old
));
626 /* Second part of new format link */
627 if (buffer
[0] == '.')
629 if (buffer
[0] == '\\')
631 if (buffer
[0] == '"')
633 len
= strlen (buffer
);
634 if (len
&& buffer
[len
- 1] == '"') {
638 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
639 amp
= strstr (old
, "\\&");
648 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START
, old
,
649 CHAR_LINK_POINTER
, buffer
, CHAR_LINK_END
, amp_arg
);
651 /* Add to the linked list */
653 current_link
->next
= malloc (sizeof (links
));
654 current_link
= current_link
->next
;
655 current_link
->next
= NULL
;
657 current_link
= &links
;
659 current_link
->linkname
= strdup (buffer
);
660 current_link
->filename
= c_in
;
661 current_link
->line
= in_row
;
667 main (int argc
, char **argv
)
669 int len
; /* Length of input line */
670 const char *c_man
; /* Manual filename */
671 const char *c_tmpl
; /* Template filename */
672 FILE *f_man
; /* Manual file */
673 FILE *f_tmpl
; /* Template file */
674 char buffer
[BUFFER_SIZE
]; /* Full input line */
676 char *outfile_buffer
; /* Large buffer to keep the output file */
677 long cont_start
; /* Start of [Contents] */
678 long file_end
; /* Length of the output file */
680 /* Validity check for arguments */
683 "Usage: man2hlp file.man template_file helpfile\n");
691 /* First stage - process the manual, write to the output file */
692 f_man
= fopen_check (c_man
, "r");
693 f_out
= fopen_check (c_out
, "w");
696 /* Repeat for each input line */
697 while (fgets (buffer
, BUFFER_SIZE
, f_man
)) {
698 char *input_line
; /* Input line without initial "\&" */
700 if (buffer
[0] == '\\' && buffer
[1] == '&')
701 input_line
= buffer
+ 2;
706 len
= strlen (input_line
);
707 /* Remove terminating newline */
708 if (input_line
[len
- 1] == '\n') {
714 /* Copy the line verbatim */
715 if (strcmp (input_line
, ".fi") == 0) {
718 print_string (input_line
);
721 } else if (link_flag
) {
722 /* The line is a link */
723 handle_link (input_line
);
724 } else if (buffer
[0] == '.') {
725 /* The line is a roff command */
726 handle_command (input_line
);
728 /* A normal line, just output it */
729 print_string (input_line
);
731 /* .TP label processed as usual line */
738 if (col
>= indentation
)
741 while (++col
< indentation
)
748 fclose_check (f_man
);
749 /* First stage ends here, closing the manual */
751 /* Second stage - process the template file */
752 f_tmpl
= fopen_check (c_tmpl
, "r");
755 /* Repeat for each input line */
757 while (fgets (buffer
, BUFFER_SIZE
, f_tmpl
)) {
759 if (*buffer
&& *buffer
!= '\n') {
760 cnode
->lname
= strdup (buffer
);
761 node
= strchr (cnode
->lname
, '\n');
767 node
= strchr (buffer
, CHAR_NODE_END
);
768 if (node
&& (node
[1] == '[')) {
769 char *p
= strchr (node
, ']');
771 if (strncmp (node
+ 1, "[main]", 6) == 0) {
777 cnode
->next
= malloc (sizeof (nodes
));
780 cnode
->node
= strdup (node
+ 2);
781 cnode
->node
[p
- node
- 2] = 0;
784 cnode
->heading_level
= 0;
791 fputs (buffer
, f_out
);
794 cont_start
= ftell (f_out
);
795 if (cont_start
<= 0) {
801 fprintf (f_out
, "\004[Contents]\n%s\n\n", topics
);
803 fprintf (f_out
, "\004[Contents]\n");
805 for (current_link
= &links
; current_link
&& current_link
->linkname
;) {
807 struct links
*next
= current_link
->next
;
809 if (strcmp (current_link
->linkname
, "Contents") == 0) {
812 for (cnode
= &nodes
; cnode
&& cnode
->node
; cnode
= cnode
->next
) {
813 if (strcmp (cnode
->node
, current_link
->linkname
) == 0) {
820 g_snprintf (buffer
, sizeof (buffer
), "Stale link \"%s\"",
821 current_link
->linkname
);
822 c_in
= current_link
->filename
;
823 in_row
= current_link
->line
;
824 print_error (buffer
);
826 free (current_link
->linkname
);
827 if (current_link
!= &links
)
832 for (cnode
= &nodes
; cnode
&& cnode
->node
;) {
833 char *node
= cnode
->node
;
834 struct node
*next
= cnode
->next
;
837 fprintf (f_out
, " %*s\001%s\002%s\003", cnode
->heading_level
,
838 "", cnode
->lname
? cnode
->lname
: node
, node
);
839 fprintf (f_out
, "\n");
849 file_end
= ftell (f_out
);
852 if ((file_end
<= 0) || (file_end
- cont_start
<= 0)) {
857 fclose_check (f_out
);
858 fclose_check (f_tmpl
);
859 /* Second stage ends here, closing all files, note the end of output */
862 * Third stage - swap two parts of the output file.
863 * First, open the output file for reading and load it into the memory.
865 f_out
= fopen_check (c_out
, "r");
867 outfile_buffer
= malloc (file_end
);
871 if (!persistent_fread (outfile_buffer
, file_end
, f_out
)) {
876 fclose_check (f_out
);
877 /* Now the output file is in the memory */
879 /* Again open output file for writing */
880 f_out
= fopen_check (c_out
, "w");
882 /* Write part after the "Contents" node */
883 if (!persistent_fwrite
884 (outfile_buffer
+ cont_start
, file_end
- cont_start
, f_out
)) {
889 /* Write part before the "Contents" node */
890 if (!persistent_fwrite (outfile_buffer
, cont_start
, f_out
)) {
895 free (outfile_buffer
);
896 fclose_check (f_out
);
897 /* Closing everything */