! Copyright (C) 2010 Joe Groff. ! See http://factorcode.org/license.txt for BSD license. USING: accessors arrays assocs calendar calendar.format combinators combinators.short-circuit fry io io.backend io.directories io.directories.hierarchy io.encodings.binary io.encodings.detect io.encodings.utf8 io.files io.files.info io.files.temp io.files.types io.files.unique io.launcher io.pathnames kernel locals math math.parser namespaces sequences sorting strings system unicode 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-CHUNK[[ <-> ]] ] } { COMMENT2 [ XML-CHUNK[[ <-> ]] ] } { COMMENT3 [ XML-CHUNK[[ <-> ]] ] } { COMMENT4 [ XML-CHUNK[[ <-> ]] ] } { DIGIT [ XML-CHUNK[[ <-> ]] ] } { FUNCTION [ XML-CHUNK[[ <-> ]] ] } { KEYWORD1 [ XML-CHUNK[[ <-> ]] ] } { KEYWORD2 [ XML-CHUNK[[ <-> ]] ] } { KEYWORD3 [ XML-CHUNK[[ <-> ]] ] } { KEYWORD4 [ XML-CHUNK[[ <-> ]] ] } { LABEL [ XML-CHUNK[[ <-> ]] ] } { LITERAL1 [ XML-CHUNK[[ <-> ]] ] } { LITERAL2 [ XML-CHUNK[[ <-> ]] ] } { LITERAL3 [ XML-CHUNK[[ <-> ]] ] } { LITERAL4 [ XML-CHUNK[[ <-> ]] ] } { MARKUP [ XML-CHUNK[[ <-> ]] ] } { OPERATOR [ XML-CHUNK[[ <-> ]] ] } [ 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 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-CHUNK[[
  • ><->
  • ]] ] 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 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-CHUNK[[ <-> <-> ]] "\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-DOC[[ <-name->

    <-name->

    <-html-lines->
    ]] ; :: 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-DOC[[ <-name->

    <-name->

    Generated from
    <-source->
    at <-timestamp->


    ]] ] 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-CHUNK[[ playOrder=<-istr->> <-name-> /> ]] ] map-index :> file-nav-points XML-DOC[[ Table of Contents <-file-nav-points-> ]] ; :: code>opf ( dir name files -- xml ) "Generating OPF manifest" print flush name ".ncx" append :> ncx-name files [ name>> file-html-name dup XML-CHUNK[[ href=<-> media-type="text/html" /> ]] ] map :> html-manifest files [ name>> file-html-name XML-CHUNK[[ /> ]] ] map :> html-spine XML-DOC[[ > <-name-> en <-html-manifest-> media-type="application/x-dtbncx+xml" /> <-html-spine-> ]] ; : write-dest-file ( xml name ext -- ) append 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 [ [ src-dir file-name :> name src-dir code-files :> files src-dir name files code>opf name ".opf" write-dest-file "vocab:codebook/cover.jpg" "." copy-file-into src-dir name files code>ncx name ".ncx" write-dest-file src-dir name files code>toc-html "_toc.html" "" write-dest-file files |[ file | src-dir file code>html file name>> file-html-name "" write-dest-file ] each "." name ".opf" kindle-path kindlegen "." 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-directory ] with-temp-directory ;