xref: /plan9/sys/src/cmd/gs/doc/gsdoc.el (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1;; gsdoc.el
2;;
3;; GNU emacs (19.34) functions to help working with the HTML form of
4;; Ghostscript documentation.
5;;
6;; Pete Kaiser 8 September 1998 V1.2
7;;		2 December 1999 V1.3	Correct improper "--" to "=="
8;;					in HTML marker comments
9;;============================================================
10;; One global key setting, which runs the function to bind some keys
11;; locally -- presumably in a buffer containing HTML code.  Plus that
12;; function itself.
13
14(global-set-key [?\C-\S-k]	'gskeys)
15
16(defun gskeys ()
17
18"Set the keys in this buffer to use with Ghostscript HTML docs."
19
20(interactive)
21(local-set-key [?\C-\S-b]	'gs-bold)
22(local-set-key [?\C-\S-c]	'gs-code)
23(local-set-key [?\C-\S-e]	'gs-emphatic)
24(local-set-key [?\C-\S-g]	'gs-get-anchor)
25(local-set-key [?\C-\S-h]	'gs-href)
26(local-set-key [?\C-\S-i]	'gs-italic)
27(local-set-key [?\C-\S-m]	'gs-mailto)
28(local-set-key [?\C-\S-n]	'gs-name)
29(local-set-key [?\C-\S-p]	'gs-put-anchor)
30(local-set-key [?\C-\S-q]	'gs-quote)
31(local-set-key [?\C-\S-r]	'gs-row-of-table)
32(local-set-key [?\C-\S-s]	'gs-selfref)
33(local-set-key [?\C-\S-t]	'gs-table)
34(local-set-key [?\C-\S-u]	'gs-tag)
35(local-set-key [?\C-\S-x]	'gs-example)
36)
37
38;;============================================================
39;; Each of these next few functions just wraps a region in a
40;; <TAG>...</TAG>, or two nested tags.  Where there are two, the first one
41;; is inner.  See the inner function ~gsregion.
42
43(defun gs-bold ()	"Make text strong (bold)."
44(interactive)
45(~gsregion "b"))
46
47(defun gs-code ()	"Make text strong code (bold TT)."
48(interactive)
49(~gsregion "tt")
50(~gsregion "b"))
51
52(defun gs-emphatic ()	"Make text emphatic (bold italic)."
53(interactive)
54(~gsregion "em")
55(~gsregion "b"))
56
57(defun gs-italic ()	"Make text italic."
58(interactive)
59(~gsregion "em"))
60
61;;============================================================
62
63(defun gs-quote ()
64
65"Indent a region with BLOCKQUOTE and separate it with empty lines from
66surrounding text."
67
68(interactive)
69
70(save-restriction (narrow-to-region (region-beginning) (region-end))
71    (goto-char (point-min)) (insert "\n\n")
72    (push-mark (1+ (point-min)) t)
73    (goto-char (point-max))
74    (~gsregion "blockquote")
75    (insert "\n\n")
76    )
77)
78
79;;============================================================
80
81(defun gs-example ()
82
83"Make an indented literatim example BLOCKQUOTE PRE and separate it with
84empty lines from surrounding text."
85
86(interactive)
87
88(save-restriction (narrow-to-region (region-beginning) (region-end))
89    (goto-char (point-min)) (insert "\n")
90    (push-mark (point-min) t)
91    (goto-char (point-max))
92    (~gsregion "pre")
93    (~gsregion "blockquote")
94    (insert "\n")
95    )
96)
97
98;;============================================================
99
100(defun gs-get-anchor ()
101
102"Beginning at the head of this line, pick up the next anchor name for later
103use along with its HTML file name.  This is useful when picking up an
104anchor name from a file in one buffer and using it in another buffer
105containing a different file."
106
107(interactive)
108
109;; From the beginning of this line find and pick up the next non-empty
110;; anchor, which might, of course not be right here -- though that's how it
111;; ought to be used, to pick up an anchor for immediate use.  The regular
112;; expression picks up only the name itself.
113
114(beginning-of-line)
115(re-search-forward "<a name=\"?\\([^\">]+\\)\"?></a>" nil t)
116(setq gs-anchor (buffer-substring (match-beginning 1) (match-end 1)))
117
118;; Get the name of this buffer, treating it as the filename.
119
120(setq gs-anchor-file (buffer-name))
121)
122
123;;============================================================
124
125(defun gs-href ()
126
127"Wrap a region in an empty link and leave point in the middle of the
128emptiness to write the link.  Maybe some day read the URL and put it
129there."
130
131(interactive)
132
133(save-restriction
134  (narrow-to-region (region-beginning) (region-end))
135  (goto-char (point-min)) (insert "<a href=\"#\">")
136  (setq HREF (- (point) 2))
137  (goto-char (point-max)) (insert "</a>")
138  (goto-char HREF)
139  )
140)
141
142;;============================================================
143
144(defun gs-mailto ()
145
146"Turn an address into a proper \"mailto:\" visually bracketed with <>."
147
148(interactive)
149
150(save-restriction
151  (narrow-to-region (region-beginning) (region-end))
152  (setq gs-address (buffer-substring (point-min) (point-max)))
153  (goto-char (point-min)) (insert "&lt;<a href=\"mailto:")
154  (goto-char (point-max)) (insert "\">" gs-address "</a>&gt;")
155  )
156)
157
158;;============================================================
159
160(defun gs-tag (Tag)
161
162"Bracket a region with some arbitrary tag read from the minibuffer, leaving
163point right after the opening word of the opening tag, and the end of the
164region at the end of the closing tag.  Leaving point there makes it
165possible, for instance, to enter additional matter in a <FONT> tag.  Get to
166the end of a region with ^x-^x."
167
168(interactive "*sTag: ")
169
170    (~gsregion Tag)
171    (exchange-point-and-mark) (forward-word 1)
172)
173
174;;============================================================
175
176(defun gs-toc ()
177
178"[Re]build the table of contents by picking up all the <Hn> lines and
179converting them to properly indented <UL> entries, placing the TOC within
180the standard TOC markers.  Note that several of the original Ghostscript
181HTML files have hand-modified TOCs, so it's wise to check before running
182this function.  It can be run from anywhere within the HTML file.
183
184This function relies on the specific format of the structure comments for
185the table of contents, which are set by the g~marker function used in
186defvars run when this package is loaded."
187
188(interactive)
189
190(setq g~html-buffer (buffer-name))
191
192(save-restriction (save-excursion
193  (widen)
194
195;; Since we're building the TOC, delete any current TOC.  Locate the place
196;; for the TOC using the standard markers, deleting everything between the
197;; TOC-beginning and TOC-end markers.  The new TOC is built entirely in the
198;; work buffer before being copied into the HTML buffer at that point.
199
200  (goto-char (point-min))
201  (search-forward g~toc-begin nil t)
202  (next-line 1) (beginning-of-line) (setq g~toc-insert (point))
203  (search-forward g~toc-end nil t)
204  (beginning-of-line) (delete-region g~toc-insert (point))
205
206;; Empty the work buffer by copying nothing into it.
207
208  (copy-to-buffer gs-work-buffer 1 1)
209
210;; Now collect all the following header lines into a buffer to work on
211;; them.  The later append-to-buffer needs point to be in the middle of the
212;; empty list, so go there before entering the work buffer.
213
214  (save-excursion (while (re-search-forward "^<h[1-6][^>]*>" nil t)
215    (beginning-of-line) (setq BOH (point))
216    (re-search-forward "</h[1-6]>\n" nil t)
217    (append-to-buffer gs-work-buffer BOH (point))
218    ))
219  (goto-char g~toc-insert)
220
221;; All the useful header lines should be in the work buffer now.
222
223  (save-excursion
224    (set-buffer gs-work-buffer)
225
226;; Formatting as list entries: insert <ul> when the level deepens and </ul>
227;; when it rises.
228
229    (goto-char (point-min))
230    (while (search-forward "</a>" nil t) (replace-match ""))
231    (goto-char (point-min))
232    (while (re-search-forward "</h[1-6]>" nil t) (replace-match "</a>"))
233    (goto-char (point-min))
234    (while (re-search-forward "<a name=\"" nil t) (replace-match "<a href=\"#"))
235
236;; Change <h[1-6]> to <li>, inserting <ul>...</ul> as needed.  Pick up the
237;; upmost level from the first header, usually <h1>, and save a copy to
238;; use to insert any terminating </ul>.
239
240    (goto-char (point-min))
241    (re-search-forward "^<h\\([1-6]\\)[^>]*>" nil t)
242    (setq First (string-to-number
243		 (buffer-substring (match-beginning 1) (match-end 1))))
244    (setq Previous First)
245    (replace-match "<li>" t t)
246
247;; Got the first one, now handle the rest.
248
249    (while (re-search-forward "^<h\\([1-6]\\)[^>]*>" nil t)
250      (setq This (string-to-number
251		  (buffer-substring (match-beginning 1) (match-end 1))))
252      (setq Hold This)
253      (replace-match "<li>" t t) (beginning-of-line)
254
255;; No point being too fancy with conditionals: the "while" statements here
256;; make at most one logically unnecessary test.
257
258      (while (> This Previous) (insert  "<ul>\n") (setq This (1- This)))
259      (while (< This Previous) (insert "</ul>\n") (setq This (1+ This)))
260      (setq Previous Hold)
261      )
262
263;; Done with the loop.  Clean up by inserting at the end any </ul> needed
264;; to get back to the top level.
265
266    (goto-char (point-max))
267    (while (> Previous First) (insert "</ul>\n") (setq Previous (1- Previous)))
268
269;; Finally add the trailing whitespace and leading whitespace and header line.
270
271    (insert "</ul></blockquote>\n\n")
272    (goto-char (point-min))
273    (insert "\n<h2>Table of contents</h2>\n\n<blockquote><ul>\n")
274
275;; The TOC is now entirely built in the work buffer.  Move it to where it's
276;; supposed to be in the original buffer.
277
278    (append-to-buffer g~html-buffer (point-min) (point-max))
279    )
280  ))
281)
282
283;;============================================================
284
285(defun gs-name ()
286
287"Insert a name anchor at point and leave point ready to enter the anchor's
288name.  Anchors are always empty (that is, <a name=...></a>)."
289
290(interactive)
291
292(insert "<a name=\"\"></a>")
293(backward-char 6)
294)
295
296;;============================================================
297
298(defun gs-put-anchor ()
299
300"Insert around the current region the last anchor picked up with
301gs-get-anchor.  This includes the HTML file name if the href is put in a
302file other than the anchor."
303
304(interactive)
305
306(save-restriction
307  (narrow-to-region (region-beginning) (region-end))
308  (goto-char (point-min))
309  (insert (concat
310    "<a href=\""
311
312;; Insert the filename (buffer name) picked up by gs-get-anchor only if
313;; this is a different buffer.
314
315    (if (string-equal gs-anchor-file (buffer-name)) "" gs-anchor-file)
316
317;; And finish unconditionally with the specific anchor name.
318
319    "#" gs-anchor "\">"))
320  (goto-char (point-max)) (insert "</a>"))
321)
322
323;;============================================================
324
325(defun gs-row-of-table ()
326
327"Set up a row of a table from the line containing point.
328
329Insert the right things at beginning and end, and in between convert tab
330and \"|\" into column breaks with a nonbreaking space in between -- which
331means that no entry can contain a tab or \"|\".  Format the HTML nicely
332for readability.
333
334Between each two substantive columns this function puts a column containing
335a single nonbreaking space to provide a visual break.  Generally in the
336first row of a table those columns should be given enough NBSPs to make
337the table look right on screen and when converted to text, but this has to
338be done by hand."
339
340(interactive)
341
342(save-restriction
343  (end-of-line) (setq EOL (point))
344  (beginning-of-line) (narrow-to-region (point) EOL)
345  (insert "<tr valign=top>\t<td>")
346  (while (re-search-forward "[|\t]" nil t)
347    (replace-match "\n\t<td>&nbsp;\n\t<td>" t t))
348  (goto-char (point-max))
349  )
350(next-line 1) (beginning-of-line)
351)
352
353;;============================================================
354
355(defun gs-selfref ()
356
357"Wrap an URL to make it its own link.  This is useful for links that should
358be visible when converted to text."
359
360(interactive)
361
362(save-restriction
363  (narrow-to-region (region-beginning) (region-end))
364  (goto-char (point-min))
365  (if (not (looking-at "http:\\|ftp:")) (insert "http://"))
366  (setq g~url (buffer-substring (point-min) (point-max)))
367  (goto-char (point-min))
368  (insert "<a href=\"")
369  (goto-char (point-max)) (insert "\">" g~url "</a>")
370  )
371)
372
373;;============================================================
374
375(defun gs-table ()
376
377"Set up an indented table around this region, leaving plenty of white space
378around the table within the HTML.  The number of columns in the table is
379hardcoded here as 3, so that number must be changed by hand if the table
380has more than 3 columns.  See gs-row-of-table for how rows are built: a
381table with N visible columns generally has 2N-1 HTML columns, including the
382columns that provide vertical white space."
383
384(interactive)
385
386(save-restriction
387  (narrow-to-region (region-beginning) (region-end))
388  (indent-rigidly (region-beginning) (region-end) -80)
389  (goto-char (point-min))
390  (insert (concat
391	"\n\n<blockquote><table cellpadding=0 cellspacing=0>\n"
392	"<tr><th colspan=3 bgcolor=\"#CCCC00\"><hr><font size=\"+1\">XXXXXXXXXX</font><hr>\n"
393	"<tr valign=bottom>\n"
394	"\t<th align=left>\n"
395	"\t<td>&nbsp;&nbsp;\n"
396	"\t<th align=left>\n"
397	"<tr>\t<td colspan=3><hr>\n"
398	))
399  (goto-char (point-max))
400  (insert "</table></blockquote>\n")
401  )
402)
403
404;;============================================================
405
406(defun gs-text-chars ()
407
408"Prepare text for inclusion in HTML by converting \"&\", \"<\", and \">\"  into
409their HTML special forms.  The function acts from point to end-of-region or
410end-of-buffer, whichever comes first.
411
412This function is NOT idempotent -- running it twice on the same text will
413certainly do the wrong thing, unless at first the text contained none of
414those characters."
415
416(interactive)
417(setq BEGIN (point))
418
419;; Replace significant characters: "&", "<", and ">".
420
421(while (search-forward "&" nil t) (replace-match "&amp;" t t))
422
423(goto-char BEGIN)
424(while (search-forward "<" nil t) (replace-match "&lt;" t t))
425
426(goto-char BEGIN)
427(while (search-forward ">" nil t) (replace-match "&gt;" t t))
428
429(goto-char BEGIN)
430)
431
432;;============================================================
433
434(defun gs-wrap-textfile ()
435
436"Prepare a text file for inclusion between <pre> and </pre>, then put a
437header and footer around it.  One would generally run this function on a
438buffer containing only an original text file; it is how the original
439history and news files were first prepared.  At this point it's likely to
440be most useful in preparing new sections for the news document."
441
442(interactive)
443
444(widen)
445
446;; First prepare the entire text by replacing special characters.
447
448(goto-char (point-min))
449(gs-text-chars)
450
451;; At the end of the file, end as-is text and add the standard footer.
452
453(goto-char (point-max))
454(backward-word 1) (next-line 1) (beginning-of-line)
455(delete-region (point) (point-max))
456(insert "\n</pre>\n")
457(insert-file "Footer.htm")
458
459;; At the beginning of the file, begin as-is text and delete everything
460;; up to the identity string (if any), saving the identity string.
461
462(goto-char (point-min))
463(insert "<pre>\n") (setq g~pre-point (point))
464(setq g~ID " [No pre-existing ID] ")
465(if (re-search-forward "^\\$Id:\\( [^ ]+ \\)\\$" nil t) (progn
466    (setq g~ID (buffer-substring (match-beginning 1) (match-end 1)))
467    (next-line 1) (beginning-of-line) (delete-region g~pre-point (point))
468    ))
469
470;; Insert the standard header and fill in the identity string.
471
472(goto-char (point-min)) (insert-file "Header.htm")
473(goto-char (point-min)) (search-forward "<!--" nil t)
474(delete-horizontal-space) (insert g~ID)
475(search-forward "<pre>\n" nil t)
476)
477
478;;============================================================
479
480(defun ~gsregion (Tag)
481
482"Tag a region, leaving point at its end and the region around the whole
483thing including the new surrounding tags; thus invoking this function twice
484successively makes the first invocation the inner tags and the second the
485outer.
486
487Not intended for interactive use; for that use gs-tag, which gives a little
488bit of additional service."
489
490(interactive)
491
492(if (not (= 0 (length Tag))) (save-restriction
493  (narrow-to-region (region-beginning) (region-end))
494  (goto-char (point-min)) (insert "<"  Tag ">")
495  (goto-char (point-max)) (insert "</" Tag ">")
496  (push-mark (point-min) t)
497  (goto-char (point-max))
498  )
499  )
500)
501
502;;============================================================
503
504(defun gs-structure ()
505
506"For historical interest only: add structuring commentary to a Ghostscript
507HTML file.  It's crude, but it did most of the work.  Future files will
508acquire their structure through plagiarism, like any other code.
509
510Now they've all been structured, and this function was used to do it.  The
511placement of table-of-contents lines never worked, because most of the
512files didn't yet have TOCS.  Now all files that should have TOCs have
513properly placed markers, but that's history."
514
515(interactive)
516
517(setq g~thisfile (buffer-name))
518
519(widen)
520(goto-char (point-min))
521
522;; Replace the RCS $Id if one can be found in exactly the right format, and
523;; otherwise insert one just after the title, along with a warning message.
524
525(if (re-search-forward "<!-- $Id: *\\([^ ]*\\) $ -->" nil t)
526    (progn
527      (setq Original (buffer-substring (match-beginning 1) (match-end 1)))
528      (replace-match g~thisfile t t nil 1)
529      )
530    (progn
531      (search-forward "</title>" nil t) (end-of-line)
532      (insert (concat "\n<!-- $Id: " g~thisfile " $ -->"))
533      (setq Original "(UNSET by gs-structure)")
534      )
535    )
536
537(end-of-line)
538(insert (concat "\n<!-- Originally: " Original " -->"))
539
540;; Place the visible header marker immediately after <BODY>.
541
542(re-search-forward "<body[^>]*>" nil t)
543    (end-of-line) (forward-char 1)
544    (insert (concat g~header-begin "\n\n"))
545
546;; Place the headline marker before the first <table> block.
547
548(search-forward "<table" nil t) (search-backward "\n\n" nil t)
549    (forward-word 1) (beginning-of-line)
550    (insert (concat g~headline-begin "\n\n"))
551
552;; After the first table block place the end-headline marker and both
553;; table-of-contents markers, without worrying where the table of contents
554;; really is.  The TOC markers can easily be moved by hand later.
555
556(search-forward "\n\n" nil t)
557    (backward-word 1) (end-of-line) (forward-char 1)
558    (insert (concat
559	"\n"
560	g~headline-end	"\n\n"
561	g~toc-begin	"\n\n"
562	g~toc-end	"\n\n"))
563
564;; The hints section begins with the first paragraph after where the TOC
565;; markers are placed, and ends with <HR>.  This isn't precise, and in fact
566;; fails for several files, but once again only an approximation is needed
567;; because it'll be edited by hand later.
568
569(search-forward "<p>" nil t) (beginning-of-line)
570    (insert (concat g~hint-begin "\n\n"))
571
572(search-forward "<hr>" nil t) (beginning-of-line)
573    (insert (concat g~hint-end "\n\n"))
574
575;; The visible header ends with (and includes) the first <HR>, and the
576;; contents begin immediately thereafter.
577
578(search-forward "<hr>\n" nil t)
579    (insert (concat "\n" g~header-end "\n\n"))
580
581(forward-word 1) (beginning-of-line)
582    (insert (concat g~contents-begin "\n\n"))
583
584;; The contents end before the final <HR> and the trailer begins
585;; immediately thereafter.
586
587(goto-char (point-max)) (search-backward "<hr>" nil t)
588    (backward-word 1) (end-of-line) (forward-char 1)
589    (insert (concat
590	"\n"
591	g~contents-end	"\n\n"
592	g~trailer-begin	"\n\n"))
593
594;; The trailer ends with </BODY>.
595
596(goto-char (point-max)) (search-backward "</body>" nil t)
597    (insert (concat "\n" g~trailer-end "\n\n"))
598
599;; We may have introduced trailing whitespace and extra empty lines.
600;; Remove them.
601
602(goto-char (point-min))
603(while (re-search-forward "[ \t\240\r]+$" nil t) (replace-match "" t t))
604(goto-char (point-min))
605(while (re-search-forward "\n\n\n+" nil t) (replace-match "\n\n" t t))
606
607)
608
609;;============================================================
610;; When this file is loaded into emacs, define the structure markers for GS
611;; HTML files.  These markers have two purposes: first, to make the HTML
612;; more readable, and second, to enable these functions to locate sections
613;; unambiguously (see gs-toc, the table of contents builder).  Note that
614;; the markers do not include LF.
615
616(defun g~marker (basic)
617
618"Build a complete Ghostscript HTML file marker from its text-only part.
619gs-toc relies entirely on this function, so if it's ever changed, gs-toc
620and existing markers would also have to be changed to keep pace.
621
622Intended only for initialization, not interactive use.
623
624All the existing files are now marked up, and since any future ones are
625(properly) likely to be created by plagiarism, it's difficult to imagine
626why anyone would want to change this unless they want to go to the trouble
627of coming up with a much more useful marking scheme."
628
629(interactive)
630
631(setq HEAD (concat "<!-- [" basic "] "))
632(concat HEAD
633    (substring
634    "====================================================================== -->"
635    (- (length HEAD) 80)
636    ))
637)
638
639;;============================================================
640;; Initialization code that must run after functions are defined.
641;;
642;; Look in a Ghostscript HTML file to see how these markers are used,
643;; generally
644;;
645;;	begin visible header
646;;		begin headline
647;;		end headline
648;;		begin table of contents
649;;		end table of contents
650;;		begin hint
651;;		end hint
652;;	end visible header
653;;	begin contents
654;;	end contents
655;;	begin visible trailer
656;;	end visible trailer
657;;
658;; although the TOC is in slightly different positions in a few files.
659
660(defvar g~header-begin		(g~marker "1.0 begin visible header")
661	"Begin the HTML file's visible header material")
662
663(defvar g~header-end		(g~marker "1.0 end visible header")
664	"End the HTML file's visible header")
665
666(defvar g~headline-begin	(g~marker "1.1 begin headline")
667	"Begin the conspicuous headline")
668
669(defvar g~headline-end		(g~marker "1.1 end headline")
670	"End the conspicuous headline")
671
672(defvar g~toc-begin		(g~marker "1.2 begin table of contents")
673	"Begin the table of contents")
674
675(defvar g~toc-end		(g~marker "1.2 end table of contents")
676	"End the table of contents")
677
678(defvar g~hint-begin		(g~marker "1.3 begin hint")
679	"Begin the \"for other information\" section")
680
681(defvar g~hint-end		(g~marker "1.3 end hint")
682	"End the \"for other information\" section")
683
684(defvar g~contents-begin	(g~marker "2.0 begin contents")
685	"Begin the main contents")
686
687(defvar g~contents-end		(g~marker "2.0 end contents")
688	"End the main contents")
689
690(defvar g~trailer-begin		(g~marker "3.0 begin visible trailer")
691	"Begin the visible standard trailer material")
692
693(defvar g~trailer-end		(g~marker "3.0 end visible trailer")
694	"End the visible standard trailer material")
695
696;;============================================================
697;; Some working variables
698
699(defvar gs-anchor	"JUNK"		"*Anchor name to insert")
700(defvar gs-anchor-file	"JUNKFILE"	"*Anchor filename to insert")
701(defvar gs-work-buffer	"*GS work*"	"*Ghostscript working buffer")
702