--- /dev/null
+! (c)2010 Joe Groff bsd license
+USING: accessors arrays assocs calendar calendar.format
+combinators combinators.short-circuit fry io io.backend
+io.directories io.encodings.binary io.encodings.detect
+io.encodings.utf8 io.files io.files.info io.files.types
+io.files.unique io.launcher io.pathnames kernel locals math
+math.parser namespaces sequences sorting strings system
+unicode.categories xml.syntax xml.writer xmode.catalog
+xmode.marker xmode.tokens ;
+IN: codebook
+
+! Usage: "my/source/tree" codebook
+! Writes tree.opf, tree.ncx, and tree.html to a temporary directory
+! Writes tree.mobi to resource:codebooks
+! Requires kindlegen to compile tree.mobi for Kindle
+
+CONSTANT: codebook-style
+ {
+ { COMMENT1 [ [XML <i><font color="#555555"><-></font></i> XML] ] }
+ { COMMENT2 [ [XML <i><font color="#555555"><-></font></i> XML] ] }
+ { COMMENT3 [ [XML <i><font color="#555555"><-></font></i> XML] ] }
+ { COMMENT4 [ [XML <i><font color="#555555"><-></font></i> XML] ] }
+ { DIGIT [ [XML <font color="#333333"><-></font> XML] ] }
+ { FUNCTION [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ { KEYWORD1 [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ { KEYWORD2 [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ { KEYWORD3 [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ { KEYWORD4 [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ { LABEL [ [XML <b><font color="#333333"><-></font></b> XML] ] }
+ { LITERAL1 [ [XML <font color="#333333"><-></font> XML] ] }
+ { LITERAL2 [ [XML <font color="#333333"><-></font> XML] ] }
+ { LITERAL3 [ [XML <font color="#333333"><-></font> XML] ] }
+ { LITERAL4 [ [XML <font color="#333333"><-></font> XML] ] }
+ { MARKUP [ [XML <b><font color="#333333"><-></font></b> XML] ] }
+ { OPERATOR [ [XML <b><font color="#111111"><-></font></b> XML] ] }
+ [ drop ]
+ }
+
+: first-line ( filename encoding -- line )
+ [ readln ] with-file-reader ;
+
+TUPLE: code-file
+ name encoding mode ;
+
+: include-file-name? ( name -- ? )
+ {
+ [ path-components [ "." head? ] any? not ]
+ [ link-info type>> +regular-file+ = ]
+ } 1&& ;
+
+: code-files ( dir -- files )
+ '[
+ [ include-file-name? ] filter [
+ dup detect-file dup binary?
+ [ f ] [ 2dup dupd first-line find-mode ] if
+ code-file boa
+ ] map [ mode>> ] filter [ name>> ] sort-with
+ ] with-directory-tree-files ;
+
+: html-name-char ( char -- str )
+ {
+ { [ dup alpha? ] [ 1string ] }
+ { [ dup digit? ] [ 1string ] }
+ [ >hex 6 CHAR: 0 pad-head "_" "_" surround ]
+ } cond ;
+
+: file-html-name ( name -- name )
+ [ html-name-char ] { } map-as concat ".html" append ;
+
+: toc-list ( files -- list )
+ [ name>> ] map natural-sort [
+ [ file-html-name ] keep
+ [XML <li><a href=<->><-></a></li> XML]
+ ] map ;
+
+! insert zero-width non-joiner between all characters so words can wrap anywhere
+: zwnj ( string -- s|t|r|i|n|g )
+ [ CHAR: \u00200c "" 2sequence ] { } map-as concat ;
+
+! We wrap every line in <tt> because Kindle tends to forget the font when
+! moving back pages
+: htmlize-tokens ( tokens line# -- html-tokens )
+ swap [
+ [ str>> zwnj ] [ id>> ] bi codebook-style case
+ ] map [XML <tt><font size="-2" color="#666666"><-></font> <-></tt> XML]
+ "\n" 2array ;
+
+: line#>string ( i line#len -- i-string )
+ [ number>string ] [ CHAR: \s pad-head ] bi* ;
+
+:: code>html ( dir file -- page )
+ file name>> :> name
+ "Generating HTML for " write name write "..." print flush
+ dir [ file [ name>> ] [ encoding>> ] bi file-lines ] with-directory :> lines
+ lines length 1 + number>string length :> line#len
+ file mode>> load-mode :> rules
+ f lines [| l i | l rules tokenize-line i 1 + line#len line#>string htmlize-tokens ]
+ map-index concat nip :> html-lines
+ <XML <html>
+ <head>
+ <title><-name-></title>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ </head>
+ <body>
+ <h2><-name-></h2>
+ <pre><-html-lines-></pre>
+ <mbp:pagebreak xmlns:mbp="http://www.mobipocket.com/mbp" />
+ </body>
+ </html> XML> ;
+
+:: code>toc-html ( dir name files -- html )
+ "Generating HTML table of contents" print flush
+
+ now timestamp>rfc822 :> timestamp
+ dir absolute-path :> source
+ dir [
+ files toc-list :> toc
+
+ <XML <html>
+ <head>
+ <title><-name-></title>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ </head>
+ <body>
+ <h1><-name-></h1>
+ <font size="-2">Generated from<br/>
+ <b><tt><-source-></tt></b><br/>
+ at <-timestamp-></font><br/>
+ <br/>
+ <ul><-toc-></ul>
+ <mbp:pagebreak xmlns:mbp="http://www.mobipocket.com/mbp" />
+ </body>
+ </html> XML>
+ ] with-directory ;
+
+:: code>ncx ( dir name files -- xml )
+ "Generating NCX table of contents" print flush
+
+ files [| file i |
+ file name>> :> name
+ name file-html-name :> filename
+ i 2 + number>string :> istr
+
+ [XML <navPoint class="book" id=<-filename-> playOrder=<-istr->>
+ <navLabel><text><-name-></text></navLabel>
+ <content src=<-filename-> />
+ </navPoint> XML]
+ ] map-index :> file-nav-points
+
+ <XML <?xml version="1.0" encoding="UTF-8" ?>
+ <ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
+ <navMap>
+ <navPoint class="book" id="toc" playOrder="1">
+ <navLabel><text>Table of Contents</text></navLabel>
+ <content src="_toc.html" />
+ </navPoint>
+ <-file-nav-points->
+ </navMap>
+ </ncx> XML> ;
+
+:: code>opf ( dir name files -- xml )
+ "Generating OPF manifest" print flush
+ name ".ncx" append :> ncx-name
+
+ files [
+ name>> file-html-name dup
+ [XML <item id=<-> href=<-> media-type="text/html" /> XML]
+ ] map :> html-manifest
+
+ files [ name>> file-html-name [XML <itemref idref=<-> /> XML] ] map :> html-spine
+
+ <XML <?xml version="1.0" encoding="UTF-8" ?>
+ <package
+ version="2.0"
+ xmlns="http://www.idpf.org/2007/opf"
+ unique-identifier=<-name->>
+ <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <dc:title><-name-></dc:title>
+ <dc:language>en</dc:language>
+ <meta name="cover" content="my-cover-image" />
+ </metadata>
+ <manifest>
+ <item href="cover.jpg" id="my-cover-image" media-type="image/jpeg" />
+ <item id="html-toc" href="_toc.html" media-type="text/html" />
+ <-html-manifest->
+ <item id="toc" href=<-ncx-name-> media-type="application/x-dtbncx+xml" />
+ </manifest>
+ <spine toc="toc">
+ <itemref idref="html-toc" />
+ <-html-spine->
+ </spine>
+ <guide>
+ <reference type="toc" title="Table of Contents" href="_toc.html" />
+ </guide>
+ </package> XML> ;
+
+: write-dest-file ( xml dest-dir name ext -- )
+ append append-path utf8 [ write-xml ] with-file-writer ;
+
+SYMBOL: kindlegen-path
+kindlegen-path [ "kindlegen" ] initialize
+
+SYMBOL: codebook-output-path
+codebook-output-path [ "resource:codebooks" ] initialize
+
+: kindlegen ( path -- )
+ [ kindlegen-path get "-unicode" ] dip 3array try-process ;
+
+: kindle-path ( directory name extension -- path )
+ [ append-path ] dip append ;
+
+:: codebook ( src-dir -- )
+ codebook-output-path get normalize-path :> dest-dir
+
+ "Generating ebook for " write src-dir write " in " write dest-dir print flush
+
+ dest-dir make-directories
+ [
+ current-temporary-directory get :> temp-dir
+ src-dir file-name :> name
+ src-dir code-files :> files
+
+ src-dir name files code>opf
+ temp-dir name ".opf" write-dest-file
+
+ "vocab:codebook/cover.jpg" temp-dir copy-file-into
+
+ src-dir name files code>ncx
+ temp-dir name ".ncx" write-dest-file
+
+ src-dir name files code>toc-html
+ temp-dir "_toc.html" "" write-dest-file
+
+ files [| file |
+ src-dir file code>html
+ temp-dir file name>> file-html-name "" write-dest-file
+ ] each
+
+ temp-dir name ".opf" kindle-path kindlegen
+ temp-dir name ".mobi" kindle-path dest-dir copy-file-into
+
+ dest-dir name ".mobi" kindle-path :> mobi-path
+
+ "Job's finished: " write mobi-path print flush
+ ] cleanup-unique-working-directory ;