diff options
author | Thomas Letan <contact@thomasletan.fr> | 2019-08-04 00:32:18 +0200 |
---|---|---|
committer | Thomas Letan <contact@thomasletan.fr> | 2019-08-04 00:32:18 +0200 |
commit | 5eeebd3c96e291804b0d73a927295b83a6ad4f28 (patch) | |
tree | 745df66976172ed919a2a60ff6a48d8840e1c463 | |
parent | chore: Add a program skeleton (diff) |
feature: Generate an EPUB that mupdf can read
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.lock | 226 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/epub.rs | 108 | ||||
-rw-r--r-- | src/main.rs | 50 | ||||
-rw-r--r-- | src/project.rs | 121 | ||||
-rw-r--r-- | src/render.rs | 149 |
7 files changed, 609 insertions, 55 deletions
@@ -1,2 +1,4 @@ /target **/*.rs.bk + +/_build
\ No newline at end of file @@ -1,9 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -11,78 +13,110 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "celtchar" version = "0.1.0" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ogmarkup 0.1.0 (git+https://git.sr.ht/~lthms/ogmarkup/?rev=326b6e7c0d4ad366d7ecb6358aae742d9533fb63)", - "toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ogmarkup 0.1.0 (git+https://git.sr.ht/~lthms/ogmarkup/?rev=feaf8d0cbc7aa34ac521020914df91489ef24595)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cfg-if" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.32.0" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "lexical-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "libc" -version = "0.2.43" +version = "0.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "nom" -version = "4.1.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "ogmarkup" version = "0.1.0" -source = "git+https://git.sr.ht/~lthms/ogmarkup/?rev=326b6e7c0d4ad366d7ecb6358aae742d9533fb63#326b6e7c0d4ad366d7ecb6358aae742d9533fb63" +source = "git+https://git.sr.ht/~lthms/ogmarkup/?rev=feaf8d0cbc7aa34ac521020914df91489ef24595#feaf8d0cbc7aa34ac521020914df91489ef24595" +dependencies = [ + "nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -90,32 +124,93 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "serde" -version = "1.0.80" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stackvector" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "static_assertions" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "syn" +version = "0.15.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "termion" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -123,10 +218,10 @@ dependencies = [ [[package]] name = "toml" -version = "0.4.8" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -135,6 +230,19 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -145,8 +253,13 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "winapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -166,23 +279,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16" -"checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a" -"checksum ogmarkup 0.1.0 (git+https://git.sr.ht/~lthms/ogmarkup/?rev=326b6e7c0d4ad366d7ecb6358aae742d9533fb63)" = "<none>" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum lexical-core 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b0f90c979adde96d19eb10eb6431ba0c441e2f9e9bdff868b2f6f5114ff519" +"checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum ogmarkup 0.1.0 (git+https://git.sr.ht/~lthms/ogmarkup/?rev=feaf8d0cbc7aa34ac521020914df91489ef24595)" = "<none>" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" -"checksum toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2ecc31b0351ea18b3fe11274b8db6e4d82bce861bbb22e6dbed40417902c65" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" +"checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" +"checksum stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4725650978235083241fab0fdc8e694c3de37821524e7534a1a9061d1068af" +"checksum static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4f8de36da215253eb5f24020bfaa0646613b48bf7ebe36cdfa37c3b3b33b241" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" +"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" @@ -4,9 +4,11 @@ version = "0.1.0" authors = ["Thomas Letan <contact@thomasletan.fr>"] [dependencies] -toml = "0.4" -clap = "2.32" +toml = "0.5" +clap = "2.33" +serde = "1.0" +serde_derive = "1.0" [dependencies.ogmarkup] git = "https://git.sr.ht/~lthms/ogmarkup/" -rev = "326b6e7c0d4ad366d7ecb6358aae742d9533fb63"
\ No newline at end of file +rev = "feaf8d0cbc7aa34ac521020914df91489ef24595"
\ No newline at end of file diff --git a/src/epub.rs b/src/epub.rs new file mode 100644 index 0000000..191f487 --- /dev/null +++ b/src/epub.rs @@ -0,0 +1,108 @@ +use crate::project::{Error, Project, Chapter}; +use std::fs::{create_dir, create_dir_all}; +use std::path::PathBuf; + +const EPUB_MIMETYPE: &'static str = "application/epub+zip"; +const CONTAINER_XML: &'static str = r#"<?xml version="1.0"?> +<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0"> + <rootfiles> + <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/> + </rootfiles> +</container>"#; + +fn create_mimetype() -> Result<(), Error> { + std::fs::write("mimetype", EPUB_MIMETYPE) + .map_err(|_| Error(String::from("cannot create mimetype")))?; + + Ok(()) +} + +fn create_container() -> Result<(), Error> { + create_dir("META-INF") + .map_err(|_| Error(String::from("cannot create META-INF")))?; + + std::fs::write("META-INF/container.xml", CONTAINER_XML) + .map_err(|_| Error(String::from("cannot create container.xml")))?; + + Ok(()) +} + +fn create_chapters(chapters : &Vec<Chapter<String>>) -> Result<Vec<String>, Error> { + + chapters.iter().enumerate() + .map(|(idx, c)| { + let path : String = format!("{}.xhtml", idx); + std::fs::write( + PathBuf::from(format!("OEBPS/Text/{}", path)).as_path(), + format!( + "<html><head></head><body>{}</body></html>", + c.content.as_str() + ) + ) + .map_err(|_| Error(format!("cannot create {:?}", path)))?; + + Ok(path) + }) + .collect::<Result<Vec<String>, Error>>() +} + +pub fn generate(project : &Project<String>) -> Result<(), Error> { + create_mimetype()?; + create_container()?; + + create_dir_all("OEBPS/Text") + .map_err(|_| Error(String::from("cannot create OEBPS")))?; + + let files = create_chapters(&project.chapters)?; + + let mut filesxml = String::from(""); + for f in files.iter() { + filesxml.push_str( + format!( + r#"<item href="Text/{}" id="{}" media-type="application/xhtml+xml" />"#, + f, f + ).as_str() + ); + } + + let mut spine = String::from(""); + for f in files.iter() { + spine.push_str( + format!( + r#"<itemref idref="{}" />"#, + f + ).as_str() + ); + } + + std::fs::write( + "OEBPS/content.opf", + format!( + r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId" version="2.0" xmlns:opf="http://www.idpf.org/2007/opf"> + <metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> + <dc:title>{}</dc:title> + <dc:language>fr</dc:language> + <dc:creator opf:role="aut">{}</dc:creator> + <dc:type>text</dc:type> + <dc:description>Ceci est une description</dc:description> + </metadata> + <manifest> + <item href="toc.ncx" id="ncx" media-type="application/x-dtbncx+xml" /> + {} + </manifest> + <spine toc="ncxtoc"> + {} + </spine> +</package> +"#, + project.title, + project.author, + filesxml, + spine + ) + ) + .map_err(|_| Error(String::from("cannot create content.opf")))?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 7b9cae8..112efb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,64 @@ extern crate clap; +extern crate ogmarkup; +extern crate serde_derive; +extern crate toml; use clap::{App, SubCommand}; -fn main() { +pub mod render; +pub mod project; +pub mod epub; + +use project::{Project, Error}; +use ogmarkup::typography::FRENCH; + +use std::fs::{create_dir, remove_dir_all}; + +const BUILD_DIR : &'static str = "_build"; + +fn cd_clean_build_dir() -> Result<(), Error> { + remove_dir_all(BUILD_DIR) + .map_err(|_| Error(String::from("cannot clean up _build/")))?; + + create_dir(BUILD_DIR) + .map_err(|_| Error(String::from("cannot create _build/")))?; + + std::env::set_current_dir(BUILD_DIR) + .map_err(|_| Error(String::from("cannot set current directory to _build")))?; + + Ok(()) +} + +pub fn build() -> Result<(), Error> { + Project::cd_root()?; + + let project = Project::find_project()? + .load_and_render(&FRENCH)?; + + cd_clean_build_dir()?; + + epub::generate(&project)?; + + Ok(()) +} + +fn main() -> Result<(), Error> { let matches = App::new("celtchar") .version("0.1") .author("Thomas Letan") .about("A tool to generate novels") .subcommand(SubCommand::with_name("new") .about("Create a new celtchar document")) + .subcommand(SubCommand::with_name("build") + .about("Build a celtchar document")) .get_matches(); let (subcommand, _args) = matches.subcommand(); match subcommand { - "new" => println!("Hello, world!"), - _ => println!("meh"), + "build" => build()?, + _ => unimplemented!(), } + + Ok(()) } diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..0bf35a9 --- /dev/null +++ b/src/project.rs @@ -0,0 +1,121 @@ +use std::fs; +use std::path::PathBuf; +use std::env::current_dir; + +use serde_derive::Deserialize; + +use ogmarkup::typography::Typography; + +use crate::render::Html; + +const PROJECT_FILE: &'static str = "Book.toml"; + +#[derive(Debug)] +pub struct Error(pub String); + +#[derive(Debug, Deserialize)] +pub struct Chapter<A> { + pub title: String, + pub content: A, +} + +fn compile_file <'input, T> ( + path : PathBuf, + typo : &T, +) -> Result<String, Error> +where + T : Typography, +{ + let input = fs::read_to_string(path.as_path()).map_err(|_| Error(format!("cannot open {:?}", path)))?; + + ogmarkup::compile(input.as_str(), typo) + .map(|x: Html| x.to_string()) + .map_err(|_| Error(format!("cannot render {:?}", path))) +} + +impl Chapter<Vec<PathBuf>> { + pub fn load_and_render <'input, T> ( + self, + typo : &T, + ) -> Result<Chapter<String>, Error> + where + T : Typography + { + + self.content.iter() + .map(|path| compile_file(path.to_path_buf(), typo)) + .collect::<Result<Vec<String>, Error>>() + .map(|x| Chapter { + title: self.title, + content: x.join(""), + }) + } +} + +#[derive(Debug, Deserialize)] +pub struct Project<A> { + pub author: String, + pub title: String, + pub chapters: Vec<Chapter<A>> +} + +impl Project<Vec<PathBuf>> { + pub fn cd_root() -> Result<(), Error> { + let mut cwd: PathBuf = current_dir() + .map_err(|_| Error(String::from("cannot get current directory")))?; + + loop { + cwd.push(PROJECT_FILE); // (*) + + if cwd.exists() { + cwd.pop(); + + std::env::set_current_dir(cwd.as_path()) + .map_err(|_| Error(format!("cannot set current directory to {:?}", cwd)))?; + + return Ok(()); + } else { + // We `pop` a first time for `Book.toml`, since we have pushed + // previously it (see (*)) + cwd.pop(); + + // We `pop` a second time to get the parent directory of cwd. If + // `pop` returns false, we are at the root of the current FS, and + // there is no project file to find. + if !cwd.pop() { + return Err(Error(String::from("could not find Book.toml"))) + } + } + } + } + + /// This function tries to open `./Book.toml`. If it succeeds, it tries to + /// read it as a TOML file. + pub fn find_project() -> Result<Self, Error> { + let input = fs::read_to_string(PROJECT_FILE) + .map_err(|_| Error(String::from("found Book.toml, but cannot read it")))?; + + return toml::from_str(input.as_str()) + .map_err(|e| Error(String::from(format!("toml error: {:?}", e)))); + } + + pub fn load_and_render<'input, T> ( + self, + typo : &T, + ) -> Result<Project<String>, Error> + where + T : Typography + { + let author = self.author; + let title = self.title; + + self.chapters.into_iter() + .map(|chapter| chapter.load_and_render(typo)) + .collect::<Result<Vec<Chapter<String>>, Error>>() + .map(|x| Project { + author: author, + title: title, + chapters: x + }) + } +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..3e737b0 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,149 @@ +use ogmarkup::typography::Space; +use ogmarkup::generator::Output; + +pub struct Html(String); + +impl Html { + fn push_str(&mut self, s: &str) -> () { + self.0.push_str(s); + } + + pub fn to_string(self) -> String { + self.0 + } +} + +impl Output for Html { + fn empty(input_size: usize) -> Html { + Html(String::with_capacity((15 * input_size) / 10)) + } + + fn render_space(&mut self, space: Space) -> () { + self.push_str(match space { + Space::Normal => " ", + Space::Nbsp => " ", + Space::None => "", + }) + } + + fn render_word(&mut self, word: & str) -> () { + self.push_str(word) + } + + fn render_mark(&mut self, mark: &str) -> () { + self.push_str(mark) + } + + fn render_illformed(&mut self, err: &str) -> () { + self.push_str(err) + } + + fn emph_template<F>(&mut self, format: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<em>"); + format(self); + self.push_str("</em>"); + } + + fn strong_emph_template<F>(&mut self, format: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<strong>"); + format(self); + self.push_str("</strong>"); + } + + fn reply_template<F>(&mut self, reply: F, _author: &Option<&str>) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<span class=\"reply\">"); + reply(self); + self.push_str("</span>"); + } + + fn thought_template<F>(&mut self, reply: F, author: &Option<&str>) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<span class=\"thought"); + author.map(|a| { + self.push_str(" by-"); + self.push_str(a); + }); + self.push_str("\">"); + reply(self); + self.push_str("</span>"); + } + + fn dialogue_template<F>(&mut self, reply: F, author: &Option<&str>) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<span class=\"dialogue"); + author.map(|a| { + self.push_str(" by-"); + self.push_str(a); + }); + self.push_str("\">"); + reply(self); + self.push_str("</span>"); + } + + fn between_dialogue(&mut self) -> () { + self.push_str("</p><p>"); + } + + fn illformed_inline_template<F>(&mut self, err: F) -> () + where + F: FnOnce(&mut Html) -> () + { + self.push_str("<span class=\"illformed_inline\">"); + err(self); + self.push_str("</span>"); + } + + fn paragraph_template<F>(&mut self, para: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<p>"); + para(self); + self.push_str("</p>"); + } + + fn illformed_block_template<F>(&mut self, err: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<div class=\"illformed_block\">"); + err(self); + self.push_str("</div>"); + } + + fn story_template<F>(&mut self, story: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<div class=\"story\">"); + story(self); + self.push_str("</div>"); + } + + fn aside_template<F>(&mut self, cls: &Option<&str>, aside: F) -> () + where + F : FnOnce(&mut Html) -> () + { + self.push_str("<div class=\"aside"); + cls.map(|c| { + self.push_str(" "); + self.push_str(c); + }); + self.push_str("\">"); + aside(self); + self.push_str("</div>"); + } +} |