View documentation | View script | License |
Download script | History | Other scripts by: carl |
5-Oct 9:37 UTC
[0.048] 27.18k
[0.048] 27.18k
Archive version of: makedoc2.r ... version: 3 ... carl 15-Jan-2005Amendment note: Minor change in how code examples are space-trimmed. || Publicly available? Yes REBOL [ Title: "MakeDoc 2 - The REBOL Standard Document Formatter" Version: 2.5.1 Copyright: "REBOL Technologies 1999-2005" Author: "Carl Sassenrath" File: %makedoc2.r Date: 10-Jan-2005 Purpose: { This is the official MakeDoc document formatter that is used by REBOL Technologies for all documentation. It is the fastest and easiest way to create good looking documentation using any text editor (even ones that do not auto-wrap text). It creates titles, headings, contents, bullets, numbered lists, indented examples, note blocks, and more. For documentation, notes, and other info visit http://www.rebol.net/docs/makedoc.html } Usage: { Create a text document in any editor. Separate each paragraph with a blank line. Run this script and provide your text file. The output file will be the same name with .html following it. If you use REBOL/View the output file will be displayed in your web browser as well. You can also call this script from other scripts (e.g. CGI). These are supported: do %makedoc2.r do/args %makedoc2.r %document.txt do/args %makedoc2.r 'load-only doc: scan-doc read %file.txt set [title out] gen-html/options doc [(options)] write %out.html out } Library: [ level: 'intermediate platform: 'all type: [tool] domain: [html cgi markup] tested-under: none support: none license: 'BSD see-also: none ] ] ; There are three parts to this script: ; 1. The document input scanner. ; 2. The document output formatter (for HTML). ; 3. The code that deals with input and output files. *scanner*: context [ ;-- Debugging: verbose: off debug: func [data] [if verbose [print data]] ;-- Module Variables: text: none para: none code: none title: none left-flag: off opts: [] ;[no-toc no-nums] out: [] ; The output block (static, reused) ;--- Parser rules for the Makedoc text language (top-down): rules: [some commands] commands: [ newline here: (debug ["---PARSE:" copy/part here find here newline]) ;-- Document sections: | ["===" | "-1-"] text-line (emit-section 1) | ["---" | "-2-"] text-line (emit-section 2) | ["+++" | "-3-"] text-line (emit-section 3) | ["..." | "-4-"] text-line (emit-section 4) | "###" to end (emit end none) ; allows notes, comments to follow ;-- Common commands: | #"*" [ [">>" | "**"] text-block (emit bullet3 para) | [">" | "*" ] text-block (emit bullet2 para) | text-block (emit bullet para) ] | #"#" [ ">>" text-block (emit enum3 para) | ">" text-block (emit enum2 para) | text-block (emit enum para) ] | #":" define opt newline (emit define reduce [text para]) ; ">>" reserved ; "<<" reserved ;-- Enter a special section: | #"\" [ "in" (emit indent-in none) | "note" text-line (emit note-in text) | "table" text-line (emit table-in text) | "group" (emit group-in none) | "center" (emit center-in none) ] ;-- Exit a special section: | #"/" [ "in" (emit indent-out none) | "note" (emit note-out none) | "table" (emit table-out none) | "group" (emit group-out none) | "center" (emit center-out none) ] ;-- Extended commands (all begin with "="): | #";" text-block ; comments and hidden paragraphs | #"=" [ #"=" output (emit output trim/auto code) | "image" image | "row" (emit table-row none) | "options" [ any [ spaces [ "toc" (remove find opts 'no-toc) | "nums" (remove find opts 'no-nums) | "indent" (remove find opts 'no-indent) | "no-indent" (append opts 'no-indent) | "no-toc" (append opts 'no-toc) | "no-nums" (append opts 'no-nums) | "no-template" (append opts 'no-template) | "no-title" (append opts 'no-title) ] ] ] | "template" some-chars (repend opts ['template as-file text]) ] ;-- Primary implied paragraph types: | example (emit code trim/auto detab code) | paragraph ( either title [emit para para][emit title title: para] ) | skip (debug "???WARN: Unrecognized") ] space: charset " ^-" nochar: charset " ^-^/" chars: complement nochar spaces: [any space] some-chars: [some space copy text some chars] text-line: [any space copy text thru newline] text-block: [any space paragraph opt newline] ; ignore leading space, extra NL !??? paragraph: [copy para some [chars thru newline]] example: [copy code some [indented | some newline indented]] indented: [some space chars thru newline] output: [copy code indented any ["==" copy text indented (append code head insert text " ")]] ; compensate for == define: [copy text to " -" 2 skip text-block] image: [ left? any space copy text some chars ( if text/1 = #"%" [remove text] ; remove %file text: as-file text emit image reduce [text pick [left center] left-flag] ) ] left?: [some space "left" (left-flag: on) | none (left-flag: off)] as-file: func [str] [to-file trim str] ;-- Output emitters: emit: func ['word data] [ debug ["===EMIT: " word] if block? word [word: do word] ;???? if string? data [trim/tail data] repend out [word data] ] emit-section: func [num] [ emit [to-word join "sect" num] text title: true ] ;-- Export function to scan doc. Returns format block. set 'scan-doc func [str /options block] [ clear out if options [ if find block 'no-title [title: true] ] emit options opts str: join str "^/^/###" ; makes the parse easier parse/all detab str rules if verbose [ n: 1 foreach [word data] out [ print [word data] if (n: n + 1) > 5 [break] ] ] copy out ] ] *html*: context [ ;-- HTML foprmat global option variables: no-nums: ; Do not use numbered sections no-toc: ; Do not generate table of contents no-title: ; Do not generate a title or boilerplate no-indent: ; Do not indent each section ok-old-doc: ; Allow old markup convention (slower) no-template: ; Do not use a template HTML page none toc-levels: 2 ; Levels shown in table of contents image-path: %/ ; or set to "" if local set 'gen-html func [ doc [block!] /options opts [block!] /local title template ][ clear out ; (reused) group-count: 0 opts: set-options any [opts select doc 'options] ; Template can be provided in =template or in ; options block following 'template. If options ; has 'no-template, than do not use a template. if not no-template [ template: any [select opts 'template select doc 'template] if file? template [template: read template] if not template [template: trim/auto default-template] ] ; Emit title and boilerplate: if not no-title [title: emit-boiler doc] ; Emit table of contents: clear-sects if not no-toc [ emit-toc doc clear-sects ] prior-cmd: none forskip doc 2 [ ; If in a table, emit a cell each time. if all [ in-table zero? group-count ; do not emit cell if in group not find [table-out table-row] doc/1 not find [table-in table-row] prior-cmd ][ emit-table-cell ] switch prior-cmd: doc/1 [ para [emit-para doc/2] code [doc: emit-code doc] enum [emit-item doc enum <ol>] bullet [emit-bull doc doc/1 <ul>] bullet2 [emit-bull doc doc/1 <ul>] bullet3 [emit-bull doc doc/1 <ul>] define [emit-def doc] sect1 [emit-sect 1 doc/2] sect2 [emit-sect 2 doc/2] sect3 [emit-sect 3 doc/2] sect4 [emit-sect 4 doc/2] image [emit-image doc/2] table-in [emit-table doc/2] table-out [emit-table-end] table-row [emit-table-row] center-in [emit <center>] center-out [emit </center>] note-in [emit-note doc/2] note-out [emit-note-end] group-in [group-count: group-count + 1] group-out [group-count: max 0 group-count - 1] indent-in [emit <blockquote>] indent-out [emit </blockquote>] ] ] doc: head doc emit </blockquote> if template [ ; Template variables all begin with $ replace/all template "$title" title replace template "$content" out replace/all template "$date" now/date out: template ] reduce [title out] ] set-options: func [options] [ foreach opt [ no-nums no-toc no-indent no-template no-title ][set opt found? find options opt] options ] ;-- Default HTML Template: default-template: { <html> <!--Page generated by REBOL--> <head><title>$title</title><style> body, p, td {font-family: arial, sans-serif, helvetica; font-size: 10pt;} h1 {font-size: 16pt; Font-Weight: bold;} h2 {font-size: 14pt; color: #2030a0; Font-Weight: bold; width: 100%; border-bottom: 1px solid #c09060;} h3 {font-size: 12pt; color: #2030a0; Font-Weight: bold;} h4 {font-size: 10pt; color: #2030a0; Font-Weight: bold;} h5 {font-size: 10pt; Font-Weight: bold;} tt {font-family: "courier new", monospace, courier; font-size: 9pt;} pre {font: bold 10pt "courier new", monospace, console; background-color: #f0f0f0; padding: 16px; border: solid #a0a0a0 1px;} .toc1 {margin-left: 1cm; font-size: 12pt; font-weight: bold;} .toc2 {margin-left: 2cm; font-size: 10pt; font-weight: bold;} .toc3 {margin-left: 3cm; font-size: 10pt;} .toc4 {margin-left: 4cm; font-size: 10pt; color: grey;} .output {color: #803020} .note {background-color: #f0f0a0; width: 100%; padding: 16px; border: solid #a0a0a0 1px;} </style></head> <body bgcolor="white"> <table width="660" cellpadding="4" cellspacing="0" border="0"> <tr><td><p>$content</p><hr></td></tr><tr> <td align=center><a href=http://www.rebol.com>MakeDoc2 by REBOL</a> - $date</td> </tr></table></body></html> } ;-- HTML Emit Utility Functions: out: make string! 10000 emit: func [data /line] [ ; Primary emit function: insert insert tail out reduce data newline ] wsp: charset " ^-^/" ; whitespace: sp, tab, return emit-end-tag: func [tag] [ ; Emit an end tag from a tag. tag: copy/part tag any [find tag wsp tail tag] insert tag #"/" emit tag ] emit-tag: func [text tag start end] [ ; Used to emit special one-sided tags: while [text: find text tag] [ remove/part text length? tag text: insert text start text: insert any [find text end-char tail text] end ] ] end-char: charset [" " ")" "]" "." "," "^/"] escape-html: func [text][ ; Convert to avoid special HTML chars: foreach [from to] html-codes [replace/all text from to] text ] html-codes: ["&" "&" "<" "<" ">" ">"] emit-lines: func [text] [ ; Emit separate lines in normal font: replace/all text newline <BR> emit text ] ;-- HTML Document Formatting Functions: emit-para: func [text] [ ; Emit standard text paragraph: if ok-old-doc [ emit-tag text "<c>" "<i>" "</i>" emit-tag text "<w>" "<b>" "</b>" emit-tag text "<s>" "<b><i>" "</i></b>" ] emit [<p> text </p>] ] emit-code: func [doc] [ emit <pre> while [ switch doc/1 [ code [emit [escape-html doc/2]] output [emit [<span class=output> escape-html doc/2 </span>]] ] ][doc: skip doc 2] emit </pre> doc: skip doc -2 ] emit-image: func [spec /local tag] [ ; Emit image. Spec = 'center or default is 'left. emit [ either spec/2 = 'center [<p align=center>][<p>] join {<img src="} [(join image-path spec/1) {">}] </p> ] ] buls: [bullet bullet2 bullet3] diff-bull: func [b1 b2][ ; Compute difference in bullet depths. if not find buls b1 [return index? find buls b2] if not find buls b2 [return index? find buls b1] offset? find buls b2 find buls b1 ] emit-bull: func [doc item tag][ loop diff-bull item doc/-2 [emit tag] emit [<li> doc/2 </li>] loop diff-bull item doc/3 [emit-end-tag tag] ] emit-item: func [doc 'item tag] [ ; Emit a list item. Start and end the list if necessary. if doc/-2 <> item [emit tag] emit [<li> doc/2 </li>] if doc/3 <> item [emit-end-tag tag] ] emit-def: func [doc] [ ; Emit indented definitions table. Start and end it as necessary. if doc/-2 <> 'define [ emit {<table cellspacing="6" border="0" width="95%">} ] emit [ <tr><td width="20"> " " </td> <td valign="top" width="80"> <b> any [doc/2/1 " "] </b></td> <td valign="top"> any [doc/2/2 " "] </td> </tr> ] if doc/3 <> 'define [emit {</table>}] ] emit-note: func [text] [ ; Start a note sidebar, centered on page: emit [<div class=note><b> text </b><p>] ] emit-note-end: does [ ; End a note sidebar. emit </div> ] in-table: in-header: false emit-table: does [ in-table: true in-header: true emit {<table border="0" cellspacing="1" cellpadding="4" bgcolor=#505050> <tr bgcolor="silver"><td><b>} ] emit-table-end: does [ in-table: false emit "</td></tr></table><p>" ] emit-table-cell: does [ emit pick [{</b></td><td><b>} {</td><td valign=top bgcolor=white>}] in-header ] emit-table-row: does [ in-header: false emit "</td></tr><tr><td valign=top bgcolor=white>" ] ;-- Section handling: clear-sects: does [sects: 0.0.0.0] next-section: func [level /local bump mask] [ ; Return next section number. Clear sub numbers. set [bump mask] pick [ [1.0.0.0 1.0.0.0] [0.1.0.0 1.1.0.0] [0.0.1.0 1.1.1.0] [0.0.0.1 1.1.1.1] ] level level: form sects: sects + bump * mask clear find level ".0" level ] make-heading: func [level num str /toc /local lnk][ ; Make a proper heading link or TOC target. ; Determine link target str. Search for [target] in front of heading. either parse str [ "[" copy lnk to "]" s: to end ][ str: next s ; remove link target ][ lnk: join "section-" num ] if not no-nums [str: rejoin [num pick [". " " "] level = 1 str]] rejoin either toc [ [{<a class=toc} level { href="#} lnk {">} str </a>] ][ [{<h} level + 1 { id="} lnk {">} str {</h} level + 1 {>}] ] ] emit-sect: func [level str /local sn] [ ; Unindent prior level: if all [not no-indent level <= 2 sects/1 > 0] [emit </blockquote>] emit <p> sn: next-section level emit make-heading level sn str if all [not no-indent level <= 2] [emit <blockquote>] ] emit-toc: func [doc /local w sn] [ ; Output table of contents: emit [<h2> "Contents:" </h2>] foreach [word str] doc [ if w: find [sect1 sect2 sect3 sect4] word [ w: index? w if w <= toc-levels [ sn: next-section w emit [make-heading/toc w sn str <br>] ] ] ] ] emit-boiler: func [doc /local title info temp] [ ; Output top boiler plate: title: any [ select doc 'title select doc 'sect1 "Untitled" ] emit [<h1> title </h1>] foreach [word val] doc [ if word = 'code [ emit {<blockquote><b>} emit-lines val emit {</b></blockquote>} remove/part find doc 'code 2 break ] if not find [title template options] word [break] ] title ] ] do-makedoc: has [in-view? file msg doc] [ in-view?: all [value? 'view? view?] ; Are we using View? ; Get the file name from the script argument: file: system/script/args if string? file [file: to-file file] ; If no file provided, should we do the last file again? if all [ not file exists? %last-file.tmp ][ file: load %last-file.tmp either confirm reform ["Reprocess" file "?"] [ system/script/args: none ][ file: none ] ] ; If no file still, then ask the user for the file name: if not file [ either in-view? [ file: request-file/only ][ file: ask "Filename? " file: all [not empty? trim file to-file file] ] ] ; No file provided: if not file [exit] ; File must exist: if not exists? file [ msg: reform ["Error:" file "does not exist"] either in-view? [alert msg] [ask msg] exit ] ; Save this as the last file processed: save %last-file.tmp file ; Process the file. Returns [title doc] doc: second gen-html scan-doc read file ; Create output file name: append clear find/last file #"." ".html" write file doc if all [in-view? not system/script/args] [browse file] ] ; Start process (but caller may request it only be loaded): if system/script/args <> 'load-only [do-makedoc] Notes
|