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
36 #define BUFFER_SIZE 256
38 static int col
= 0; /* Current output column */
39 static int out_row
= 1; /* Current output row */
40 static int in_row
= 0; /* Current input row */
41 static int no_split_flag
= 0; /* Flag: Don't split section on next ".SH" */
42 static int skip_flag
= 0; /* Flag: Skip this section.
45 2 = title skipped, skipping text */
46 static int link_flag
= 0; /* Flag: Next line is a link */
47 static int verbatim_flag
= 0; /* Flag: Copy input to output verbatim */
48 static int node
= 0; /* Flag: This line is an original ".SH" */
50 static const char *c_out
; /* Output filename */
51 static FILE *f_out
; /* Output file */
53 static const char *c_in
; /* Current input filename */
55 static int indentation
; /* Indentation level, n spaces */
56 static int tp_flag
; /* Flag: .TP paragraph
57 1 = this line is .TP label,
58 2 = first line of label description. */
59 static char *topics
= NULL
;
62 char *node
; /* Section name */
63 char *lname
; /* Translated .SH, NULL if not translated */
68 static struct node nodes
;
69 static struct node
*cnode
; /* Current node */
71 #define MAX_STREAM_BLOCK 8192
74 * Read in blocks of reasonable size and make sure we read everything.
75 * Failure to read everything is an error, indicated by returning 0.
78 persistent_fread (void *data
, size_t len
, FILE *stream
)
81 size_t bytes_done
= 0;
82 char *ptr
= (char *) data
;
87 while (bytes_done
< len
) {
88 count
= len
- bytes_done
;
89 if (count
> MAX_STREAM_BLOCK
)
90 count
= MAX_STREAM_BLOCK
;
92 count
= fread (ptr
, 1, count
, stream
);
105 * Write in blocks of reasonable size and make sure we write everything.
106 * Failure to write everything is an error, indicated by returning 0.
109 persistent_fwrite (const void *data
, size_t len
, FILE *stream
)
112 size_t bytes_done
= 0;
113 const char *ptr
= (const char *) data
;
118 while (bytes_done
< len
) {
119 count
= len
- bytes_done
;
120 if (count
> MAX_STREAM_BLOCK
)
121 count
= MAX_STREAM_BLOCK
;
123 count
= fwrite (ptr
, 1, count
, stream
);
135 /* Report error in input */
137 print_error (const char *message
)
139 fprintf (stderr
, "man2hlp: %s in file \"%s\" on line %d\n", message
,
143 /* Do fopen(), exit if it fails */
145 fopen_check (const char *filename
, const char *flags
)
147 char tmp
[BUFFER_SIZE
];
150 f
= fopen (filename
, flags
);
152 g_snprintf (tmp
, sizeof (tmp
), "man2hlp: Cannot open file \"%s\"",
161 /* Do fclose(), exit if it fails */
163 fclose_check (FILE *f
)
166 perror ("man2hlp: File error");
171 perror ("man2hlp: Cannot close file");
176 /* Change output line */
182 fprintf (f_out
, "\n");
185 /* Calculate the length of string */
187 string_len (const char *buffer
)
189 static int anchor_flag
= 0; /* Flag: Inside hypertext anchor name */
190 static int lc_link_flag
= 0; /* Flag: Inside hypertext link target name */
191 int backslash_flag
= 0; /* Flag: Backslash quoting */
192 int c
; /* Current character */
193 int len
= 0; /* Result: the length of the string */
197 if (c
== CHAR_LINK_POINTER
)
198 lc_link_flag
= 1; /* Link target name starts */
199 else if (c
== CHAR_LINK_END
)
200 lc_link_flag
= 0; /* Link target name ends */
201 else if (c
== CHAR_NODE_END
) {
202 /* Node anchor name starts */
204 /* Ugly hack to prevent loss of one space */
207 /* Don't add control characters to the length */
208 if (c
>= 0 && c
< 32)
210 /* Attempt to handle backslash quoting */
211 if (c
== '\\' && !backslash_flag
) {
216 /* Increase length if not inside anchor name or link target name */
217 if (!anchor_flag
&& !lc_link_flag
)
219 if (anchor_flag
&& c
== ']') {
220 /* Node anchor name ends */
227 /* Output the string */
229 print_string (char *buffer
)
231 int len
; /* The length of current word */
232 int c
; /* Current character */
233 int backslash_flag
= 0;
235 /* Skipping lines? */
238 /* Copying verbatim? */
240 /* Attempt to handle backslash quoting */
243 if (c
== '\\' && !backslash_flag
) {
251 /* Split into words */
252 buffer
= strtok (buffer
, " \t\n");
253 /* Repeat for each word */
255 /* Skip empty strings */
257 len
= string_len (buffer
);
258 /* Change the line if about to break the right margin */
259 if (col
+ len
>= HELP_TEXT_WIDTH
)
261 /* Words are separated by spaces */
265 } else if (indentation
) {
266 while (col
++ < indentation
)
269 /* Attempt to handle backslash quoting */
272 if (c
== '\\' && !backslash_flag
) {
279 /* Increase column */
282 /* Get the next word */
283 buffer
= strtok (NULL
, " \t\n");
288 /* Like print_string but with printf-like syntax */
290 printf_string (const char *format
, ...)
293 char buffer
[BUFFER_SIZE
];
295 va_start (args
, format
);
296 g_vsnprintf (buffer
, sizeof (buffer
), format
, args
);
298 print_string (buffer
);
301 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
303 handle_node (char *buffer
, int is_sh
)
305 int len
, heading_level
;
307 /* If we already skipped a section, don't skip another */
308 if (skip_flag
== 2) {
311 /* Get the command parameters */
312 buffer
= strtok (NULL
, "");
313 if (buffer
== NULL
) {
314 print_error ("Syntax error: .SH: no title");
318 if (buffer
[0] == '"') {
320 len
= strlen (buffer
);
321 if (buffer
[len
- 1] == '"') {
326 /* Calculate heading level */
328 while (buffer
[heading_level
] == ' ')
330 /* Heading level must be even */
331 if (heading_level
& 1)
332 print_error ("Syntax error: .SH: odd heading level");
334 /* Don't start a new section */
336 print_string (buffer
);
340 } else if (skip_flag
) {
341 /* Skipping title and marking text for skipping */
344 buffer
+= heading_level
;
345 if (!is_sh
|| !node
) {
346 /* Start a new section, but omit empty section names */
348 fprintf (f_out
, "%c[%s]", CHAR_NODE_END
, buffer
);
353 /* Add section to the linked list */
357 cnode
->next
= malloc (sizeof (nodes
));
360 cnode
->node
= strdup (buffer
);
363 cnode
->heading_level
= heading_level
;
366 /* print_string() strtok()es buffer, so */
367 cnode
->lname
= strdup (buffer
);
368 print_string (buffer
);
372 } /* Start new section */
373 } /* Has parameters */
377 /* Convert character from the macro name to the font marker */
379 char_to_font (char c
)
383 return CHAR_FONT_NORMAL
;
385 return CHAR_FONT_BOLD
;
387 return CHAR_FONT_ITALIC
;
394 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
395 * Return 0 if the command wasn't recognized, 1 otherwise
398 handle_alt_font (char *buffer
)
406 if (strlen (buffer
) != 3)
409 if (buffer
[0] != '.')
412 font
[0] = char_to_font (buffer
[1]);
413 font
[1] = char_to_font (buffer
[2]);
415 /* Exclude names with unknown characters, .BB, .II and .RR */
416 if (font
[0] == 0 || font
[1] == 0 || font
[0] == font
[1])
419 p
= strtok (NULL
, "");
430 in_quotes
= !in_quotes
;
435 if (*p
== ' ' && !in_quotes
) {
437 /* Don't change font if we are at the end */
439 alt_state
= !alt_state
;
440 *w
++ = font
[alt_state
];
443 /* Skip more spaces */
453 /* Turn off attributes if necessary */
454 if (font
[alt_state
] != CHAR_FONT_NORMAL
)
455 *w
++ = CHAR_FONT_NORMAL
;
458 print_string (buffer
);
463 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
465 handle_tp_ip (int is_tp
)
477 /* Handle all the roff dot commands. See man groff_man for details */
479 handle_command (char *buffer
)
483 /* Get the command name */
484 strtok (buffer
, " \t");
486 if (strcmp (buffer
, ".SH") == 0) {
488 handle_node (buffer
, 1);
489 } else if (strcmp (buffer
, ".\\\"NODE") == 0) {
490 handle_node (buffer
, 0);
491 } else if (strcmp (buffer
, ".\\\"DONT_SPLIT\"") == 0) {
493 } else if (strcmp (buffer
, ".\\\"SKIP_SECTION\"") == 0) {
495 } else if (strcmp (buffer
, ".\\\"LINK2\"") == 0) {
496 /* Next two input lines form a link */
498 } else if ((strcmp (buffer
, ".PP") == 0)
499 || (strcmp (buffer
, ".P") == 0)
500 || (strcmp (buffer
, ".LP") == 0)) {
502 /* End of paragraph */
506 } else if (strcmp (buffer
, ".nf") == 0) {
507 /* Following input lines are to be handled verbatim */
511 } else if (strcmp (buffer
, ".I") == 0 || strcmp (buffer
, ".B") == 0
512 || strcmp (buffer
, ".SB") == 0) {
513 /* Bold text or italics text */
516 int backslash_flag
= 0;
519 * Causes the text on the same line or the text on the
520 * next line to appear in boldface font, one point
521 * size smaller than the default font.
524 /* FIXME: text is optional, so there is no error */
525 p
= strtok (NULL
, "");
527 print_error ("Syntax error: .I | .B | .SB : no text");
531 *buffer
= (buffer
[1] == 'I') ? CHAR_FONT_ITALIC
: CHAR_FONT_BOLD
;
533 /* Attempt to handle backslash quoting */
534 for (w
= &buffer
[1]; *p
; p
++) {
535 if (*p
== '\\' && !backslash_flag
) {
543 *w
++ = CHAR_FONT_NORMAL
;
545 print_string (buffer
);
546 } else if (strcmp (buffer
, ".TP") == 0) {
548 } else if (strcmp (buffer
, ".IP") == 0) {
550 } else if (strcmp (buffer
, ".\\\"TOPICS") == 0) {
553 ("Syntax error: .\\\"TOPICS must be first command");
556 buffer
= strtok (NULL
, "");
557 if (buffer
== NULL
) {
558 print_error ("Syntax error: .\\\"TOPICS: no text");
562 if (buffer
[0] == '"') {
564 len
= strlen (buffer
);
565 if (buffer
[len
- 1] == '"') {
570 topics
= strdup (buffer
);
571 } else if (strcmp (buffer
, ".br") == 0) {
574 } else if (strncmp (buffer
, ".\\\"", 3) == 0) {
576 } else if (strcmp (buffer
, ".TH") == 0) {
578 } else if (strcmp (buffer
, ".SM") == 0) {
579 /* Causes the text on the same line or the text on the
580 * next line to appear in a font that is one point
581 * size smaller than the default font. */
582 buffer
= strtok (NULL
, "");
584 print_string (buffer
);
585 } else if (handle_alt_font (buffer
) == 1) {
588 /* Other commands are ignored */
589 char warn_str
[BUFFER_SIZE
];
590 g_snprintf (warn_str
, sizeof (warn_str
),
591 "Warning: unsupported command %s", buffer
);
592 print_error (warn_str
);
597 static struct links
{
598 char *linkname
; /* Section name */
599 int line
; /* Input line in ... */
600 const char *filename
;
602 } links
, *current_link
;
605 handle_link (char *buffer
)
614 /* Old format link, not supported */
617 /* First part of new format link */
618 /* Bold text or italics text */
619 if (buffer
[0] == '.' && (buffer
[1] == 'I' || buffer
[1] == 'B'))
620 for (buffer
+= 2; *buffer
== ' ' || *buffer
== '\t'; buffer
++);
621 g_strlcpy (old
, buffer
, sizeof (old
));
625 /* Second part of new format link */
626 if (buffer
[0] == '.')
628 if (buffer
[0] == '\\')
630 if (buffer
[0] == '"')
632 len
= strlen (buffer
);
633 if (len
&& buffer
[len
- 1] == '"') {
637 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
638 amp
= strstr (old
, "\\&");
647 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START
, old
,
648 CHAR_LINK_POINTER
, buffer
, CHAR_LINK_END
, amp_arg
);
650 /* Add to the linked list */
652 current_link
->next
= malloc (sizeof (links
));
653 current_link
= current_link
->next
;
654 current_link
->next
= NULL
;
656 current_link
= &links
;
658 current_link
->linkname
= strdup (buffer
);
659 current_link
->filename
= c_in
;
660 current_link
->line
= in_row
;
666 main (int argc
, char **argv
)
668 int len
; /* Length of input line */
669 const char *c_man
; /* Manual filename */
670 const char *c_tmpl
; /* Template filename */
671 FILE *f_man
; /* Manual file */
672 FILE *f_tmpl
; /* Template file */
673 char buffer
[BUFFER_SIZE
]; /* Full input line */
674 char *lc_node
= NULL
;
675 char *outfile_buffer
; /* Large buffer to keep the output file */
676 long cont_start
; /* Start of [Contents] */
677 long file_end
; /* Length of the output file */
679 /* Validity check for arguments */
682 "Usage: man2hlp file.man template_file helpfile\n");
690 /* First stage - process the manual, write to the output file */
691 f_man
= fopen_check (c_man
, "r");
692 f_out
= fopen_check (c_out
, "w");
695 /* Repeat for each input line */
696 while (fgets (buffer
, BUFFER_SIZE
, f_man
)) {
697 char *input_line
; /* Input line without initial "\&" */
699 if (buffer
[0] == '\\' && buffer
[1] == '&')
700 input_line
= buffer
+ 2;
705 len
= strlen (input_line
);
706 /* Remove terminating newline */
707 if (input_line
[len
- 1] == '\n') {
713 /* Copy the line verbatim */
714 if (strcmp (input_line
, ".fi") == 0) {
717 print_string (input_line
);
720 } else if (link_flag
) {
721 /* The line is a link */
722 handle_link (input_line
);
723 } else if (buffer
[0] == '.') {
724 /* The line is a roff command */
725 handle_command (input_line
);
727 /* A normal line, just output it */
728 print_string (input_line
);
730 /* .TP label processed as usual line */
737 if (col
>= indentation
)
740 while (++col
< indentation
)
747 fclose_check (f_man
);
748 /* First stage ends here, closing the manual */
750 /* Second stage - process the template file */
751 f_tmpl
= fopen_check (c_tmpl
, "r");
754 /* Repeat for each input line */
756 while (fgets (buffer
, BUFFER_SIZE
, f_tmpl
)) {
758 if (*buffer
&& *buffer
!= '\n') {
759 cnode
->lname
= strdup (buffer
);
760 lc_node
= strchr (cnode
->lname
, '\n');
766 lc_node
= strchr (buffer
, CHAR_NODE_END
);
767 if (lc_node
&& (lc_node
[1] == '[')) {
768 char *p
= strchr (lc_node
, ']');
770 if (strncmp (lc_node
+ 1, "[main]", 6) == 0) {
776 cnode
->next
= malloc (sizeof (nodes
));
779 cnode
->node
= strdup (lc_node
+ 2);
780 cnode
->node
[p
- lc_node
- 2] = 0;
783 cnode
->heading_level
= 0;
790 fputs (buffer
, f_out
);
793 cont_start
= ftell (f_out
);
794 if (cont_start
<= 0) {
800 fprintf (f_out
, "\004[Contents]\n%s\n\n", topics
);
802 fprintf (f_out
, "\004[Contents]\n");
804 for (current_link
= &links
; current_link
&& current_link
->linkname
;) {
806 struct links
*next
= current_link
->next
;
808 if (strcmp (current_link
->linkname
, "Contents") == 0) {
811 for (cnode
= &nodes
; cnode
&& cnode
->node
; cnode
= cnode
->next
) {
812 if (strcmp (cnode
->node
, current_link
->linkname
) == 0) {
819 g_snprintf (buffer
, sizeof (buffer
), "Stale link \"%s\"",
820 current_link
->linkname
);
821 c_in
= current_link
->filename
;
822 in_row
= current_link
->line
;
823 print_error (buffer
);
825 free (current_link
->linkname
);
826 if (current_link
!= &links
)
831 for (cnode
= &nodes
; cnode
&& cnode
->node
;) {
832 struct node
*next
= cnode
->next
;
833 lc_node
= cnode
->node
;
836 fprintf (f_out
, " %*s\001%s\002%s\003", cnode
->heading_level
,
837 "", cnode
->lname
? cnode
->lname
: lc_node
, lc_node
);
838 fprintf (f_out
, "\n");
848 file_end
= ftell (f_out
);
851 if ((file_end
<= 0) || (file_end
- cont_start
<= 0)) {
856 fclose_check (f_out
);
857 fclose_check (f_tmpl
);
858 /* Second stage ends here, closing all files, note the end of output */
861 * Third stage - swap two parts of the output file.
862 * First, open the output file for reading and load it into the memory.
864 f_out
= fopen_check (c_out
, "r");
866 outfile_buffer
= malloc (file_end
);
870 if (!persistent_fread (outfile_buffer
, file_end
, f_out
)) {
875 fclose_check (f_out
);
876 /* Now the output file is in the memory */
878 /* Again open output file for writing */
879 f_out
= fopen_check (c_out
, "w");
881 /* Write part after the "Contents" node */
882 if (!persistent_fwrite
883 (outfile_buffer
+ cont_start
, file_end
- cont_start
, f_out
)) {
888 /* Write part before the "Contents" node */
889 if (!persistent_fwrite (outfile_buffer
, cont_start
, f_out
)) {
894 free (outfile_buffer
);
895 fclose_check (f_out
);
896 /* Closing everything */