mirror of
https://github.com/lise-henry/crowbook
synced 2024-05-10 12:46:17 +02:00
Compare commits
8 Commits
68132165e0
...
c1f54fdbd4
Author | SHA1 | Date | |
---|---|---|---|
Elisabeth Henry | c1f54fdbd4 | ||
Elisabeth Henry | 0f8a4f3026 | ||
Elisabeth Henry | b2f17cb4a2 | ||
Elisabeth Henry | 1a84b58e9c | ||
Elisabeth Henry | 1bdcdaa309 | ||
Elisabeth Henry | e2fa7aff3c | ||
Elisabeth Henry | 682cc039a3 | ||
Elisabeth Henry | 0ff24f94f4 |
|
@ -41,6 +41,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
|
@ -90,6 +99,23 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.8"
|
||||
|
@ -147,6 +173,16 @@ version = "2.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
|
@ -188,6 +224,21 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"strsim 0.8.0",
|
||||
"textwrap 0.11.0",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.21"
|
||||
|
@ -208,7 +259,7 @@ dependencies = [
|
|||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"strsim 0.10.0",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
|
@ -251,7 +302,7 @@ version = "0.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap 4.3.21",
|
||||
"entities",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
|
@ -340,7 +391,7 @@ name = "crowbook"
|
|||
version = "0.17.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"clap",
|
||||
"clap 4.3.21",
|
||||
"comrak",
|
||||
"console",
|
||||
"crowbook-intl",
|
||||
|
@ -356,10 +407,11 @@ dependencies = [
|
|||
"numerals",
|
||||
"punkt",
|
||||
"rayon",
|
||||
"rust-i18n",
|
||||
"simplelog",
|
||||
"syntect",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
"textwrap 0.16.0",
|
||||
"upon",
|
||||
"uuid",
|
||||
"walkdir 2.3.3",
|
||||
|
@ -443,6 +495,12 @@ dependencies = [
|
|||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.2"
|
||||
|
@ -529,18 +587,63 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
|
||||
dependencies = [
|
||||
"aho-corasick 1.0.3",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex 1.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ignore",
|
||||
"walkdir 2.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.2"
|
||||
|
@ -602,6 +705,23 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"lazy_static 1.4.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex 1.9.3",
|
||||
"same-file 1.0.6",
|
||||
"thread_local 1.1.7",
|
||||
"walkdir 2.3.3",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
|
@ -615,7 +735,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -646,7 +776,7 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.2",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
@ -657,11 +787,20 @@ version = "0.4.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.2",
|
||||
"rustix 0.38.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
|
@ -862,7 +1001,7 @@ version = "1.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -970,7 +1109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"line-wrap",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
|
@ -1214,7 +1353,7 @@ dependencies = [
|
|||
"aho-corasick 0.6.10",
|
||||
"memchr",
|
||||
"regex-syntax 0.5.6",
|
||||
"thread_local",
|
||||
"thread_local 0.3.6",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
|
@ -1262,6 +1401,77 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca136c6f6d53a2de7264bb392ea7c1f83357e00d131a24275b1661ea1c23c3af"
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074e2507aedea43bdeb742cb55fc339a0704625050c9532a226c7ddbd1a05f62"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 2.34.0",
|
||||
"globwalk",
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"quote 1.0.32",
|
||||
"regex 1.9.3",
|
||||
"rust-i18n-extract",
|
||||
"rust-i18n-macro",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-extract"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89ac25fb50c8d0893ee6436056fb4a0cc6f6e1df99239d7c104421d007d445e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ignore",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.32",
|
||||
"regex 1.9.3",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-macro"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e09ef5c1e310112eea3c19c4e18e3e62968b002eb535ff5b242ca1200742f996"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.32",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-support"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14eb094cd0072c5f09f333eea36fcd8c64961f9eb61dbd09e82eff51c58e8414"
|
||||
dependencies = [
|
||||
"globwalk",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.66",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
|
@ -1363,6 +1573,27 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"ryu",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
|
@ -1401,6 +1632,12 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -1418,6 +1655,17 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.32",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.28"
|
||||
|
@ -1483,6 +1731,15 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
|
@ -1523,6 +1780,16 @@ dependencies = [
|
|||
"lazy_static 1.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.25"
|
||||
|
@ -1553,6 +1820,40 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
|
@ -1642,6 +1943,12 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -1913,6 +2220,15 @@ version = "0.48.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.5.2"
|
||||
|
|
|
@ -46,6 +46,7 @@ nightly = ["punkt", "hyphenation"]
|
|||
crowbook-intl = "0.2"
|
||||
|
||||
[dependencies]
|
||||
rust-i18n = "2"
|
||||
html-escape = "0.2"
|
||||
mime_guess = "2"
|
||||
comrak = "0.18"
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
msg:
|
||||
autograph: "Enter autograph:"
|
||||
default_book: |
|
||||
"author: Your name"
|
||||
"title: Your title"
|
||||
"lang: en"
|
||||
|
||||
"## Output formats"
|
||||
|
||||
"# Uncomment and fill to generate files"
|
||||
"# output.html: some_file.html"
|
||||
"# output.epub: some_file.epub"
|
||||
"# output.pdf: some_file.pdf"
|
||||
|
||||
"# Or uncomment the following to generate PDF, HTML and EPUB files based on this file's name"
|
||||
"# output: [pdf, epub, html]"
|
||||
|
||||
"# Uncomment and fill to set cover image (for EPUB)"
|
||||
"# cover: some_cover.png"
|
||||
chapter_list: "\n## List of chapters\n"
|
||||
created: "Created %{file}, now you'll have to complete it!"
|
||||
cmd:
|
||||
about: Render a Markdown book in EPUB, PDF or HTML.
|
||||
single: Use a single Markdown file instead of a book configuration file
|
||||
emoji: Force emoji usage even if it might not work on your system
|
||||
verbose: Print warnings in parsing/rendering
|
||||
quiet: Don't print info/error messages
|
||||
create: Create a new book with existing Markdown files
|
||||
autograph: Prompts for an autograph for this book
|
||||
output: Specify output file
|
||||
lang: Set the runtime language used by Crowbook
|
||||
to: Generate specific format
|
||||
set: Set a list of book options
|
||||
no_fancy: Disably fancy UI
|
||||
list_options: List all possible options
|
||||
list_options_md: List all possible options, formatted in Markdown
|
||||
template: Prints the default content of a template
|
||||
book: File containing the book configuration file, or a Markdown file when called with --single
|
||||
stats: Print some project statistics
|
||||
clap:
|
||||
template: |
|
||||
|
||||
{bin} {version} by {author}
|
||||
{about}
|
||||
|
||||
USAGE:
|
||||
{usage}
|
||||
|
||||
OPTIONS:
|
||||
{options}
|
||||
|
||||
ARGS:
|
||||
{positionals}
|
||||
|
||||
error:
|
||||
invalid_template: "%{template} is not a valid template name"
|
||||
no_file: |
|
||||
You must pass the name of a book configuration file.
|
||||
For more information try --help.
|
||||
autograph: could not read autograph from stdin
|
||||
occurred: "Crowbook exited successfully, but the following errors occurred:"
|
||||
warning: WARNING
|
||||
error: ERROR
|
||||
odd_number: |
|
||||
An odd number of arguments was passed to --set, but it takes
|
||||
a list of key value pairs.
|
||||
set_key: "Error in setting key %{key}: %{error}"
|
||||
create: "Could not create file %{file}: it already exists!"
|
|
@ -0,0 +1,95 @@
|
|||
ui:
|
||||
parsing: Parsing...
|
||||
parsing_file: "Parsing %{file}"
|
||||
rendering: Rendering...
|
||||
rendering_format: rendering...
|
||||
waiting: waiting...
|
||||
options: setting options
|
||||
chapters: Parsing chapters
|
||||
processing: Processing...
|
||||
processing_file: "Processing %{file}..."
|
||||
finished: Finished
|
||||
generated: "generated %{path}"
|
||||
error: ERROR
|
||||
error:
|
||||
markdown: "Error parsing markdown: %{error}"
|
||||
config: "Error parsing configuration file: "
|
||||
template: "Error compiling template: %{template}"
|
||||
render_error: "Error during rendering: "
|
||||
zipper: "Error during temporary files editing: "
|
||||
bookoption: "Error converting BookOption: "
|
||||
invalid_option: "Error accessing book option: "
|
||||
syntect: "Error higligting syntax: "
|
||||
file_not_found: "Could not find file '%{file}' for %{description}"
|
||||
utf8_error: "UTF-8 error: %{error}"
|
||||
initial: empty str token, could not find initial
|
||||
no_string: "%{s} is not a string"
|
||||
no_string_vector: "%{s} is not a string vector"
|
||||
no_path: "%{s} is not a path"
|
||||
no_bool: "%{s} is not a boolean"
|
||||
no_char: "%{s} is not a char"
|
||||
no_i32: "%{s} is not an i32"
|
||||
no_f32: "%{s} is not a f32"
|
||||
book_init: "Error initializing book: could not set %{key} to %{value}: %{error}"
|
||||
parse_book: |
|
||||
"could not parse %{file} as a book file."
|
||||
Maybe you meant to run crowbook with the --single argument?
|
||||
yaml_block: "YAML block was not valid YAML: %{error}"
|
||||
yaml_hash: YAML part of the book is not a valid hashmap
|
||||
chapter_whitspace: chapter filenames must not contain whitespace
|
||||
no_chapter_name: "no chapter name specified"
|
||||
source: "could not read source: %{error}"
|
||||
format_line: ill-formatted line specifying chapter number
|
||||
chapter_number: "error parsing chapter number: %{error}"
|
||||
part_number_line: ill-formatted line specifying part number
|
||||
part_number: "error parsing part number: %{error}"
|
||||
part_definition: found invalid part definition in the chapter list
|
||||
chapter_definition: found invalid chapter definition in the chapter list
|
||||
rendering: "Error rendering %{name}: %{error}"
|
||||
infer: "output to %{format} set to auto but can't find book file name to infer it"
|
||||
support: "the %{format} renderer does not support auto for output path"
|
||||
unknown: "unknown format %{format}"
|
||||
unknown_short: "unknown format"
|
||||
utf8: "file %{file} contains invalid UTF-8"
|
||||
heading: "this subchapter contains a heading that, when adjusted, is not in the right range (%{n} instead of [0-6])"
|
||||
invalid_template: "invalid template '%{template}'"
|
||||
read_file: "file '%{file}' could not be read"
|
||||
compile_template: "could not compile '%{template}': %{error}"
|
||||
roman_numerals: "can not use roman numerals with zero or negative chapter numbers (%{n})"
|
||||
render_key: "could not render `%{key}` for metadata:\n%{error}"
|
||||
yaml_set: "Inline YAML block could not set %{key} to %{value}: %{err}"
|
||||
renderer:
|
||||
no_output: This renderer does not support the auto output
|
||||
file_creation: "could not create file '%{file}': '%{err}"
|
||||
write: "could not write book content to file '%{file}': %{err}"
|
||||
warn:
|
||||
above: "Warning: book contains chapter '%{file}' in a directory above the book file, this might cause problems"
|
||||
format:
|
||||
book: book
|
||||
book_chapter: book chapter
|
||||
html_single: HTML (standalone page)
|
||||
html_dir: HTML (multiple pages)
|
||||
tex: LaTeX
|
||||
pdf: PDF
|
||||
epub: EPUB
|
||||
html_if: HTML (interactive fiction)
|
||||
debug:
|
||||
yaml_replace: "Inline YAML block replaced %{key} previously set to %{old_val} to %{new_val}"
|
||||
yaml_set: "Inline YAML block set %{key} to %{value}"
|
||||
yaml_ignore: "Ignoring YAML block:\n%{block}"
|
||||
found_yaml_block: "Found something that looked like a YAML block:\n%{block}"
|
||||
found_yaml_block2: "... but it didn't parse correctly as YAML('%{error}'), so treating it like Markdown."
|
||||
epub:
|
||||
zip_command: "Could not run zip command, falling back to zip library"
|
||||
cover: cover
|
||||
image_or_cover: image or cover
|
||||
resources: additional resource from resources.files
|
||||
ambiguous: "EPUB (%{source}): detected two chapters inside the same markdown file."
|
||||
ambiguous_invisible: "EPUB (%{source}): detected two chapter titles inside the same markdown file, in a file where chapter titles are not even rendered."
|
||||
title_conflict: "EPUB ({source}): conflict between: %{title1} and %{title2}"
|
||||
guess: "EPUB: could not guess the format of %{file} based on extension. Assuming png."
|
||||
msg:
|
||||
attempting: "Attempting to generate %{format}..."
|
||||
generated: "Succesfully generated %{format}: %{path}"
|
||||
generated_short: "Succesfully generated %{format}"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2016-2022Élisabeth HENRY.
|
||||
// Copyright (C) 2016-2023Élisabeth HENRY.
|
||||
//
|
||||
// This file is part of Crowbook.
|
||||
//
|
||||
|
@ -18,6 +18,7 @@
|
|||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use console::style;
|
||||
use crowbook::Book;
|
||||
use rust_i18n::t;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
@ -33,7 +34,7 @@ pub fn print_warning(msg: &str, emoji: bool) {
|
|||
if emoji {
|
||||
eprint!("{}", style(WARNING).yellow());
|
||||
}
|
||||
eprintln!("{} {}", style(lformat!("WARNING")).bold().yellow(), msg);
|
||||
eprintln!("{} {}", style(t!("error.warning")).bold().yellow(), msg);
|
||||
}
|
||||
|
||||
/// Prints an error
|
||||
|
@ -41,7 +42,7 @@ pub fn print_error(s: &str, emoji: bool) {
|
|||
if emoji {
|
||||
eprint!("{}", style(ERROR).red());
|
||||
}
|
||||
eprintln!("{} {}", style(lformat!("ERROR")).bold().red(), s);
|
||||
eprintln!("{} {}", style(t!("error.error")).bold().red(), s);
|
||||
}
|
||||
|
||||
/// Prints an error on stderr and exit the program
|
||||
|
@ -82,10 +83,7 @@ pub fn get_book_options(matches: &ArgMatches) -> Vec<(&str, &str)> {
|
|||
let v: Vec<_> = iter.collect();
|
||||
if v.len() % 2 != 0 {
|
||||
print_error_and_exit(
|
||||
&lformat!(
|
||||
"An odd number of arguments was passed to --set, but it takes \
|
||||
a list of key value pairs."
|
||||
),
|
||||
&t!("error.odd_number"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
@ -96,9 +94,6 @@ pub fn get_book_options(matches: &ArgMatches) -> Vec<(&str, &str)> {
|
|||
output.push((key.as_str(), value.as_str()));
|
||||
}
|
||||
}
|
||||
if matches.get_flag("proofread") {
|
||||
output.push(("proofread", "true"));
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
|
@ -113,7 +108,7 @@ pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
|
|||
for (key, value) in options {
|
||||
let res = book.options.set(key, value);
|
||||
if let Err(err) = res {
|
||||
print_error_and_exit(&lformat!("Error in setting key {}: {}", key, err), false);
|
||||
print_error_and_exit(&t!("error.set_key", key = key, error = err), false);
|
||||
}
|
||||
output.push_str(&format!("{key}: {value}\n"));
|
||||
}
|
||||
|
@ -126,7 +121,7 @@ pub fn create_book(matches: &ArgMatches) -> ! {
|
|||
let mut f: Box<dyn Write> = if let Some(book) = matches.get_one::<String>("BOOK") {
|
||||
if fs::metadata(book).is_ok() {
|
||||
print_error_and_exit(
|
||||
&lformat!("Could not create file {}: it already exists!", book),
|
||||
&t!("error.create", file = book),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
@ -142,29 +137,12 @@ pub fn create_book(matches: &ArgMatches) -> ! {
|
|||
f.write_all(s.as_bytes()).unwrap();
|
||||
} else {
|
||||
f.write_all(
|
||||
lformat!(
|
||||
"author: Your name
|
||||
title: Your title
|
||||
lang: en
|
||||
|
||||
## Output formats
|
||||
|
||||
# Uncomment and fill to generate files
|
||||
# output.html: some_file.html
|
||||
# output.epub: some_file.epub
|
||||
# output.pdf: some_file.pdf
|
||||
|
||||
# Or uncomment the following to generate PDF, HTML and EPUB files based on this file's name
|
||||
# output: [pdf, epub, html]
|
||||
|
||||
# Uncomment and fill to set cover image (for EPUB)
|
||||
# cover: some_cover.png\n"
|
||||
)
|
||||
t!("msg.default_book")
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
f.write_all(lformat!("\n## List of chapters\n").as_bytes())
|
||||
f.write_all(t!("msg.chapter_list").as_bytes())
|
||||
.unwrap();
|
||||
for file in values {
|
||||
f.write_all(format!("+ {file}\n").as_bytes()).unwrap();
|
||||
|
@ -172,7 +150,7 @@ lang: en
|
|||
if let Some(s) = matches.get_one::<String>("BOOK") {
|
||||
println!(
|
||||
"{}",
|
||||
lformat!("Created {}, now you'll have to complete it!", s)
|
||||
t!("msg.created", file = s)
|
||||
);
|
||||
}
|
||||
exit(0);
|
||||
|
@ -188,37 +166,24 @@ pub fn create_matches() -> ArgMatches {
|
|||
// in its own function for testing purpose
|
||||
fn app() -> clap::Command {
|
||||
lazy_static! {
|
||||
static ref ABOUT: String = lformat!("Render a Markdown book in EPUB, PDF or HTML.");
|
||||
static ref SINGLE: String = lformat!("Use a single Markdown file instead of a book configuration file");
|
||||
static ref EMOJI: String = lformat!("Force emoji usage even if it might not work on your system");
|
||||
static ref VERBOSE: String = lformat!("Print warnings in parsing/rendering");
|
||||
static ref QUIET: String = lformat!("Don't print info/error messages");
|
||||
static ref PROOFREAD: String = lformat!("Enable proofreading");
|
||||
static ref CREATE: String = lformat!("Create a new book with existing Markdown files");
|
||||
static ref AUTOGRAPH: String = lformat!("Prompts for an autograph for this book");
|
||||
static ref OUTPUT: String = lformat!("Specify output file");
|
||||
static ref LANG: String = lformat!("Set the runtime language used by Crowbook");
|
||||
static ref TO: String = lformat!("Generate specific format");
|
||||
static ref SET: String = lformat!("Set a list of book options");
|
||||
static ref NO_FANCY: String = lformat!("Disably fancy UI");
|
||||
static ref LIST_OPTIONS: String = lformat!("List all possible options");
|
||||
static ref LIST_OPTIONS_MD: String = lformat!("List all possible options, formatted in Markdown");
|
||||
static ref PRINT_TEMPLATE: String = lformat!("Prints the default content of a template");
|
||||
static ref BOOK: String = lformat!("File containing the book configuration file, or a Markdown file when called with --single");
|
||||
static ref STATS: String = lformat!("Print some project statistics");
|
||||
static ref TEMPLATE: String = lformat!("\
|
||||
{{bin}} {{version}} by {{author}}
|
||||
{{about}}
|
||||
|
||||
USAGE:
|
||||
{{usage}}
|
||||
|
||||
OPTIONS:
|
||||
{{options}}
|
||||
|
||||
ARGS:
|
||||
{{positionals}}
|
||||
");
|
||||
static ref ABOUT: String = t!("cmd.about");
|
||||
static ref SINGLE: String = t!("cmd.single");
|
||||
static ref EMOJI: String = t!("cmd.emoji");
|
||||
static ref VERBOSE: String = t!("cmd.verbose");
|
||||
static ref QUIET: String = t!("cmd.quiet");
|
||||
static ref CREATE: String = t!("cmd.create");
|
||||
static ref AUTOGRAPH: String = t!("cmd.autograph");
|
||||
static ref OUTPUT: String = t!("cmd.output");
|
||||
static ref LANG: String = t!("cmd.lang");
|
||||
static ref TO: String = t!("cmd.to");
|
||||
static ref SET: String = t!("cmd.set");
|
||||
static ref NO_FANCY: String = t!("cmd.no_fancy");
|
||||
static ref LIST_OPTIONS: String = t!("cmd.list_options");
|
||||
static ref LIST_OPTIONS_MD: String = t!("cmd.list_options_md");
|
||||
static ref PRINT_TEMPLATE: String = t!("cmd.template");
|
||||
static ref BOOK: String = t!("cmd.book");
|
||||
static ref STATS: String = t!("cmd.stats");
|
||||
static ref TEMPLATE: String = t!("clap.template");
|
||||
}
|
||||
|
||||
let app = Command::new("crowbook")
|
||||
|
@ -269,13 +234,6 @@ ARGS:
|
|||
.help(QUIET.as_str())
|
||||
.conflicts_with("verbose"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("proofread")
|
||||
.short('p')
|
||||
.long("poofread")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help(PROOFREAD.as_str()),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("files")
|
||||
.short('c')
|
||||
|
@ -305,10 +263,6 @@ ARGS:
|
|||
"tex",
|
||||
"odt",
|
||||
"html.dir",
|
||||
"proofread.html",
|
||||
"proofread.html.dir",
|
||||
"proofread.pdf",
|
||||
"proofread.tex",
|
||||
])
|
||||
.help(TO.as_str()),
|
||||
)
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
extern crate crowbook;
|
||||
extern crate crowbook_intl_runtime;
|
||||
extern crate yaml_rust;
|
||||
|
||||
#[macro_use]
|
||||
mod localize_macros;
|
||||
#[cfg(feature = "binary")]
|
||||
|
@ -13,6 +9,9 @@ mod real_main;
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
rust_i18n::i18n!("lang/bin", fallback="en");
|
||||
|
||||
|
||||
#[cfg(feature = "binary")]
|
||||
fn main() {
|
||||
crate::real_main::real_main();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2016-2022 Élisabeth HENRY.
|
||||
// Copyright (C) 2016-2023 Élisabeth HENRY.
|
||||
//
|
||||
// This file is part of Crowbook.
|
||||
//
|
||||
|
@ -17,11 +17,10 @@
|
|||
|
||||
use crate::helpers::*;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crowbook::Stats;
|
||||
use crowbook::{Book, BookOptions, Result};
|
||||
use crowbook_intl_runtime::set_lang;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use simplelog::{ConfigBuilder, LevelFilter, SimpleLogger, TermLogger, WriteLogger};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
|
@ -29,6 +28,8 @@ use std::io;
|
|||
use std::io::Read;
|
||||
use std::process::exit;
|
||||
use yaml_rust::Yaml;
|
||||
use rust_i18n::t;
|
||||
|
||||
|
||||
/// Render a book to specific format
|
||||
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
|
||||
|
@ -66,9 +67,9 @@ pub fn try_main() -> Result<()> {
|
|||
});
|
||||
if let Some(val) = lang {
|
||||
if val.starts_with("fr") {
|
||||
set_lang("fr");
|
||||
rust_i18n::set_locale("fr");
|
||||
} else {
|
||||
set_lang("en");
|
||||
rust_i18n::set_locale("en");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +111,7 @@ pub fn try_main() -> Result<()> {
|
|||
exit(0);
|
||||
}
|
||||
Err(_) => print_error_and_exit(
|
||||
&lformat!("{} is not a valid template name.", template),
|
||||
&t!("error.invalid_template", template = template),
|
||||
emoji,
|
||||
),
|
||||
}
|
||||
|
@ -122,10 +123,7 @@ pub fn try_main() -> Result<()> {
|
|||
let book = matches.get_one::<String>("BOOK");
|
||||
if book.is_none() {
|
||||
print_error_and_exit(
|
||||
&lformat!(
|
||||
"You must pass the file of a book configuration \
|
||||
file.\nFor more information try --help."
|
||||
),
|
||||
&t!("error.no_file"),
|
||||
emoji,
|
||||
);
|
||||
}
|
||||
|
@ -173,7 +171,7 @@ pub fn try_main() -> Result<()> {
|
|||
{
|
||||
let mut book = Book::new();
|
||||
if matches.get_flag("autograph") {
|
||||
println!("{}", &lformat!("Enter autograph: "));
|
||||
println!("{}", &t!("msg.autograph"));
|
||||
let mut autograph = String::new();
|
||||
match io::stdin().read_to_string(&mut autograph) {
|
||||
Ok(_) => {
|
||||
|
@ -184,7 +182,7 @@ pub fn try_main() -> Result<()> {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
|
||||
Err(_) => print_error(&t!("error.autograph") , emoji),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +234,7 @@ pub fn try_main() -> Result<()> {
|
|||
file.read_to_string(&mut errors).unwrap();
|
||||
if !errors.is_empty() {
|
||||
print_warning(
|
||||
&lformat!("Crowbook exited successfully, but the following errors occurred:"),
|
||||
&t!("error.occurred"),
|
||||
emoji,
|
||||
);
|
||||
// Non-efficient dedup algorithm but we need to keep the order
|
||||
|
|
212
src/lib/book.rs
212
src/lib/book.rs
|
@ -48,6 +48,7 @@ use std::path::{Path, PathBuf};
|
|||
use numerals::roman::Roman;
|
||||
use rayon::prelude::*;
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
use rust_i18n::t;
|
||||
|
||||
/// Type of header (part or chapter)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -170,20 +171,20 @@ impl<'a> Book<'a> {
|
|||
|
||||
book.add_format(
|
||||
"html",
|
||||
lformat!("HTML (standalone page)"),
|
||||
t!("format.html_single"),
|
||||
Box::new(HtmlSingle {}),
|
||||
)
|
||||
.add_format(
|
||||
"html.dir",
|
||||
lformat!("HTML (multiple pages)"),
|
||||
t!("format.html_dir"),
|
||||
Box::new(HtmlDir {}),
|
||||
)
|
||||
.add_format("tex", lformat!("LaTeX"), Box::new(Latex {}))
|
||||
.add_format("pdf", lformat!("PDF"), Box::new(Pdf {}))
|
||||
.add_format("epub", lformat!("EPUB"), Box::new(Epub {}))
|
||||
.add_format("tex", t!("format.tex"), Box::new(Latex {}))
|
||||
.add_format("pdf", t!("format.pdf"), Box::new(Pdf {}))
|
||||
.add_format("epub", t!("format.epub"), Box::new(Epub {}))
|
||||
.add_format(
|
||||
"html.if",
|
||||
lformat!("HTML (interactive fiction)"),
|
||||
t!("html_if"),
|
||||
Box::new(HtmlIf {}),
|
||||
);
|
||||
book
|
||||
|
@ -256,8 +257,8 @@ impl<'a> Book<'a> {
|
|||
if let Err(err) = self.options.set(key, value) {
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Error initializing book: could not set {key} to {value}: {error}",
|
||||
t!(
|
||||
"error.book_init",
|
||||
key = key,
|
||||
value = value,
|
||||
error = err
|
||||
|
@ -290,7 +291,7 @@ impl<'a> Book<'a> {
|
|||
self.options.source = Source::new(filename.as_str());
|
||||
|
||||
let f = File::open(path.as_ref()).map_err(|_| {
|
||||
Error::file_not_found(Source::empty(), lformat!("book"), filename.clone())
|
||||
Error::file_not_found(Source::empty(), t!("format.book"), filename.clone())
|
||||
})?;
|
||||
// Set book path to book's directory
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
|
@ -304,11 +305,8 @@ impl<'a> Book<'a> {
|
|||
if err.is_config_parser() && path.as_ref().ends_with(".md") {
|
||||
let err = Error::default(
|
||||
Source::empty(),
|
||||
lformat!(
|
||||
"could not parse {file} as a book \
|
||||
file.\nMaybe you meant to run crowbook \
|
||||
with the --single argument?",
|
||||
file = misc::normalize(path)
|
||||
t!("error.parse_book",
|
||||
file = misc::normalize(path)
|
||||
),
|
||||
);
|
||||
Err(err)
|
||||
|
@ -395,7 +393,7 @@ impl<'a> Book<'a> {
|
|||
Err(err) => {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("YAML block was not valid YAML: {error}", error = err),
|
||||
t!("error.yaml_block", error = err),
|
||||
))
|
||||
}
|
||||
Ok(mut docs) => {
|
||||
|
@ -412,10 +410,7 @@ impl<'a> Book<'a> {
|
|||
} else {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"YAML part of the book is not a \
|
||||
valid hashmap"
|
||||
),
|
||||
t!("error.parse_book"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -458,27 +453,24 @@ impl<'a> Book<'a> {
|
|||
if words.len() > 1 {
|
||||
return Err(Error::config_parser(
|
||||
source,
|
||||
lformat!(
|
||||
"chapter filenames must not contain \
|
||||
whitespace"
|
||||
),
|
||||
t!("error.chapter_whitespace"),
|
||||
));
|
||||
} else if words.is_empty() {
|
||||
return Err(Error::config_parser(
|
||||
source,
|
||||
lformat!("no chapter name specified"),
|
||||
t!("error.no_chapter_name"),
|
||||
));
|
||||
}
|
||||
Ok(words[0])
|
||||
}
|
||||
|
||||
self.bar_set_message(Crowbar::Main, &lformat!("setting options"));
|
||||
self.bar_set_message(Crowbar::Main, &t!("ui.options"));
|
||||
|
||||
let mut s = String::new();
|
||||
source.read_to_string(&mut s).map_err(|err| {
|
||||
Error::config_parser(
|
||||
Source::empty(),
|
||||
lformat!("could not read source: {error}", error = err),
|
||||
t!("error.source", error = err),
|
||||
)
|
||||
})?;
|
||||
|
||||
|
@ -552,11 +544,11 @@ impl<'a> Book<'a> {
|
|||
// Update cleaner according to options (autoclean/lang)
|
||||
self.update_cleaner();
|
||||
|
||||
self.bar_set_message(Crowbar::Main, &lformat!("Parsing chapters"));
|
||||
self.bar_set_message(Crowbar::Main, &t!("ui.chapters"));
|
||||
|
||||
// Parse chapters
|
||||
let lines: Vec<_> = lines.collect();
|
||||
self.add_second_bar(&lformat!("Processing..."), lines.len() as u64);
|
||||
self.add_second_bar(&t!("ui.processing"), lines.len() as u64);
|
||||
for line in lines {
|
||||
self.inc_second_bar();
|
||||
line_number += 1;
|
||||
|
@ -599,17 +591,14 @@ impl<'a> Book<'a> {
|
|||
if parts.len() != 2 {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"ill-formatted line specifying \
|
||||
chapter number"
|
||||
),
|
||||
t!("error.format_line"),
|
||||
));
|
||||
}
|
||||
let file = get_filename(&self.source, parts[1])?;
|
||||
let number = parts[0].parse::<i32>().map_err(|err| {
|
||||
Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("error parsing chapter number: {error}", error = err),
|
||||
t!("error.chapter_number", error = err),
|
||||
)
|
||||
})?;
|
||||
self.add_chapter(Number::Specified(number), file, true)?;
|
||||
|
@ -637,33 +626,27 @@ impl<'a> Book<'a> {
|
|||
if parts.len() != 2 {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"ill-formatted line specifying \
|
||||
part number"
|
||||
),
|
||||
t!("error.part_number_line")
|
||||
));
|
||||
}
|
||||
let file = get_filename(&self.source, parts[1])?;
|
||||
let number = parts[0].parse::<i32>().map_err(|err| {
|
||||
Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("error parsing part number: {error}", error = err),
|
||||
t!("error.part_number", error = err),
|
||||
)
|
||||
})?;
|
||||
self.add_chapter(Number::SpecifiedPart(number), file, true)?;
|
||||
} else {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("found invalid part definition in the chapter list"),
|
||||
t!("error.part_definition"),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"found invalid chapter definition in \
|
||||
the chapter list"
|
||||
),
|
||||
t!("error.chapter_definition"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -675,13 +658,6 @@ impl<'a> Book<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine whether proofreading is activated or not
|
||||
fn is_proofread(&self) -> bool {
|
||||
self.options.get_bool("proofread").unwrap()
|
||||
&& (self.options.get("output.proofread.html").is_ok()
|
||||
|| self.options.get("output.proofread.html.dir").is_ok()
|
||||
|| self.options.get("output.proofread.pdf").is_ok())
|
||||
}
|
||||
|
||||
/// Generates output files acccording to book options.
|
||||
///
|
||||
|
@ -709,9 +685,6 @@ impl<'a> Book<'a> {
|
|||
.formats
|
||||
.keys()
|
||||
.filter(|fmt| {
|
||||
if !self.is_proofread() && fmt.contains("proofread") {
|
||||
return false;
|
||||
}
|
||||
self.options.get_path(&format!("output.{fmt}")).is_ok()
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
|
@ -735,7 +708,7 @@ impl<'a> Book<'a> {
|
|||
self.render_format_with_bar(fmt, i);
|
||||
});
|
||||
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &t!("ui.finished"));
|
||||
|
||||
// if handles.is_empty() {
|
||||
// Logger::display_warning(lformat!("Crowbook generated no file because no output file was \
|
||||
|
@ -748,7 +721,7 @@ impl<'a> Book<'a> {
|
|||
let mut key = String::from("output.");
|
||||
key.push_str(format);
|
||||
if let Ok(path) = self.options.get_path(&key) {
|
||||
self.bar_set_message(Crowbar::Spinner(bar), &lformat!("rendering..."));
|
||||
self.bar_set_message(Crowbar::Spinner(bar), &t!("ui.rendering_format"));
|
||||
let result = self.render_format_to_file_with_bar(format, path, bar);
|
||||
if let Err(err) = result {
|
||||
self.bar_finish(
|
||||
|
@ -758,8 +731,7 @@ impl<'a> Book<'a> {
|
|||
);
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Error rendering {name}: {error}",
|
||||
t!("error.rendering",
|
||||
name = format,
|
||||
error = err
|
||||
)
|
||||
|
@ -776,7 +748,7 @@ impl<'a> Book<'a> {
|
|||
) -> Result<()> {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!("Attempting to generate {format}...", format = format)
|
||||
t!("msg.attempting", format = format)
|
||||
);
|
||||
let path = path.into();
|
||||
match self.formats.get(format) {
|
||||
|
@ -790,14 +762,13 @@ impl<'a> Book<'a> {
|
|||
{
|
||||
s.to_string_lossy().into_owned()
|
||||
} else {
|
||||
return Err(Error::default(&self.source, lformat!("output to {format} set to auto but can't find book file name to infer it",
|
||||
return Err(Error::default(&self.source, t!("error.infer",
|
||||
format = description)));
|
||||
};
|
||||
let file = renderer.auto_path(&file).map_err(|_| {
|
||||
Error::default(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"the {format} renderer does not support auto for output path",
|
||||
t!("error.support",
|
||||
format = description
|
||||
),
|
||||
)
|
||||
|
@ -808,8 +779,8 @@ impl<'a> Book<'a> {
|
|||
};
|
||||
renderer.render_to_file(self, &path)?;
|
||||
let path = misc::normalize(path);
|
||||
let msg = lformat!(
|
||||
"Succesfully generated {format}: {path}",
|
||||
let msg = t!(
|
||||
"msg.generated",
|
||||
format = description,
|
||||
path = &path
|
||||
);
|
||||
|
@ -817,13 +788,13 @@ impl<'a> Book<'a> {
|
|||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {path}", path = path),
|
||||
&t!("ui.generated", path = path),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(Error::default(
|
||||
Source::empty(),
|
||||
lformat!("unknown format {format}", format = format),
|
||||
t!("error.unknown", format = format),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -842,7 +813,7 @@ impl<'a> Book<'a> {
|
|||
pub fn render_format_to<T: Write>(&mut self, format: &str, f: &mut T) -> Result<()> {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!("Attempting to generate {format}...", format = format)
|
||||
t!("msg.attempting", format = format)
|
||||
);
|
||||
let bar = self.add_spinner_to_multibar(format);
|
||||
match self.formats.get(format) {
|
||||
|
@ -851,12 +822,12 @@ impl<'a> Book<'a> {
|
|||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {format}", format = format),
|
||||
&t!("ui.generated", path = format),
|
||||
);
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &t!("ui.finished"));
|
||||
info!(
|
||||
"{}",
|
||||
lformat!("Succesfully generated {format}", format = description)
|
||||
t!("msg.generated_short", format = description)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -864,9 +835,9 @@ impl<'a> Book<'a> {
|
|||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&lformat!("{error}", error = e),
|
||||
&format!("{error}", error = e),
|
||||
);
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Error, &lformat!("ERROR"));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Error, &t!("ui.error"));
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
|
@ -874,11 +845,11 @@ impl<'a> Book<'a> {
|
|||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&lformat!("unknown format"),
|
||||
&t!("error.unknown_short"),
|
||||
);
|
||||
Err(Error::default(
|
||||
Source::empty(),
|
||||
lformat!("unknown format {format}", format = format),
|
||||
t!("error.unknown", format = format),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -904,7 +875,7 @@ impl<'a> Book<'a> {
|
|||
pub fn render_format_to_file<P: Into<PathBuf>>(&mut self, format: &str, path: P) -> Result<()> {
|
||||
let bar = self.add_spinner_to_multibar(format);
|
||||
self.render_format_to_file_with_bar(format, path, bar)?;
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &t!("ui.finished"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -920,21 +891,21 @@ impl<'a> Book<'a> {
|
|||
) -> Result<&mut Self> {
|
||||
self.bar_set_message(
|
||||
Crowbar::Main,
|
||||
&lformat!("Processing {file}...", file = file),
|
||||
&t!("ui.processing_file", file = file),
|
||||
);
|
||||
let mut content = String::new();
|
||||
source.read_to_string(&mut content).map_err(|_| {
|
||||
Error::parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"file {file} contains invalid UTF-8",
|
||||
t!(
|
||||
"error.utf8",
|
||||
file = misc::normalize(file)
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
// parse the file
|
||||
self.bar_set_message(Crowbar::Second, &lformat!("Parsing..."));
|
||||
self.bar_set_message(Crowbar::Second, &t!("ui.parsing..."));
|
||||
|
||||
let mut parser = Parser::from(self);
|
||||
parser.set_source_file(file);
|
||||
|
@ -954,9 +925,8 @@ impl<'a> Book<'a> {
|
|||
if offset.starts_with("..") {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Warning: book contains chapter '{file}' in a directory above \
|
||||
the book file, this might cause problems",
|
||||
t!(
|
||||
"warn.above",
|
||||
file = misc::normalize(file)
|
||||
)
|
||||
);
|
||||
|
@ -1020,7 +990,7 @@ impl<'a> Book<'a> {
|
|||
let new = *n + level;
|
||||
if !(0..=6).contains(&new) {
|
||||
return Err(Error::parser(Source::new(file),
|
||||
lformat!("this subchapter contains a heading that, when adjusted, is not in the right range ({} instead of [0-6])", new)));
|
||||
t!("error.heading", n = new)));
|
||||
}
|
||||
*n = new;
|
||||
}
|
||||
|
@ -1050,11 +1020,7 @@ impl<'a> Book<'a> {
|
|||
) -> Result<&mut Self> {
|
||||
self.bar_set_message(
|
||||
Crowbar::Main,
|
||||
&lformat!("Parsing {file}", file = misc::normalize(file)),
|
||||
);
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!("Parsing chapter: {file}...", file = misc::normalize(file))
|
||||
&t!("ui.parsing_file", file = misc::normalize(file)),
|
||||
);
|
||||
|
||||
// try to open file
|
||||
|
@ -1062,7 +1028,7 @@ impl<'a> Book<'a> {
|
|||
let f = File::open(&path).map_err(|_| {
|
||||
Error::file_not_found(
|
||||
&self.source,
|
||||
lformat!("book chapter"),
|
||||
t!("format.book_chapter"),
|
||||
format!("{}", path.display()),
|
||||
)
|
||||
})?;
|
||||
|
@ -1137,7 +1103,7 @@ impl<'a> Book<'a> {
|
|||
_ => {
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("invalid template '{template}'"),
|
||||
t!("error.invalid_template"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
@ -1149,7 +1115,7 @@ impl<'a> Book<'a> {
|
|||
f.read_to_string(&mut res).map_err(|_| {
|
||||
Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("file '{file}' could not be read", file = s),
|
||||
t!("error.read_file", file = s),
|
||||
)
|
||||
})?;
|
||||
Ok(Cow::Owned(res))
|
||||
|
@ -1171,8 +1137,8 @@ impl<'a> Book<'a> {
|
|||
self.options.get_str(tpl).unwrap().to_owned())
|
||||
.map_err(|e| Error::template(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"could not compile '{template}': {error}",
|
||||
t!(
|
||||
"error.compile_template",
|
||||
template = "tpl",
|
||||
error = e
|
||||
))
|
||||
|
@ -1197,8 +1163,8 @@ impl<'a> Book<'a> {
|
|||
if n <= 0 {
|
||||
return Err(Error::render(
|
||||
Source::empty(),
|
||||
lformat!(
|
||||
"can not use roman numerals with zero or negative chapter numbers ({n})",
|
||||
t!(
|
||||
"error.roman_numerals",
|
||||
n = n
|
||||
),
|
||||
));
|
||||
|
@ -1314,9 +1280,8 @@ impl<'a> Book<'a> {
|
|||
Err(err) => {
|
||||
return Err(Error::render(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"could not render `{key}` for \
|
||||
metadata:\n{error}",
|
||||
t!(
|
||||
"error.render_key",
|
||||
key = &key,
|
||||
error = err
|
||||
),
|
||||
|
@ -1349,9 +1314,10 @@ impl<'a> Book<'a> {
|
|||
Ok(result) => Ok(result),
|
||||
Err(err) => Err(Error::template(
|
||||
source,
|
||||
lformat!(
|
||||
"could not compile '{template_name}': {:#}",
|
||||
err
|
||||
t!(
|
||||
"error.compile_template",
|
||||
template = template_name,
|
||||
error = format!("{:#}", err)
|
||||
),
|
||||
)),
|
||||
}
|
||||
|
@ -1384,24 +1350,18 @@ impl<'a> Book<'a> {
|
|||
if let Some(old_value) = opt {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Inline YAML block \
|
||||
replaced {:?} \
|
||||
previously set to \
|
||||
{:?} to {:?}",
|
||||
key,
|
||||
old_value,
|
||||
value
|
||||
t!("debug.yaml_replace",
|
||||
key = format!("{:?}", key),
|
||||
old_val = format!("{:?}", old_value),
|
||||
new_val = format!("{:?}", value)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Inline YAML block \
|
||||
set {:?} to {:?}",
|
||||
key,
|
||||
value
|
||||
t!("debug.yaml_set",
|
||||
key = format!("{:?}", key),
|
||||
value = format!("{:?}", value)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1409,12 +1369,11 @@ impl<'a> Book<'a> {
|
|||
Err(e) => {
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Inline YAML block could \
|
||||
not set {:?} to {:?}: {}",
|
||||
key,
|
||||
value,
|
||||
e
|
||||
t!(
|
||||
"error.yaml_set",
|
||||
key = format!("{:?}", key,),
|
||||
value = format!("{:?}", value),
|
||||
err = e
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1425,10 +1384,8 @@ impl<'a> Book<'a> {
|
|||
} else {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Ignoring YAML \
|
||||
block:
|
||||
\n{block}",
|
||||
t!(
|
||||
"debug.yaml_ignore",
|
||||
block = &yaml_block
|
||||
)
|
||||
);
|
||||
|
@ -1437,18 +1394,13 @@ impl<'a> Book<'a> {
|
|||
Err(err) => {
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Found something that looked like a \
|
||||
YAML block:\n{block}",
|
||||
t!("debug.found_yaml_block",
|
||||
block = &yaml_block
|
||||
)
|
||||
);
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"... but it didn't parse correctly as \
|
||||
YAML('{error}'), so treating it like \
|
||||
Markdown.",
|
||||
t!("debug.found_yaml_block2",
|
||||
error = err
|
||||
)
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
use crate::book::{Book, Crowbar, CrowbarState};
|
||||
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use rust_i18n::t;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
@ -79,19 +80,7 @@ impl Book<'_> {
|
|||
.add(ProgressBar::new_spinner());
|
||||
b.enable_steady_tick(Duration::from_millis(200));
|
||||
self.bars.mainbar = Some(b);
|
||||
// let sty = ProgressStyle::default_spinner()
|
||||
// .tick_chars("🕛🕐🕑🕒🕓🕔🕔🕕🕖🕗🕘🕘🕙🕚V")
|
||||
// .tick_chars("/|\\-V")
|
||||
// .template("{spinner:.dim.bold.yellow} {prefix} {wide_msg}");
|
||||
self.bar_set_style(Crowbar::Main, CrowbarState::Running);
|
||||
// self.bars.guard = Some(thread::spawn(move || {
|
||||
// if let Err(_) = multibar.join() {
|
||||
// error!(
|
||||
// "{}",
|
||||
// lformat!("could not display fancy UI, try running crowbook with --no-fancy")
|
||||
// );
|
||||
// }
|
||||
// }));
|
||||
}
|
||||
|
||||
/// Sets a finished message to the progress bar, if it is set
|
||||
|
@ -148,12 +137,12 @@ impl Book<'_> {
|
|||
pub fn add_spinner_to_multibar(&mut self, key: &str) -> usize {
|
||||
if let Some(ref multibar) = self.bars.multibar {
|
||||
if let Some(ref mainbar) = self.bars.mainbar {
|
||||
mainbar.set_message(lformat!("Rendering..."));
|
||||
mainbar.set_message(t!("ui.rendering"));
|
||||
}
|
||||
|
||||
let bar = multibar.add(ProgressBar::new_spinner());
|
||||
bar.enable_steady_tick(Duration::from_millis(200));
|
||||
bar.set_message(lformat!("waiting..."));
|
||||
bar.set_message(t!("ui.waiting"));
|
||||
bar.set_prefix(format!("{key}:"));
|
||||
let i = self.bars.spinners.len();
|
||||
self.bars.spinners.push(bar);
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::error::{Error, Result, Source};
|
|||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use rust_i18n::t;
|
||||
|
||||
/// Trait that must be implemented by the various renderers to render a whole book.
|
||||
|
||||
|
@ -29,7 +30,7 @@ pub trait BookRenderer: Sync {
|
|||
fn auto_path(&self, _book_file: &str) -> Result<String> {
|
||||
Err(Error::default(
|
||||
Source::empty(),
|
||||
lformat!("This renderer does not support the auto output"),
|
||||
t!("error.renderer.no_output"),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -47,8 +48,8 @@ pub trait BookRenderer: Sync {
|
|||
let mut file = File::create(path).map_err(|err| {
|
||||
Error::default(
|
||||
Source::empty(),
|
||||
lformat!(
|
||||
"could not create file '{file}': {err}",
|
||||
t!(
|
||||
"error.renderer.file_creation",
|
||||
file = path.display(),
|
||||
err = err
|
||||
),
|
||||
|
@ -57,8 +58,8 @@ pub trait BookRenderer: Sync {
|
|||
file.write_all(&content).map_err(|err| {
|
||||
Error::default(
|
||||
Source::empty(),
|
||||
lformat!(
|
||||
"could not write book content to file '{file}': {err}",
|
||||
t!(
|
||||
"error.renderer.write",
|
||||
file = path.display(),
|
||||
err = err
|
||||
),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use rust_i18n::t;
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
|
||||
/// Structure for storing a book option
|
||||
|
@ -50,7 +52,7 @@ impl BookOption {
|
|||
BookOption::String(ref s) => Ok(s),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a string", self),
|
||||
t!("error.no_string", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +63,7 @@ impl BookOption {
|
|||
BookOption::StringVec(ref v) => Ok(v),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a string vector", self),
|
||||
t!("error.no_string_vector", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +74,7 @@ impl BookOption {
|
|||
BookOption::Path(ref s) => Ok(s),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a path", self),
|
||||
t!("error.no_path", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +85,7 @@ impl BookOption {
|
|||
BookOption::Bool(b) => Ok(b),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a bool", self),
|
||||
t!("error.no_bool", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +96,7 @@ impl BookOption {
|
|||
BookOption::Char(c) => Ok(c),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a char", self),
|
||||
t!("error.no_char", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +107,7 @@ impl BookOption {
|
|||
BookOption::Int(i) => Ok(i),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not an i32", self),
|
||||
t!("error.no_i32", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +118,7 @@ impl BookOption {
|
|||
BookOption::Float(f) => Ok(f),
|
||||
_ => Err(Error::book_option(
|
||||
Source::empty(),
|
||||
lformat!("{:?} is not a f32", self),
|
||||
t!("error.no_f32", s = format!("{:?}", self)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ use epub_builder::{
|
|||
ZipLibrary,
|
||||
};
|
||||
use upon::Template;
|
||||
use rust_i18n::t;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::convert::{AsMut, AsRef};
|
||||
|
@ -90,7 +91,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
} else {
|
||||
warn!(
|
||||
"{}",
|
||||
lformat!("Could not run zip command, falling back to zip library")
|
||||
t!("epub.zip_command")
|
||||
);
|
||||
ZipCommandOrLibrary::Library(ZipLibrary::new()
|
||||
.map_err(|err| Error::render(Source::empty(), format!("{}", err)))?)
|
||||
|
@ -254,7 +255,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
let f = fs::canonicalize(source).and_then(File::open).map_err(|_| {
|
||||
Error::file_not_found(
|
||||
&self.html.source,
|
||||
lformat!("image or cover"),
|
||||
t!("epub.image_or_cover"),
|
||||
source.to_owned(),
|
||||
)
|
||||
})?;
|
||||
|
@ -290,7 +291,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
.map_err(|_| {
|
||||
Error::file_not_found(
|
||||
&self.html.book.source,
|
||||
lformat!("additional resource from resources.files"),
|
||||
t!("epub.resources"),
|
||||
abs_path.to_string_lossy().into_owned(),
|
||||
)
|
||||
})?;
|
||||
|
@ -327,7 +328,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
if fs::metadata(&cover).is_err() {
|
||||
return Err(Error::file_not_found(
|
||||
&self.html.book.source,
|
||||
lformat!("cover"),
|
||||
t!("epub.cover"),
|
||||
cover,
|
||||
));
|
||||
}
|
||||
|
@ -349,10 +350,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
.into());
|
||||
Ok(template.render(&data).to_string()?)
|
||||
} else {
|
||||
panic!(
|
||||
"{}",
|
||||
lformat!("Why is this method called if cover is None???")
|
||||
);
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,10 +420,8 @@ impl<'a> EpubRenderer<'a> {
|
|||
} else {
|
||||
warn!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"EPUB ({source}): detected two chapter titles inside the \
|
||||
same markdown file, in a file where chapter titles are \
|
||||
not even rendered.",
|
||||
t!(
|
||||
"epub.ambiguous_invisible",
|
||||
source = self.html.source
|
||||
)
|
||||
);
|
||||
|
@ -459,16 +455,15 @@ impl<'a> EpubRenderer<'a> {
|
|||
} else {
|
||||
warn!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"EPUB ({source}): detected two chapters inside the same \
|
||||
markdown file.",
|
||||
t!(
|
||||
"epub.ambiguous",
|
||||
source = self.html.source
|
||||
)
|
||||
);
|
||||
warn!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"EPUB ({source}): conflict between: {title1} and {title2}",
|
||||
t!(
|
||||
"epub.title_conflict",
|
||||
source = self.html.source,
|
||||
title1 = self.chapter_title,
|
||||
title2 = s
|
||||
|
@ -487,9 +482,8 @@ impl<'a> EpubRenderer<'a> {
|
|||
None => {
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"EPUB: could not guess the format of {file} based on \
|
||||
extension. Assuming png.",
|
||||
t!(
|
||||
"epub.guess",
|
||||
file = s
|
||||
)
|
||||
);
|
||||
|
@ -529,10 +523,7 @@ impl<'a> EpubRenderer<'a> {
|
|||
let initial = chars.next().ok_or_else(|| {
|
||||
Error::parser(
|
||||
&html.book.source,
|
||||
lformat!(
|
||||
"empty str token, could not find \
|
||||
initial"
|
||||
),
|
||||
t!("error.initial"),
|
||||
)
|
||||
})?;
|
||||
let mut new_content = if initial.is_alphanumeric() {
|
||||
|
|
|
@ -21,6 +21,8 @@ use std::fmt;
|
|||
use std::result;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
/// Source of an error.
|
||||
///
|
||||
|
@ -107,15 +109,6 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new grammar check error.
|
||||
///
|
||||
/// Used when there is a problem connecting to languagetool
|
||||
pub fn grammar_check<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
|
||||
Error {
|
||||
source: source.into(),
|
||||
inner: Inner::GrammarCheck(msg.into()),
|
||||
}
|
||||
}
|
||||
/// Creates a new parser error.
|
||||
///
|
||||
/// Error when parsing markdown file.
|
||||
|
@ -275,9 +268,7 @@ impl error::Error for Error {
|
|||
| Inner::InvalidOption(ref s)
|
||||
| Inner::Render(ref s)
|
||||
| Inner::Template(ref s)
|
||||
| Inner::Syntect(ref s)
|
||||
| Inner::GrammarCheck(ref s) => s.as_ref(),
|
||||
|
||||
| Inner::Syntect(ref s) => s.as_ref(),
|
||||
Inner::FileNotFound(..) => "File not found",
|
||||
}
|
||||
}
|
||||
|
@ -296,30 +287,23 @@ impl fmt::Display for Error {
|
|||
|
||||
match self.inner {
|
||||
Inner::Default(ref s) => write!(f, "{s}"),
|
||||
Inner::GrammarCheck(ref s) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
lformat!("Error while trying to check grammar: {error}", error = s)
|
||||
)
|
||||
}
|
||||
Inner::Parser(ref s) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
lformat!("Error parsing markdown: {error}", error = s)
|
||||
t!("error.markdown", error = s)
|
||||
)
|
||||
}
|
||||
Inner::ConfigParser(ref s) => {
|
||||
f.write_str(&lformat!("Error parsing configuration file: "))?;
|
||||
f.write_str(&t!("error.config"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
Inner::FileNotFound(ref description, ref file) => {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
lformat!(
|
||||
"Could not find file '{file}' for {description}",
|
||||
t!(
|
||||
"error.file_not_found",
|
||||
file = file,
|
||||
description = description
|
||||
)
|
||||
|
@ -329,27 +313,27 @@ impl fmt::Display for Error {
|
|||
write!(
|
||||
f,
|
||||
"{}",
|
||||
lformat!("Error compiling template: {template}", template = s)
|
||||
t!("error.template", template = s)
|
||||
)
|
||||
}
|
||||
Inner::Render(ref s) => {
|
||||
f.write_str(&lformat!("Error during rendering: "))?;
|
||||
f.write_str(&t!("error.render_error"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
Inner::Zipper(ref s) => {
|
||||
f.write_str(&lformat!("Error during temporary files editing: "))?;
|
||||
f.write_str(&t!("error.zipper"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
Inner::BookOption(ref s) => {
|
||||
f.write_str(&lformat!("Error converting BookOption: "))?;
|
||||
f.write_str(&t!("error.bookoption"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
Inner::InvalidOption(ref s) => {
|
||||
f.write_str(&lformat!("Error accessing book option: "))?;
|
||||
f.write_str(&t!("error.invalid_option"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
Inner::Syntect(ref s) => {
|
||||
f.write_str(&lformat!("Error higligting syntax: "))?;
|
||||
f.write_str(&t!("error.syntect"))?;
|
||||
f.write_str(s)
|
||||
}
|
||||
}?;
|
||||
|
@ -372,7 +356,7 @@ impl From<FromUtf8Error> for Error {
|
|||
fn from(err: FromUtf8Error) -> Error {
|
||||
Error::render(
|
||||
Source::empty(),
|
||||
lformat!("UTF-8 error: {error}", error = err),
|
||||
t!("error.utf8_error", error = err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -381,7 +365,7 @@ impl From<std::str::Utf8Error> for Error {
|
|||
fn from(err: std::str::Utf8Error) -> Error {
|
||||
Error::render(
|
||||
Source::empty(),
|
||||
lformat!("UTF-8 error: {error}", error = err),
|
||||
t!("error.utf8_error", error = err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -424,8 +408,6 @@ enum Inner {
|
|||
InvalidOption(Cow<'static, str>),
|
||||
/// Error when compiling template
|
||||
Template(Cow<'static, str>),
|
||||
/// Error when connecting to LanguageTool
|
||||
GrammarCheck(Cow<'static, str>),
|
||||
/// Error when parsing code syntax
|
||||
Syntect(Cow<'static, str>),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2016, 2017, 2018 Élisabeth HENRY.
|
||||
// Copyright (C) 2016-2023 Élisabeth HENRY.
|
||||
//
|
||||
// This file is part of Crowbook.
|
||||
//
|
||||
|
@ -109,10 +109,6 @@ extern crate log;
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[cfg(feature = "proofread")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub use book::Book;
|
||||
pub use book_renderer::BookRenderer;
|
||||
pub use bookoption::BookOption;
|
||||
|
@ -127,6 +123,8 @@ pub use stats::Stats;
|
|||
pub use token::Data;
|
||||
pub use token::Token;
|
||||
|
||||
rust_i18n::i18n!("lang/lib", fallback="en");
|
||||
|
||||
#[macro_use]
|
||||
#[doc(hidden)]
|
||||
mod localize_macros;
|
||||
|
|
|
@ -1,666 +0,0 @@
|
|||
// Copyright (C) 2016 Élisabeth HENRY.
|
||||
//
|
||||
// This file is part of Crowbook.
|
||||
//
|
||||
// Crowbook is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation, either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Caribon is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received ba copy of the GNU Lesser General Public License
|
||||
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::token::Token;
|
||||
use crate::error::{Result, Error, Source};
|
||||
use crate::book::Book;
|
||||
|
||||
use std::mem;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::convert::AsRef;
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::BitOr;
|
||||
|
||||
use cmark::{Parser as CMParser, Event, Tag, Options};
|
||||
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
/// The list of features used in a document.
|
||||
pub struct Features {
|
||||
pub image: bool,
|
||||
pub blockquote: bool,
|
||||
pub codeblock: bool,
|
||||
pub ordered_list: bool,
|
||||
pub footnote: bool,
|
||||
pub table: bool,
|
||||
pub url: bool,
|
||||
pub subscript: bool,
|
||||
pub superscript: bool,
|
||||
}
|
||||
|
||||
impl Features {
|
||||
/// Creates a new set of features where all are set to false
|
||||
pub fn new() -> Features {
|
||||
Features {
|
||||
image: false,
|
||||
blockquote: false,
|
||||
codeblock: false,
|
||||
ordered_list: false,
|
||||
footnote: false,
|
||||
table: false,
|
||||
url: false,
|
||||
subscript: false,
|
||||
superscript: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl BitOr for Features {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
Features {
|
||||
image: self.image | rhs.image,
|
||||
blockquote: self.blockquote | rhs.blockquote,
|
||||
codeblock: self.codeblock | rhs.codeblock,
|
||||
ordered_list: self.ordered_list | rhs.ordered_list,
|
||||
footnote: self.footnote | rhs.footnote,
|
||||
table: self.table | rhs.table,
|
||||
url: self.url | rhs.url,
|
||||
subscript: self.subscript | rhs.subscript,
|
||||
superscript: self.superscript | rhs.superscript,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A parser that reads markdown and convert it to AST (a vector of `Token`s)
|
||||
///
|
||||
/// This AST can then be used by various renderes.
|
||||
///
|
||||
/// As this Parser uses Pulldown-cmark's one, it should be able to parse most
|
||||
/// *valid* CommonMark variant of Markdown.
|
||||
///
|
||||
/// Compared to other Markdown parser, it might fail more often on invalid code, e.g.
|
||||
/// footnotes references that are not defined anywhere.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crowbook::Parser;
|
||||
/// let mut parser = Parser::new();
|
||||
/// let result = parser.parse("Some *valid* Markdown[^1]\n\n[^1]: with a valid footnote");
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use crowbook::Parser;
|
||||
/// let mut parser = Parser::new();
|
||||
/// let result = parser.parse("Some footnote pointing to nothing[^1] ");
|
||||
/// assert!(result.is_err());
|
||||
/// ```
|
||||
pub struct Parser {
|
||||
footnotes: HashMap<String, Vec<Token>>,
|
||||
source: Source,
|
||||
features: Features,
|
||||
|
||||
html_as_text: bool,
|
||||
superscript: bool,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
/// Creates a parser
|
||||
pub fn new() -> Parser {
|
||||
Parser {
|
||||
footnotes: HashMap::new(),
|
||||
source: Source::empty(),
|
||||
features: Features::new(),
|
||||
html_as_text: true,
|
||||
superscript: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a parser with options from a book configuration file
|
||||
pub fn from(book: &Book) -> Parser {
|
||||
let mut parser = Parser::new();
|
||||
parser.html_as_text = book.options.get_bool("crowbook.html_as_text").unwrap();
|
||||
parser.superscript = book.options.get_bool("crowbook.markdown.superscript").unwrap();
|
||||
parser
|
||||
}
|
||||
|
||||
/// Enable/disable HTML as text
|
||||
pub fn html_as_text(&mut self, b: bool) {
|
||||
self.html_as_text = b;
|
||||
}
|
||||
|
||||
/// Sets a parser's source file
|
||||
pub fn set_source_file(&mut self, s: &str) {
|
||||
self.source = Source::new(s);
|
||||
}
|
||||
|
||||
/// Parse a file and returns an AST or an error
|
||||
pub fn parse_file<P: AsRef<Path>>(&mut self, filename: P) -> Result<Vec<Token>> {
|
||||
let path: &Path = filename.as_ref();
|
||||
let mut f = File::open(path)
|
||||
.map_err(|_| {
|
||||
Error::file_not_found(&self.source,
|
||||
lformat!("markdown file"),
|
||||
format!("{}", path.display()))
|
||||
})?;
|
||||
let mut s = String::new();
|
||||
|
||||
f.read_to_string(&mut s)
|
||||
.map_err(|_| {
|
||||
Error::parser(&self.source,
|
||||
lformat!("file {file} contains invalid UTF-8, could not parse it",
|
||||
file = path.display()))
|
||||
})?;
|
||||
self.parse(&s)
|
||||
}
|
||||
|
||||
/// Parse a string and returns an AST an Error.
|
||||
pub fn parse(&mut self, s: &str) -> Result<Vec<Token>> {
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
let mut p = CMParser::new_ext(s, opts);
|
||||
|
||||
|
||||
let mut res = vec![];
|
||||
self.parse_events(&mut p, &mut res, None)?;
|
||||
|
||||
self.parse_footnotes(&mut res)?;
|
||||
|
||||
collapse(&mut res);
|
||||
|
||||
find_standalone(&mut res);
|
||||
|
||||
// Transform superscript and subscript
|
||||
if self.superscript {
|
||||
self.parse_super_vec(&mut res);
|
||||
self.parse_sub_vec(&mut res);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Parse an inline string and returns a list of `Token`.
|
||||
///
|
||||
/// This function removes the outermost `Paragraph` in most of the
|
||||
/// cases, as it is meant to be used for an inline string (e.g. metadata)
|
||||
pub fn parse_inline(&mut self, s: &str) -> Result<Vec<Token>> {
|
||||
let mut tokens = self.parse(s)?;
|
||||
// Unfortunately, parser will put all this in a paragraph, so we might need to remove it.
|
||||
if tokens.len() == 1 {
|
||||
let res = match tokens[0] {
|
||||
Token::Paragraph(ref mut v) => Some(mem::replace(v, vec![])),
|
||||
_ => None,
|
||||
};
|
||||
match res {
|
||||
Some(tokens) => Ok(tokens),
|
||||
_ => Ok(tokens),
|
||||
}
|
||||
} else {
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of features used by this parser
|
||||
pub fn features(&self) -> Features {
|
||||
self.features
|
||||
}
|
||||
|
||||
|
||||
/// Replace footnote reference with their definition
|
||||
fn parse_footnotes(&mut self, v: &mut Vec<Token>) -> Result<()> {
|
||||
for token in v {
|
||||
match *token {
|
||||
Token::Footnote(ref mut content) => {
|
||||
let reference = if let Token::Str(ref text) = content[0] {
|
||||
text.clone()
|
||||
} else {
|
||||
panic!("Reference is not a vector of a single Token::Str");
|
||||
};
|
||||
if let Some(in_vec) = self.footnotes.get(&reference) {
|
||||
*content = in_vec.clone();
|
||||
} else {
|
||||
return Err(Error::parser(&self.source,
|
||||
lformat!("footnote reference {reference} does \
|
||||
not have a matching definition",
|
||||
reference = &reference)));
|
||||
}
|
||||
}
|
||||
Token::Paragraph(ref mut vec) |
|
||||
Token::Header(_, ref mut vec) |
|
||||
Token::Emphasis(ref mut vec) |
|
||||
Token::Strong(ref mut vec) |
|
||||
Token::Code(ref mut vec) |
|
||||
Token::BlockQuote(ref mut vec) |
|
||||
Token::CodeBlock(_, ref mut vec) |
|
||||
Token::List(ref mut vec) |
|
||||
Token::OrderedList(_, ref mut vec) |
|
||||
Token::Item(ref mut vec) |
|
||||
Token::Table(_, ref mut vec) |
|
||||
Token::TableHead(ref mut vec) |
|
||||
Token::TableRow(ref mut vec) |
|
||||
Token::TableCell(ref mut vec) |
|
||||
Token::Link(_, _, ref mut vec) |
|
||||
Token::Image(_, _, ref mut vec) => self.parse_footnotes(vec)?,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Looks for super script in a vector of tokens
|
||||
fn parse_super_vec(&mut self, v: &mut Vec<Token>) {
|
||||
for i in 0..v.len() {
|
||||
let new = if v[i].is_str() {
|
||||
if let Token::Str(ref s) = v[i] {
|
||||
parse_super_sub(s, b'^')
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
if v[i].is_code() || !v[i].is_container() {
|
||||
continue;
|
||||
}
|
||||
if let Some(ref mut inner) = v[i].inner_mut() {
|
||||
self.parse_super_vec(inner);
|
||||
}
|
||||
None
|
||||
};
|
||||
if let Some(mut new) = new {
|
||||
self.features.superscript = true;
|
||||
let mut post = v.split_off(i);
|
||||
post.remove(0);
|
||||
self.parse_super_vec(&mut post);
|
||||
v.append(&mut new);
|
||||
v.append(&mut post);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks for subscript in a vector of token
|
||||
fn parse_sub_vec(&mut self, v: &mut Vec<Token>) {
|
||||
for i in 0..v.len() {
|
||||
let new = if v[i].is_str() {
|
||||
if let Token::Str(ref s) = v[i] {
|
||||
parse_super_sub(s, b'~')
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
if v[i].is_code() || !v[i].is_container() {
|
||||
continue;
|
||||
}
|
||||
if let Some(ref mut inner) = v[i].inner_mut() {
|
||||
self.parse_sub_vec(inner);
|
||||
}
|
||||
None
|
||||
};
|
||||
if let Some(mut new) = new {
|
||||
self.features.subscript = true;
|
||||
let mut post = v.split_off(i);
|
||||
post.remove(0);
|
||||
self.parse_sub_vec(&mut post);
|
||||
v.append(&mut new);
|
||||
v.append(&mut post);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_events<'a>(&mut self,
|
||||
p: &mut CMParser<'a>,
|
||||
v: &mut Vec<Token>,
|
||||
current_tag: Option<&Tag>)
|
||||
-> Result<()> {
|
||||
while let Some(event) = p.next() {
|
||||
match event {
|
||||
Event::Html(text) | Event::InlineHtml(text) => {
|
||||
if self.html_as_text {
|
||||
v.push(Token::Str(text.into_owned()));
|
||||
} else {
|
||||
debug!("{}", lformat!("ignoring HTML block '{}'", text));
|
||||
}
|
||||
},
|
||||
|
||||
Event::Text(text) => {
|
||||
v.push(Token::Str(text.into_owned()));
|
||||
}
|
||||
Event::Start(tag) => self.parse_tag(p, v, tag)?,
|
||||
Event::End(tag) => {
|
||||
debug_assert!(format!("{:?}", Some(&tag)) == format!("{:?}", current_tag),
|
||||
format!("Error: opening and closing tags mismatch!\n{:?} ≠ \
|
||||
{:?}",
|
||||
tag,
|
||||
current_tag));
|
||||
break;
|
||||
}
|
||||
Event::SoftBreak => v.push(Token::SoftBreak),
|
||||
Event::HardBreak => v.push(Token::HardBreak),
|
||||
Event::FootnoteReference(text) => {
|
||||
v.push(Token::Footnote(vec![Token::Str(text.into_owned())]))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_tag<'a>(&mut self,
|
||||
p: &mut CMParser<'a>,
|
||||
v: &mut Vec<Token>,
|
||||
tag: Tag<'a>)
|
||||
-> Result<()> {
|
||||
let mut res = vec![];
|
||||
|
||||
self.parse_events(p, &mut res, Some(&tag))?;
|
||||
|
||||
|
||||
let token = match tag {
|
||||
Tag::Paragraph => Token::Paragraph(res),
|
||||
Tag::Emphasis => Token::Emphasis(res),
|
||||
Tag::Strong => Token::Strong(res),
|
||||
Tag::Code => Token::Code(res),
|
||||
Tag::Header(x) => Token::Header(x, res),
|
||||
Tag::Link(url, title) => {
|
||||
self.features.url = true;
|
||||
Token::Link(url.into_owned(), title.into_owned(), res)
|
||||
},
|
||||
Tag::Image(url, title) => {
|
||||
self.features.image = true;
|
||||
Token::Image(url.into_owned(), title.into_owned(), res)
|
||||
},
|
||||
Tag::Rule => Token::Rule,
|
||||
Tag::List(opt) => {
|
||||
if let Some(n) = opt {
|
||||
self.features.ordered_list = true;
|
||||
Token::OrderedList(n, res)
|
||||
} else {
|
||||
Token::List(res)
|
||||
}
|
||||
}
|
||||
Tag::Item => Token::Item(res),
|
||||
Tag::BlockQuote => {
|
||||
self.features.blockquote = true;
|
||||
Token::BlockQuote(res)
|
||||
},
|
||||
Tag::CodeBlock(language) => {
|
||||
self.features.codeblock = true;
|
||||
Token::CodeBlock(language.into_owned(), res)
|
||||
},
|
||||
Tag::Table(v) => {
|
||||
self.features.table = true;
|
||||
// TODO: actually use v's alignments
|
||||
Token::Table(v.len() as i32, res)
|
||||
},
|
||||
Tag::TableHead => Token::TableHead(res),
|
||||
Tag::TableRow => Token::TableRow(res),
|
||||
Tag::TableCell => Token::TableCell(res),
|
||||
Tag::FootnoteDefinition(reference) => {
|
||||
if self.footnotes.contains_key(reference.as_ref()) {
|
||||
warn!("{}", lformat!("in {file}, found footnote definition for \
|
||||
note '{reference}' but previous \
|
||||
definition already exist, overriding it",
|
||||
file = self.source,
|
||||
reference = reference));
|
||||
}
|
||||
self.footnotes.insert(reference.into_owned(), res);
|
||||
Token::SoftBreak
|
||||
}
|
||||
};
|
||||
v.push(token);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Look to a string and see if there is some superscript or subscript in it.
|
||||
/// If there, returns a vec of tokens.
|
||||
///
|
||||
/// params: s: the string to parse, c, either b'^' for superscript or b'~' for subscript.
|
||||
fn parse_super_sub(s: &str, c: u8) -> Option<Vec<Token>> {
|
||||
let match_indices:Vec<_> = s.match_indices(c as char).collect();
|
||||
if match_indices.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let to_escape = format!("\\{}", c as char);
|
||||
let escaped = format!("{}", c as char);
|
||||
let escape = |s: String| -> String {
|
||||
s.replace(&to_escape, &escaped)
|
||||
};
|
||||
for (begin, _) in match_indices {
|
||||
let bytes = s.as_bytes();
|
||||
let len = bytes.len();
|
||||
// Check if ^ was escaped
|
||||
if begin > 0 && bytes[begin - 1] == b'\\' {
|
||||
continue;
|
||||
} else if begin + 1 >= len {
|
||||
return None;
|
||||
} else {
|
||||
let mut i = begin + 1;
|
||||
let mut sup = vec![];
|
||||
let mut end = None;
|
||||
while i < len {
|
||||
match bytes[i] {
|
||||
b'\\' => {
|
||||
if i+1 < len && bytes[i+1] == b' ' {
|
||||
sup.push(b' ');
|
||||
i += 2;
|
||||
} else if i + 1 < len && bytes[i+1] == c {
|
||||
sup.push(c);
|
||||
i += 2;
|
||||
} else {
|
||||
sup.push(b'\\');
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
b' ' => {
|
||||
return None;
|
||||
},
|
||||
b if b == c => {
|
||||
end = Some(i);
|
||||
break;
|
||||
},
|
||||
b => {
|
||||
sup.push(b);
|
||||
i += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
if sup.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Some(end) = end {
|
||||
let mut tokens = vec![];
|
||||
if begin > 0 {
|
||||
let pre_part = String::from_utf8((&bytes[0..begin])
|
||||
.to_owned())
|
||||
.unwrap();
|
||||
tokens.push(Token::Str(escape(pre_part)));
|
||||
}
|
||||
let sup_part = String::from_utf8(sup).unwrap();
|
||||
match c {
|
||||
b'^' => tokens.push(Token::Superscript(vec![Token::Str(sup_part)])),
|
||||
b'~' => tokens.push(Token::Subscript(vec![Token::Str(sup_part)])),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
if end+1 < len {
|
||||
let post_part = String::from_utf8((&bytes[end + 1..]).to_owned()).unwrap();
|
||||
if let Some(mut v) = parse_super_sub(&post_part, c) {
|
||||
tokens.append(&mut v);
|
||||
} else {
|
||||
tokens.push(Token::Str(escape(post_part)));
|
||||
}
|
||||
}
|
||||
return Some(tokens);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
/// Replace consecutives Strs by a Str of both, collapse soft breaks to previous std and so on
|
||||
fn collapse(ast: &mut Vec<Token>) {
|
||||
let mut i = 0;
|
||||
while i < ast.len() {
|
||||
if ast[i].is_str() && i + 1 < ast.len() {
|
||||
if ast[i + 1].is_str() {
|
||||
// Two consecutives Str, concatenate them
|
||||
let token = ast.remove(i + 1);
|
||||
if let (&mut Token::Str(ref mut dest), Token::Str(ref source)) = (&mut ast[i],
|
||||
token) {
|
||||
// dest.push(' ');
|
||||
dest.push_str(source);
|
||||
continue;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else if ast[i + 1] == Token::SoftBreak {
|
||||
ast.remove(i + 1);
|
||||
if let &mut Token::Str(ref mut dest) = &mut ast[i] {
|
||||
dest.push(' ');
|
||||
continue;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If token is containing others, recurse into them
|
||||
if let Some(ref mut inner) = ast[i].inner_mut() {
|
||||
collapse(inner);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace images which are alone in a paragraph by standalone images
|
||||
fn find_standalone(ast: &mut Vec<Token>) {
|
||||
for token in ast {
|
||||
let res = if let &mut Token::Paragraph(ref mut inner) = token {
|
||||
if inner.len() == 1 {
|
||||
if inner[0].is_image() {
|
||||
if let Token::Image(source, title, inner) = mem::replace(&mut inner[0],
|
||||
Token::Rule) {
|
||||
Token::StandaloneImage(source, title, inner)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
// If paragraph only contains a link only containing an image, ok too
|
||||
// Fixme: messy code and unnecessary clone
|
||||
if let Token::Link(ref url, ref alt, ref mut inner) = inner[0] {
|
||||
if inner[0].is_image() {
|
||||
if let Token::Image(source, title, inner) = mem::replace(&mut inner[0],
|
||||
Token::Rule) {
|
||||
Token::Link(url.clone(), alt.clone(), vec![Token::StandaloneImage(source, title, inner)])
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
*token = res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_parse_super_str() {
|
||||
let c = b'^';
|
||||
assert!(parse_super_sub("String without superscript", c).is_none());
|
||||
assert!(parse_super_sub("String \\^without\\^ superscript", c).is_none());
|
||||
assert!(parse_super_sub("String ^without superscript", c).is_none());
|
||||
assert!(parse_super_sub("String ^without superscript^", c).is_none());
|
||||
assert_eq!(parse_super_sub("^up^", c),
|
||||
Some(vec!(Token::Superscript(vec!(Token::Str(String::from("up")))))));
|
||||
assert_eq!(parse_super_sub("foo^up^ bar", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("up".to_owned()))),
|
||||
Token::Str(" bar".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo^up^ bar^up^baz", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("up".to_owned()))),
|
||||
Token::Str(" bar".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("up".to_owned()))),
|
||||
Token::Str("baz".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo^up^ bar^baz", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("up".to_owned()))),
|
||||
Token::Str(" bar^baz".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo\\^bar^up^", c),
|
||||
Some(vec!(Token::Str("foo^bar".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("up".to_owned()))))));
|
||||
assert_eq!(parse_super_sub("foo^bar\\^up^", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("bar^up".to_owned()))))));
|
||||
assert_eq!(parse_super_sub("foo^bar up^", c),
|
||||
None);
|
||||
assert_eq!(parse_super_sub("foo^bar\\ up^", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Superscript(vec!(Token::Str("bar up".to_owned()))))));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_supb_str() {
|
||||
let c = b'~';
|
||||
assert!(parse_super_sub("String without subscript", c).is_none());
|
||||
assert!(parse_super_sub("String \\~without\\~ subscript", c).is_none());
|
||||
assert!(parse_super_sub("String ~without subscript", c).is_none());
|
||||
assert!(parse_super_sub("String ~without\nsubscript", c).is_none());
|
||||
assert!(parse_super_sub("String ~without subscript~", c).is_none());
|
||||
assert_eq!(parse_super_sub("~down~", c),
|
||||
Some(vec!(Token::Subscript(vec!(Token::Str(String::from("down")))))));
|
||||
assert_eq!(parse_super_sub("foo~down~ bar", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("down".to_owned()))),
|
||||
Token::Str(" bar".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo~down~ bar~down~baz", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("down".to_owned()))),
|
||||
Token::Str(" bar".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("down".to_owned()))),
|
||||
Token::Str("baz".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo~down~ bar~baz", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("down".to_owned()))),
|
||||
Token::Str(" bar~baz".to_owned()))));
|
||||
assert_eq!(parse_super_sub("foo\\~bar~down~", c),
|
||||
Some(vec!(Token::Str("foo~bar".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("down".to_owned()))))));
|
||||
assert_eq!(parse_super_sub("foo~bar\\~down~", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("bar~down".to_owned()))))));
|
||||
assert_eq!(parse_super_sub("foo~bar down~", c),
|
||||
None);
|
||||
assert_eq!(parse_super_sub("foo~bar\\ down~", c),
|
||||
Some(vec!(Token::Str("foo".to_owned()),
|
||||
Token::Subscript(vec!(Token::Str("bar down".to_owned()))))));
|
||||
|
||||
}
|
Loading…
Reference in New Issue