This commit is contained in:
Matthieu 2018-06-17 20:14:58 +02:00
commit a9bdf4d9a9
47 changed files with 1534 additions and 450 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ rls
/target
**/*.rs.bk
rls
translations
po/*.po~

304
Cargo.lock generated
View File

@ -263,11 +263,11 @@ dependencies = [
[[package]]
name = "cookie"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.11.0-dev"
source = "git+https://github.com/alexcrichton/cookie-rs?rev=0365a18#0365a18e4518e498ac6a508dab6b006add7f162e"
dependencies = [
"base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -297,15 +297,6 @@ dependencies = [
"build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.3.0"
@ -315,20 +306,6 @@ dependencies = [
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.4.1"
@ -420,14 +397,6 @@ name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "error-chain"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "error-chain"
version = "0.11.0"
@ -505,11 +474,6 @@ dependencies = [
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gcc"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.9.0"
@ -518,6 +482,23 @@ dependencies = [
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext-rs"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gettext-sys 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)",
"locale_config 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "glob"
version = "0.2.11"
@ -625,6 +606,11 @@ dependencies = [
"unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "indexmap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "iovec"
version = "0.1.2"
@ -699,6 +685,17 @@ dependencies = [
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "locale_config"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.3.9"
@ -745,14 +742,6 @@ name = "matches"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.0.1"
@ -920,21 +909,22 @@ dependencies = [
"vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ordermap"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pear"
version = "0.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.1.0"
source = "git+http://github.com/SergioBenitez/Pear?rev=54667ae#54667aefef084f2411e86392402a955770f1e7aa"
dependencies = [
"pear_codegen 0.1.0 (git+http://github.com/SergioBenitez/Pear?rev=54667ae)",
]
[[package]]
name = "pear_codegen"
version = "0.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.1.0"
source = "git+http://github.com/SergioBenitez/Pear?rev=54667ae#54667aefef084f2411e86392402a955770f1e7aa"
dependencies = [
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -944,11 +934,6 @@ name = "percent-encoding"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pest"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pest"
version = "1.0.6"
@ -1019,18 +1004,21 @@ dependencies = [
"dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext-rs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_codegen 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_contrib 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_codegen 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_contrib 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_i18n 0.1.1 (git+https://github.com/BaptisteGelez/rocket_i18n?rev=457b88c59ec31905a9193df43df58bee55b4b83d)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1063,6 +1051,14 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.3.15"
@ -1084,6 +1080,14 @@ dependencies = [
"proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "r2d2"
version = "0.8.2"
@ -1114,26 +1118,6 @@ dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.37"
@ -1209,62 +1193,92 @@ dependencies = [
[[package]]
name = "ring"
version = "0.11.0"
version = "0.13.0-alpha5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.4.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba#df7111143e466c18d1f56377a8d9530a5a306aba"
dependencies = [
"base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"isatty 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"pear 0.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"pear_codegen 0.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"state 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pear 0.1.0 (git+http://github.com/SergioBenitez/Pear?rev=54667ae)",
"rocket_codegen_next 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_http 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"state 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_codegen"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.4.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba#df7111143e466c18d1f56377a8d9530a5a306aba"
dependencies = [
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_http 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_codegen_next"
version = "0.4.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba#df7111143e466c18d1f56377a8d9530a5a306aba"
dependencies = [
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_contrib"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.4.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba#df7111143e466c18d1f56377a8d9530a5a306aba"
dependencies = [
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_http"
version = "0.4.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba#df7111143e466c18d1f56377a8d9530a5a306aba"
dependencies = [
"cookie 0.11.0-dev (git+https://github.com/alexcrichton/cookie-rs?rev=0365a18)",
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pear 0.1.0 (git+http://github.com/SergioBenitez/Pear?rev=54667ae)",
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_i18n"
version = "0.1.1"
source = "git+https://github.com/BaptisteGelez/rocket_i18n?rev=457b88c59ec31905a9193df43df58bee55b4b83d#457b88c59ec31905a9193df43df58bee55b4b83d"
dependencies = [
"gettext-rs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1400,12 +1414,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "smallvec"
version = "0.4.4"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "state"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1474,6 +1488,16 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.3 (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 = "synom"
version = "0.11.3"
@ -1525,15 +1549,16 @@ dependencies = [
[[package]]
name = "tera"
version = "0.10.10"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1837,7 +1862,7 @@ dependencies = [
[[package]]
name = "untrusted"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1962,13 +1987,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "053b26c8ce23b4c505a9479beace98f95899e0bf5c5255cf0219e9b0f48cf6ea"
"checksum cookie 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "477eb650753e319be2ae77ec368a58c638f9f0c4d941c39bad95e950fb1d1d0d"
"checksum cookie 0.11.0-dev (git+https://github.com/alexcrichton/cookie-rs?rev=0365a18)" = "<none>"
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c1bdc73742c36f7f35ebcda81dbb33a7e0d33757d03a06d9ddca762712ec5ea2"
"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
"checksum crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4e2817eb773f770dcb294127c011e22771899c21d18fce7dd739c0b9832e81"
"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
"checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
@ -1979,7 +2002,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
@ -1990,8 +2012,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum gettext-rs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b8c2412d5758f68a9eeba161f9ecb9a55f56bfdbf17857650b98f2b9b281a47"
"checksum gettext-sys 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)" = "62c644c0b8b73706fb8c7420533fd30abf6f41c2703994bc6f0826fceb7fb3d6"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
@ -2002,6 +2025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)" = "549dbb86397490ce69d908425b9beebc85bbaad25157d67479d4995bb56fdf9a"
"checksum hyper-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a5aa51f6ae9842239b0fac14af5f22123b8432b4cc774a44ff059fcba0f675ca"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08173ba1e906efb6538785a8844dd496f5d34f0a2d88038e95195172fc667220"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum isatty 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a118a53ba42790ef25c82bb481ecf36e2da892646cccd361e69a6bb881e19398"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
@ -2013,13 +2037,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef"
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
"checksum libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1a429b86418868c7ea91ee50e9170683f47fd9d94f5375438ec86ec3adb74e8e"
"checksum locale_config 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "14fbee0e39bc2dd6a2427c4fdea66e9826cc1fd09b0a0b7550359f5f6efe1dab"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
"checksum markup5ever 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfedc97d5a503e96816d10fedcd5b42f760b2e525ce2f7ec71f6a41780548475"
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
@ -2038,11 +2062,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum openssl 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)" = "63246f69962e8d5ef865f82a65241d6483c8a2905a1801e2f7feb5d187d51320"
"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
"checksum openssl-sys 0.9.28 (registry+https://github.com/rust-lang/crates.io-index)" = "0bbd90640b148b46305c1691eed6039b5c8509bed16991e3562a01eeb76902a3"
"checksum ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b81cf3b8cb96aa0e73bbedfcdc9708d09fec2854ba8d474be4e6f666d7379e8b"
"checksum pear 0.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b645aa07cf1010a67e9f67b4b9b96d6c5fb9315eee678a061d6ab58e9cb77f"
"checksum pear_codegen 0.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ca34109829349aeefe22772916da5404b3f5cd0e63a72c5d91209fc809342265"
"checksum pear 0.1.0 (git+http://github.com/SergioBenitez/Pear?rev=54667ae)" = "<none>"
"checksum pear_codegen 0.1.0 (git+http://github.com/SergioBenitez/Pear?rev=54667ae)" = "<none>"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e823a5967bb4cdc6d3e46f47baaf4ecfeae44413a642b74ad44e59e49c7f6"
"checksum pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0fce5d8b5cc33983fc74f78ad552b5522ab41442c4ca91606e4236eb4b5ceefc"
"checksum pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ab94faafeb93f4c5e3ce81ca0e5a779529a602ad5d09ae6d21996bfb8b6a52bf"
"checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2"
@ -2054,14 +2076,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
"checksum proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "49b6a521dc81b643e9a51e0d1cf05df46d5a2f3c0280ea72bcb68276ba64a118"
"checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
"checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46"
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
@ -2069,10 +2091,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "241faa9a8ca28a03cbbb9815a5d085f271d4c0168a19181f106aa93240c22ddb"
"checksum ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2a6dc7fc06a05e6de183c5b97058582e9da2de0c136eafe49609769c507724"
"checksum rocket 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c000cf7233aa997a19a43f77ddc80db11b58cdbbc12e2c1385bd62cbbace3964"
"checksum rocket_codegen 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "645dd494d1340a4c16ba8decc4bb94d3e687a7e6b57552e2341dbf436b75ffaa"
"checksum rocket_contrib 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2b1f97dc98bf6fa9a861e3c0c71f150f1110350eaaebe56516377d7f4316a51a"
"checksum ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)" = "3845516753f91b4511f9b17c917ea6fa4bc5a7853a9947b0f66731aff51cdef5"
"checksum rocket 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_codegen 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_codegen_next 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_contrib 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_http 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_i18n 0.1.1 (git+https://github.com/BaptisteGelez/rocket_i18n?rev=457b88c59ec31905a9193df43df58bee55b4b83d)" = "<none>"
"checksum rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
"checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade"
@ -2091,8 +2116,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d"
"checksum slug 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "797bcb4d24e91239a8615415814f4afb2d8ca400c472de3c73f803a5a7689e11"
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
"checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c"
"checksum state 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5562ac59585fe3d9a1ccf6b4e298ce773f5063db80d59f783776b410c1714c2"
"checksum smallvec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "312a7df010092e73d6bbaf141957e868d4f30efd2bfd9bb1028ad91abec58514"
"checksum state 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
"checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
"checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191"
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
@ -2100,13 +2125,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5"
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
"checksum syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c67da57e61ebc7b7b6fff56bb34440ca3a83db037320b0507af4c10368deda7d"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
"checksum syntex_fmt_macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5386bdc48758d136af85b3880548e1f3a9fad8d7dc2b38bdb48c36a9cdefc0"
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
"checksum tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d706c3bec8103f346fc7b8a3887a2ff4195cf704bdbc6307069f32ea8a2b3af5"
"checksum tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e815b67d44c26feb06630011fb58b5b243f4e9585aac1ed0592c5795de64cd75"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
@ -2141,7 +2167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
"checksum unidecode 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
"checksum untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70afa43c8c5d23a53a3c39ec9b56232c5badc19f6bb5ad529c1d6448a7241365"
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
"checksum utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1262dfab4c30d5cb7c07026be00ee343a6cf5027fdc0104a9160f354e5db75c"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"

View File

@ -4,6 +4,7 @@ name = "plume"
version = "0.1.0"
[dependencies]
activitypub = "0.1.1"
ammonia = "1.1.0"
array_tool = "1.0"
base64 = "0.9"
bcrypt = "0.2"
@ -11,19 +12,18 @@ comrak = "0.2"
dotenv = "*"
failure = "0.1"
failure_derive = "0.1"
gettext-rs = "0.4"
heck = "0.3.0"
hex = "0.3"
hyper = "*"
lazy_static = "*"
openssl = "0.10.6"
reqwest = "0.8"
rocket = "*"
rocket_codegen = "*"
serde = "*"
serde_derive = "1.0"
serde_json = "1.0"
tera = "0.11"
url = "1.7"
ammonia = "1.1.0"
[dependencies.chrono]
features = ["serde"]
@ -33,6 +33,19 @@ version = "0.4"
features = ["postgres", "r2d2", "chrono"]
version = "*"
[dependencies.rocket]
git = "https://github.com/SergioBenitez/Rocket"
rev = "df7111143e466c18d1f56377a8d9530a5a306aba"
[dependencies.rocket_codegen]
git = "https://github.com/SergioBenitez/Rocket"
rev = "df7111143e466c18d1f56377a8d9530a5a306aba"
[dependencies.rocket_contrib]
features = ["tera_templates", "json"]
version = "*"
git = "https://github.com/SergioBenitez/Rocket"
rev = "df7111143e466c18d1f56377a8d9530a5a306aba"
[dependencies.rocket_i18n]
git = "https://github.com/BaptisteGelez/rocket_i18n"
rev = "457b88c59ec31905a9193df43df58bee55b4b83d"

161
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,161 @@
# Development Guide
## Running Plume locally
### Mac OSX
All commands are run in the Mac Terminal or terminal emulator of your choice, such as iTerm2. First, you will need [Git](https://git-scm.com/download/mac), [Homebrew](https://brew.sh/), [Rust](https://www.rust-lang.org/en-US/), and [Postgres](https://www.postgresql.org/). Follow the instructions to install Homebrew before continuing if you don't already have it.
#### Download the Repository
Navigate to the directory on your machine where you would like to install the repository, such as in `~/dev` by running `cd dev`. Now, clone the remote repository by running `git clone https://github.com/Plume-org/Plume.git`. This will install the codebase to the `Plume` subdirectory. Navigate into that directory by running `cd Plume`.
#### Rust
If you think you might already have rust on your machine, you can check by running
```
rustc --version
# Should output something like
# rustc 1.28.0-nightly (a805a2a5e 2018-06-10)
```
If you don't already have Rust, install it by running
```
curl https://sh.rustup.rs -sSf | sh
```
In the interactive installation, choose the option of the nightly toolchain. Restart your console so that the `rustc` CLI tool is available.
#### Postgres
Now we will use Homebrew to install Postgres. If you think you might already have it, try running `brew info postgres`. If it is not available, continue to install Postgres by running the following:
```
brew install postgres
```
Now, you can use the following command to start Postgres on a one-time basis.
```
pg_ctl -D /usr/local/var/postgres start
```
After starting Postgres, we need to enter [PSQL](http://postgresguide.com/utilities/psql.html), the interactive terminal for running postgres queries. We'll be running this as the user `postgres` which is an admin-type postgres user.
```
psql postgres
```
Now that you are in psql, enter the following queries to prepare the database for Plume.
```
CREATE DATABASE plume;
CREATE USER plume WITH PASSWORD 'plume';
GRANT ALL PRIVILEGES ON DATABASE plume to plume;
\q
```
The final command `\q` lets us exit psql and returns us to the Terminal. Now, we will open psql again, this time as the `plume` user we just created. Then we'll give all privileges on all tables and sequences to our `plume` user. This is for local development use only and it's not recommend to give complete access to this user in a production environment.
```
psql plume
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO plume;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO plume;
\q
```
#### Database Migration
Now that the Postgres database is set up and the `plume` user has the privileges it needs, we can set up the database using the diesel CLI. If this was your time installing Rust, you
will probably need to run that using `cargo`. `cargo` is installed with `rustc` so if you followed the earlier instructions it will already be available.
```
cargo install diesel_cli
```
The first time you run this, you can run setup. After that, every time you pull the repository you will want to run the migration command in case there were any migrations. Those commands are
```
diesel setup --database-url='postgres://localhost/plume'
diesel migration run --database-url='postgres://localhost/plume'
```
#### Running Plume
To run Plume locally, make sure you are once again in the Plume directory, such as `~/dev/Plume`. Now you will be able to run the application using the command
```
cargo run
```
#### Configuration
Now Plume should be running on your machine at [http://localhost:8000](http://localhost:8000). The first time you run the application, you'll want to configure your blog name on the [http://localhost:8000/configure](http://localhost:8000/configure) page. You'll be able to change this name later.
#### Testing the federation
To test the federation, you'll need to setup another database (see "Setup the database"),
also owned by the "plume" user, but with a different name. Then, you'll need to run the
migrations for this database too.
```
diesel migration run --database-url postgres://plume:plume@localhost/my_other_plume_db
```
To run this other instance, you'll need to give two environment variables:
- `ROCKET_PORT`, the port on which your app will run
- `DB_NAME`, the name of the database you just created
```
ROCKET_PORT=3033 DB_NAME=my_other_plume_db cargo run
```
#### Making a Pull Request
To create an upstream fork of the repository in GitHub, click "Fork" in the top right button on the main page of the [Plume repository](https://github.com/Plume-org/Plume). Now, in the command line, set another remote for the repository by running the following command, replacing `myname` with the name under which you forked the repo. You can use another name besides `upstream` if you prefer. Using [SSH](https://help.github.com/articles/connecting-to-github-with-ssh/) is recommended.
```
git remote add upstream git@github.com/myname/Plume.git
# Alt # git remote add upstream https://github.com/myname/Plume.git
```
Now, make any changes to the code you want. After committing your changes, push to the upstream fork. Once your changes are made, visit the GitHub page for your fork and select "New pull request". Add descriptive text, any issue numbers using hashtags to reference the issue number, screenshots of your changes if relevant, a description of how you tested your changes, and any other information that will help the project maintainers be able to quickly accept your pull requests.
The project maintainers may suggest further changes to improve the pull request even more. After implementing this locally, you can push to your upstream fork again and the changes will immediately show up in the pull request after pushing. Once all the suggested changes are made, the pull request may be accepted. Thanks for contributing.
#### When working with Tera templates
When working with the interface, or any message that will be displayed to the final user, keep in mind that Plume is an internationalized software. To make sure that the parts of the interface you are changing are translatable, you should:
- Use the `_` and `_n` filters instead of directly writing strings in your HTML markup
- Add the strings to translate to the `po/plume.pot` file
Here is an example: let's say we want to add two strings, a simple one and one that may deal with plurals. The first step is to add them to whatever template we want to display them in:
```jinja
<p>{{ "Hello, world!" | _ }}</p>
<p>{{ "You have {{ count }} new notifications" | _n(singular="You have one new notification", count=n_notifications) }}</p>
```
As you can see, the `_` doesn't need any special argument to work, but `_n` requires `singular` (the singular form, in English) and `count` (the number of items, to determine which form to use) to be present. Note that any parameters given to these filters can be used as regular Tera variables inside of the translated strings, like we are doing with the `count` variable in the second string above.
The second step is to add them to POT file. To add a simple message, just do:
```po
msgid "Hello, world" # The string you used with your filter
msgstr "" # Always empty
```
For plural forms, the syntax is a bit different:
```po
msgid "You have one new notification" # The singular form
msgid_plural "You have {{ count }} new notifications" # The plural one
msgstr[0] ""
msgstr[1] ""
```
And that's it! Once these new messages will have been translated, they will correctly be displayed in the requested locale!

35
INTERNATIONALIZATION.md Normal file
View File

@ -0,0 +1,35 @@
# Making Plume available in your language
*You will need to have basic git and GitHub knownledge to follow this guide. But we plan to setup a more user-friendly translation tool in the future.*
To translate Plume in your language, you'll first need to make sure it is listed in the `po/LINGUAS` file. If it is not, you can ask anybody with a development environment to add it (or do it yourself if you have a development environment). Once it will be here, Plume must be launched once to generate all the needed files.
Then you can start translating. Find the file corresponding to your locale, which is `po/YOUR_LOCALE.po`, and open it. Inside, you have a list of strings to translate. There are two kind of translatable strings.
## Simple strings
They look like this:
```po
msgid "Hello, world"
msgstr ""
```
What is next to `msgid` is the string in English. To translate it, just fill the `msgstr` field with the translation.
## Strings with plural forms
Sometimes, strings may change depending on a number (for instance, a post counter). In the `.po` files, these strings look like this:
```
msgid "One post"
msgid_plural "{{ count }} posts"
msgstr[0] ""
msgstr[1] ""
```
Then you should fill the two `msgstr` field, one with the singular form, the second with the plural one. If your language as more than two forms, you can add another one by following the same pattern (`msgstr[n] ""`).
## Interpolation
Strings you translate may contain data from Plume (a username for instance). To tell Plume where to put these data, surround their identifier by `{{` and `}}`. The identifier is also present in this form in the English string to translate (this what you can see above, with the `{{ count }} posts` message).

View File

@ -1,65 +1,13 @@
# Plume [![Build Status](https://travis-ci.org/Plume-org/Plume.svg?branch=master)](https://travis-ci.org/Plume-org/Plume)
Federated blogging engine, based on ActivityPub.
Federated blogging engine, based on ActivityPub. It uses the Rocket framework, and Diesel to interact with the database.
[**Demo instance**](https://baptiste.gelez.xyz/)
## Setup the database
It is not yet ready for production use, but we have all the basic features (account management, blogs, articles, comments, etc) and a basic federation.
You'll need Postgres.
Feel free to join our Matrix room: `#plume:disroot.org` to discuss about the project!
```
sudo su postgres
If you are interested in coding, check out the [development guide](https://github.com/Plume-org/Plume/blob/master/DEVELOPMENT.md). You can also help by reporting issues, suggesting features, or writing documentation for instance.
psql
CREATE DATABASE plume;
CREATE USER plume WITH PASSWORD 'plume';
GRANT ALL PRIVILEGES ON DATABASE plume to plume;
\q
exit
```
Then run the migrations
```
diesel migrations run # Install diesel with `cargo install diesel_cli` if needed
```
You should repeat this operation every time the database schema has been modified.
A good practice is to run it after every `git pull`.
## Starting the app
Just use:
```
cargo run
```
You'll need Rust nightly.
Once the app started, try to visit [localhost:8000](http://localhost:8000).
To configure the instance (needed before you can do anything else),
go on [/configure](http://localhost:8000/configure).
## Testing the federation
To test the federation, you'll need to setup another database (see "Setup the database"),
also owned by the "plume" user, but with a different name. Then, you'll need to run the
migrations for this database too.
```
diesel migration run --database-url postgres://plume:plume@localhost/my_other_plume_db
```
To run this other instance, you'll need to give two environment variables:
- `ROCKET_PORT`, the port on which your app will run
- `DB_NAME`, the name of the database you just created
```
ROCKET_PORT=3033 DB_NAME=my_other_plume_db cargo run
```
If you would like to help by translating Plume, we also have [a guide for you](https://github.com/Plume-org/Plume/blob/master/INTERNATIONALIZATION.md)!

2
po/LINGUAS Normal file
View File

@ -0,0 +1,2 @@
en
fr

255
po/en.po Normal file
View File

@ -0,0 +1,255 @@
msgid ""
msgstr ""
"Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2018-06-15 16:33-0700\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Latest articles"
msgstr ""
msgid "No posts to see here yet."
msgstr ""
msgid "New article"
msgstr ""
msgid "New blog"
msgstr ""
msgid "Create a blog"
msgstr ""
msgid "Title"
msgstr ""
msgid "Create blog"
msgstr ""
msgid "Comment \"{{ post }}\""
msgstr ""
msgid "Content"
msgstr ""
msgid "Submit comment"
msgstr ""
msgid "Something broke on our side."
msgstr ""
msgid "Sorry about that. If you think this is a bug, please report it."
msgstr ""
msgid "Configuration"
msgstr ""
msgid "Configure your instance"
msgstr ""
msgid "Name"
msgstr ""
msgid "Let's go!"
msgstr ""
msgid "Welcome on {{ instance_name }}"
msgstr "Welcome on {{ instance_name }}"
msgid "Notifications"
msgstr ""
msgid "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name }}{{ link_3 }}"
msgstr ""
msgid "This article is under the {{ license }} license."
msgstr ""
#, fuzzy
msgid "One like"
msgid_plural "{{ count }} likes"
msgstr[0] "One follower"
msgstr[1] "{{ count }} followers"
msgid "I don't like this anymore"
msgstr ""
msgid "Add yours"
msgstr ""
msgid "One reshare"
msgid_plural "{{ count }} reshares"
msgstr[0] ""
msgstr[1] ""
msgid "I don't want to reshare this anymore"
msgstr ""
msgid "Reshare"
msgstr ""
msgid "Comments"
msgstr ""
msgid "Respond"
msgstr ""
msgid "Comment"
msgstr ""
msgid "New post"
msgstr ""
msgid "Create a post"
msgstr ""
msgid "Publish"
msgstr ""
msgid "Login"
msgstr ""
msgid "Username or email"
msgstr ""
msgid "Password"
msgstr ""
msgid "Dashboard"
msgstr ""
msgid "Your Dashboard"
msgstr ""
msgid "Your Blogs"
msgstr ""
msgid "You don't have any blog yet. Create your own, or ask to join one."
msgstr ""
msgid "Start a new blog"
msgstr ""
msgid "Admin"
msgstr ""
msgid "It is you"
msgstr ""
msgid "Edit your profile"
msgstr ""
#, fuzzy
msgid "Open on {{ instance_url }}"
msgstr "Welcome on {{ instance_name }}"
msgid "Follow"
msgstr ""
#, fuzzy
msgid "Unfollow"
msgstr "One follower"
msgid "Recently reshared"
msgstr ""
msgid "One follower"
msgid_plural "{{ count }} followers"
msgstr[0] "One follower"
msgstr[1] "{{ count }} followers"
msgid "Edit your account"
msgstr ""
msgid "Your Profile"
msgstr ""
msgid "Display Name"
msgstr ""
msgid "Email"
msgstr ""
msgid "Summary"
msgstr ""
msgid "Update account"
msgstr ""
#, fuzzy
msgid "{{ name }}'s followers"
msgstr "One follower"
#, fuzzy
msgid "Followers"
msgstr "One follower"
msgid "New Account"
msgstr ""
msgid "Create an account"
msgstr ""
msgid "Username"
msgstr ""
msgid "Password confirmation"
msgstr ""
msgid "Create account"
msgstr ""
msgid "Plume"
msgstr ""
msgid "Menu"
msgstr ""
msgid "My account"
msgstr ""
msgid "Log Out"
msgstr ""
msgid "Log In"
msgstr ""
msgid "Register"
msgstr ""
msgid "You need to be logged in order to create a new blog"
msgstr ""
msgid "You need to be logged in order to post a comment"
msgstr ""
msgid "You need to be logged in order to like a post"
msgstr ""
msgid "You need to be logged in order to see your notifications"
msgstr ""
msgid "You need to be logged in order to write a new post"
msgstr ""
msgid "You need to be logged in order to reshare a post"
msgstr ""
msgid "Invalid username or password"
msgstr ""
msgid "You need to be logged in order to access your dashboard"
msgstr ""
msgid "You need to be logged in order to follow someone"
msgstr ""
msgid "You need to be logged in order to edit your profile"
msgstr ""

254
po/fr.po Normal file
View File

@ -0,0 +1,254 @@
msgid ""
msgstr ""
"Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2018-06-15 16:33-0700\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "Latest articles"
msgstr "Derniers articles"
msgid "No posts to see here yet."
msgstr "Aucun article pour le moment"
msgid "New article"
msgstr "Derniers articles"
msgid "New blog"
msgstr "Nouveau blog"
msgid "Create a blog"
msgstr "Créer un blog"
msgid "Title"
msgstr "Titre"
msgid "Create blog"
msgstr "Créer le blog"
msgid "Comment \"{{ post }}\""
msgstr "Commenter « {{ post }} »"
msgid "Content"
msgstr "Contenu"
msgid "Submit comment"
msgstr "Envoyer le commentaire"
msgid "Something broke on our side."
msgstr "Nous avons cassé quelque chose"
msgid "Sorry about that. If you think this is a bug, please report it."
msgstr ""
"Nous sommes désolé⋅e⋅s. Si vous pensez que c'est un bogue, merci de le "
"rapporter."
msgid "Configuration"
msgstr "Configuration"
msgid "Configure your instance"
msgstr "Configurez votre instance"
msgid "Name"
msgstr "Nom"
msgid "Let's go!"
msgstr "C'est parti !"
msgid "Welcome on {{ instance_name }}"
msgstr "Bienvenue sur {{ instance_name }}"
msgid "Notifications"
msgstr "Notifications"
msgid "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name }}{{ link_3 }}"
msgstr "Écrit par {{ link_1 }}{{ url }}{{ link_2 }}{{ name }}{{ link_3 }}"
msgid "This article is under the {{ license }} license."
msgstr "Cet article est placé sous la licence {{ license }}"
msgid "One like"
msgid_plural "{{ count }} likes"
msgstr[0] "{{ count }} personne aime cet article"
msgstr[1] "{{ count }} personnes aiment cet article"
msgid "I don't like this anymore"
msgstr "Je n'aime plus"
msgid "Add yours"
msgstr "J'aime"
msgid "One reshare"
msgid_plural "{{ count }} reshares"
msgstr[0] "{{ count }} repartage"
msgstr[1] "{{ count }} repartages"
msgid "I don't want to reshare this anymore"
msgstr "Je ne veux plus repartager"
msgid "Reshare"
msgstr "Repartagez"
msgid "Comments"
msgstr "Commentaires"
msgid "Respond"
msgstr "Répondre"
msgid "Comment"
msgstr "Commentez"
msgid "New post"
msgstr "Nouvel article"
msgid "Create a post"
msgstr "Créer un article"
msgid "Publish"
msgstr "Publier"
msgid "Login"
msgstr "Se connecter"
msgid "Username or email"
msgstr "Nom d'utilisateur ou email"
msgid "Password"
msgstr "Mot de passe"
msgid "Dashboard"
msgstr "Tableau de bord"
msgid "Your Dashboard"
msgstr "Votre tableau de bord"
msgid "Your Blogs"
msgstr "Vos blogs"
msgid "You don't have any blog yet. Create your own, or ask to join one."
msgstr ""
"Vous n'avez pas encore de blog. Créez le votre, ou demandez à en rejoindre "
"un."
msgid "Start a new blog"
msgstr "Commencez un nouveau blog"
msgid "Admin"
msgstr "Administrateur"
msgid "It is you"
msgstr "C'est vous"
msgid "Edit your profile"
msgstr "Éditez votre profil"
msgid "Open on {{ instance_url }}"
msgstr "Bienvenue sur {{ instance_name }}"
msgid "Follow"
msgstr "S'abonner"
msgid "Unfollow"
msgstr "Se désabonner"
msgid "Recently reshared"
msgstr "Récemment repartagé"
msgid "One follower"
msgid_plural "{{ count }} followers"
msgstr[0] "{{ count }} abonné⋅e"
msgstr[1] "{{ count }} abonné⋅e⋅s"
msgid "Edit your account"
msgstr "Éditer votre compte"
msgid "Your Profile"
msgstr "Votre profil"
msgid "Display Name"
msgstr "Nom affiché"
msgid "Email"
msgstr "Email"
msgid "Summary"
msgstr "Description"
msgid "Update account"
msgstr "Mettre à jour mes informations"
msgid "{{ name }}'s followers"
msgstr "{{ count }} abonné⋅e"
msgid "Followers"
msgstr "{{ count }} abonné⋅e"
msgid "New Account"
msgstr "Nouveau compte"
msgid "Create an account"
msgstr "Créer un compte"
msgid "Username"
msgstr "Nom d'utilisateur"
msgid "Password confirmation"
msgstr "Confirmation du mot de passe"
msgid "Create account"
msgstr "Créer mon compte"
msgid "Plume"
msgstr "Plume"
msgid "Menu"
msgstr "Menu"
msgid "My account"
msgstr "Mon compte"
msgid "Log Out"
msgstr "Se déconnecter"
msgid "Log In"
msgstr "Se connecter"
msgid "Register"
msgstr "S'inscrire"
msgid "You need to be logged in order to create a new blog"
msgstr "Vous devez vous connecter pour créer un nouveau blog"
msgid "You need to be logged in order to post a comment"
msgstr "Vous devez vous connecter pour commenter"
msgid "You need to be logged in order to like a post"
msgstr "Vous devez vous connecter pour aimer un article"
msgid "You need to be logged in order to see your notifications"
msgstr "Vous devez vous connecter pour voir vos notifications"
msgid "You need to be logged in order to write a new post"
msgstr "Vous devez vous connecter pour écrire un article"
msgid "You need to be logged in order to reshare a post"
msgstr "Vous devez vous connecter pour repartager un article"
msgid "Invalid username or password"
msgstr "Nom d'utilisateur ou mot de passe invalide"
msgid "You need to be logged in order to access your dashboard"
msgstr "Vous devez vous connecter pour accéder à votre tableau de bord"
msgid "You need to be logged in order to follow someone"
msgstr "Vous devez vous connecter pour suivre quelqu'un"
msgid "You need to be logged in order to edit your profile"
msgstr "Vous devez vous connecter pour modifier votre profil"

251
po/plume.pot Normal file
View File

@ -0,0 +1,251 @@
msgid ""
msgstr ""
"Project-Id-Version: plume\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid "Latest articles"
msgstr ""
msgid "No posts to see here yet."
msgstr ""
msgid "New article"
msgstr ""
msgid "New blog"
msgstr ""
msgid "Create a blog"
msgstr ""
msgid "Title"
msgstr ""
msgid "Create blog"
msgstr ""
msgid "Comment \"{{ post }}\""
msgstr ""
msgid "Content"
msgstr ""
msgid "Submit comment"
msgstr ""
msgid "Something broke on our side."
msgstr ""
msgid "Sorry about that. If you think this is a bug, please report it."
msgstr ""
msgid "Configuration"
msgstr ""
msgid "Configure your instance"
msgstr ""
msgid "Name"
msgstr ""
msgid "Let's go!"
msgstr ""
msgid "Welcome on {{ instance_name }}"
msgstr ""
msgid "Notifications"
msgstr ""
msgid "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name }}{{ link_3 }}"
msgstr ""
msgid "This article is under the {{ license }} license."
msgstr ""
msgid "One like"
msgid_plural "{{ count }} likes"
msgstr[0] ""
msgstr[1] ""
msgid "I don't like this anymore"
msgstr ""
msgid "Add yours"
msgstr ""
msgid "One reshare"
msgid_plural "{{ count }} reshares"
msgstr[0] ""
msgstr[1] ""
msgid "I don't want to reshare this anymore"
msgstr ""
msgid "Reshare"
msgstr ""
msgid "Comments"
msgstr ""
msgid "Respond"
msgstr ""
msgid "Comment"
msgstr ""
msgid "New post"
msgstr ""
msgid "Create a post"
msgstr ""
msgid "Publish"
msgstr ""
msgid "Login"
msgstr ""
msgid "Username or email"
msgstr ""
msgid "Password"
msgstr ""
msgid "Dashboard"
msgstr ""
msgid "Your Dashboard"
msgstr ""
msgid "Your Blogs"
msgstr ""
msgid "You don't have any blog yet. Create your own, or ask to join one."
msgstr ""
msgid "Start a new blog"
msgstr ""
msgid "Admin"
msgstr ""
msgid "It is you"
msgstr ""
msgid "Edit your profile"
msgstr ""
msgid "Open on {{ instance_url }}"
msgstr ""
msgid "Follow"
msgstr ""
msgid "Unfollow"
msgstr ""
msgid "Recently reshared"
msgstr ""
msgid "One follower"
msgid_plural "{{ count }} followers"
msgstr[0] ""
msgstr[1] ""
msgid "Edit your account"
msgstr ""
msgid "Your Profile"
msgstr ""
msgid "Display Name"
msgstr ""
msgid "Email"
msgstr ""
msgid "Summary"
msgstr ""
msgid "Update account"
msgstr ""
msgid "{{ name }}'s followers"
msgstr ""
msgid "Followers"
msgstr ""
msgid "New Account"
msgstr ""
msgid "Create an account"
msgstr ""
msgid "Username"
msgstr ""
msgid "Password confirmation"
msgstr ""
msgid "Create account"
msgstr ""
msgid "Plume"
msgstr ""
msgid "Menu"
msgstr ""
msgid "My account"
msgstr ""
msgid "Log Out"
msgstr ""
msgid "Log In"
msgstr ""
msgid "Register"
msgstr ""
msgid "You need to be logged in order to create a new blog"
msgstr ""
msgid "You need to be logged in order to post a comment"
msgstr ""
msgid "You need to be logged in order to like a post"
msgstr ""
msgid "You need to be logged in order to see your notifications"
msgstr ""
msgid "You need to be logged in order to write a new post"
msgstr ""
msgid "You need to be logged in order to reshare a post"
msgstr ""
msgid "Invalid username or password"
msgstr ""
msgid "You need to be logged in order to access your dashboard"
msgstr ""
msgid "You need to be logged in order to follow someone"
msgstr ""
msgid "You need to be logged in order to edit your profile"
msgstr ""

View File

@ -1 +1 @@
nightly-2018-04-18
nightly-2018-05-31

View File

@ -1,27 +1,21 @@
use activitypub::{
Actor,
activity::{Accept, Announce, Create, Follow, Like, Undo},
object::{Article, Note}
Activity, Object,
activity::{Create, Like, Undo}
};
use diesel::PgConnection;
use failure::Error;
use serde_json;
use activity_pub::{
broadcast, Id, IntoId,
actor::Actor as APActor,
sign::*
Id
};
use models::{
blogs::Blog,
comments::*,
follows,
follows::Follow,
likes,
posts::*,
reshares::*,
users::User
reshares::*
};
use safe_string::SafeString;
#[derive(Fail, Debug)]
enum InboxError {
@ -33,98 +27,69 @@ enum InboxError {
CantUndo
}
pub trait FromActivity<T: Object>: Sized {
fn from_activity(conn: &PgConnection, obj: T, actor: Id) -> Self;
fn try_from_activity(conn: &PgConnection, act: Create) -> bool {
if let Ok(obj) = act.create_props.object_object() {
Self::from_activity(conn, obj, act.create_props.actor_link::<Id>().unwrap());
true
} else {
false
}
}
}
pub trait Notify<T: Activity> {
fn notify(conn: &PgConnection, act: T, actor: Id);
}
pub trait Deletable {
/// true if success
fn delete_activity(conn: &PgConnection, id: Id) -> bool;
}
pub trait Inbox {
fn received(&self, conn: &PgConnection, act: serde_json::Value);
fn new_article(&self, conn: &PgConnection, article: Article) -> Result<(), Error> {
Post::insert(conn, NewPost {
blog_id: 0, // TODO
slug: String::from(""), // TODO
title: article.object_props.name_string().unwrap(),
content: SafeString::new(&article.object_props.content_string().unwrap()),
published: true,
license: String::from("CC-0"),
ap_url: article.object_props.url_string()?
});
Ok(())
}
fn new_comment(&self, conn: &PgConnection, note: Note, actor_id: String) -> Result<(), Error> {
let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
Comment::insert(conn, NewComment {
content: SafeString::new(&note.object_props.content_string().unwrap()),
spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")),
ap_url: note.object_props.id_string().ok(),
in_response_to_id: previous_comment.clone().map(|c| c.id),
post_id: previous_comment
.map(|c| c.post_id)
.unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id),
author_id: User::from_url(conn, actor_id).unwrap().id,
sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitystreams crate
});
Ok(())
}
fn follow(&self, conn: &PgConnection, follow: Follow) -> Result<(), Error> {
let from = User::from_url(conn, follow.follow_props.actor.as_str().unwrap().to_string()).unwrap();
match User::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()) {
Some(u) => self.accept_follow(conn, &from, &u, follow, from.id, u.id),
None => {
let blog = Blog::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()).unwrap();
self.accept_follow(conn, &from, &blog, follow, from.id, blog.id)
}
};
Ok(())
}
fn like(&self, conn: &PgConnection, like: Like) -> Result<(), Error> {
let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string());
likes::Like::insert(conn, likes::NewLike {
post_id: post.unwrap().id,
user_id: liker.unwrap().id,
ap_url: like.object_props.id_string()?
});
Ok(())
}
fn unlike(&self, conn: &PgConnection, undo: Undo) -> Result<(), Error> {
let like = likes::Like::find_by_ap_url(conn, undo.undo_props.object_object::<Like>()?.object_props.id_string()?).unwrap();
like.delete(conn);
Ok(())
}
fn announce(&self, conn: &PgConnection, announce: Announce) -> Result<(), Error> {
let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string());
Reshare::insert(conn, NewReshare {
post_id: post.unwrap().id,
user_id: user.unwrap().id,
ap_url: announce.object_props.id_string()?
});
Ok(())
}
fn save(&self, conn: &PgConnection, act: serde_json::Value) -> Result<(), Error> {
let actor_id = Id::new(act["actor"].as_str().unwrap());
match act["type"].as_str() {
Some(t) => {
match t {
"Announce" => self.announce(conn, serde_json::from_value(act.clone())?),
"Announce" => {
Reshare::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
Ok(())
},
"Create" => {
let act: Create = serde_json::from_value(act.clone())?;
match act.create_props.object["type"].as_str().unwrap() {
"Article" => self.new_article(conn, act.create_props.object_object()?),
"Note" => self.new_comment(conn, act.create_props.object_object()?, act.create_props.actor_link::<Id>()?.0),
_ => Err(InboxError::InvalidType)?
if Post::try_from_activity(conn, act.clone()) || Comment::try_from_activity(conn, act) {
Ok(())
} else {
Err(InboxError::InvalidType)?
}
},
"Follow" => self.follow(conn, serde_json::from_value(act.clone())?),
"Like" => self.like(conn, serde_json::from_value(act.clone())?),
"Follow" => {
Follow::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
Ok(())
},
"Like" => {
likes::Like::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
Ok(())
},
"Undo" => {
let act: Undo = serde_json::from_value(act.clone())?;
match act.undo_props.object["type"].as_str().unwrap() {
"Like" => self.unlike(conn, act),
"Like" => {
likes::Like::delete_activity(conn, Id::new(act.undo_props.object_object::<Like>()?.object_props.id_string()?));
Ok(())
},
_ => Err(InboxError::CantUndo)?
}
}
@ -134,26 +99,6 @@ pub trait Inbox {
None => Err(InboxError::NoType)?
}
}
fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>(
&self,
conn: &PgConnection,
from: &A,
target: &B,
follow: Follow,
from_id: i32,
target_id: i32
) {
follows::Follow::insert(conn, follows::NewFollow {
follower_id: from_id,
following_id: target_id
});
let mut accept = Accept::default();
accept.accept_props.set_actor_link::<Id>(from.clone().into_id()).unwrap();
accept.accept_props.set_object_object(follow).unwrap();
broadcast(conn, &*from, accept, vec![target.clone()]);
}
}
pub trait WithInbox {

View File

@ -19,7 +19,7 @@ pub mod request;
pub mod sign;
pub mod webfinger;
pub type ActivityPub = Content<Json>;
pub type ActivityPub = Content<Json<serde_json::Value>>;
pub const CONTEXT_URL: &'static str = "https://www.w3.org/ns/activitystreams";
pub const PUBLIC_VISIBILTY: &'static str = "https://www.w3.org/ns/activitystreams#Public";
@ -111,6 +111,12 @@ impl Id {
}
}
impl Into<String> for Id {
fn into(self) -> String {
self.0.clone()
}
}
pub trait IntoId {
fn into_id(self) -> Id;
}

View File

@ -1,7 +1,8 @@
#![feature(plugin, custom_derive, iterator_find_map)]
#![feature(plugin, custom_derive, decl_macro, iterator_find_map)]
#![plugin(rocket_codegen)]
extern crate activitypub;
extern crate ammonia;
extern crate array_tool;
extern crate base64;
extern crate bcrypt;
@ -10,6 +11,7 @@ extern crate comrak;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate gettextrs;
extern crate heck;
extern crate hex;
#[macro_use]
@ -23,13 +25,14 @@ extern crate openssl;
extern crate reqwest;
extern crate rocket;
extern crate rocket_contrib;
extern crate rocket_i18n;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate tera;
extern crate url;
extern crate ammonia;
use diesel::{pg::PgConnection, r2d2::{ConnectionManager, Pool}};
use dotenv::dotenv;
@ -126,6 +129,9 @@ fn main() {
routes::well_known::webfinger
])
.manage(init_pool())
.attach(Template::fairing())
.attach(Template::custom(|engines| {
rocket_i18n::tera(&mut engines.tera);
}))
.attach(rocket_i18n::I18n::new("plume"))
.launch();
}

View File

@ -7,8 +7,9 @@ use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, dsl::
use serde_json;
use activity_pub::{
ap_url, IntoId, PUBLIC_VISIBILTY,
ap_url, Id, IntoId, PUBLIC_VISIBILTY,
actor::Actor,
inbox::FromActivity,
object::Object
};
use models::{
@ -123,6 +124,24 @@ impl Comment {
}
}
impl FromActivity<Note> for Comment {
fn from_activity(conn: &PgConnection, note: Note, actor: Id) -> Comment {
let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string();
let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone());
Comment::insert(conn, NewComment {
content: SafeString::new(&note.object_props.content_string().unwrap()),
spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")),
ap_url: note.object_props.id_string().ok(),
in_response_to_id: previous_comment.clone().map(|c| c.id),
post_id: previous_comment
.map(|c| c.post_id)
.unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id),
author_id: User::from_url(conn, actor.into()).unwrap().id,
sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
})
}
}
impl Object for Comment {
fn serialize(&self, conn: &PgConnection) -> serde_json::Value {
let mut to = self.get_author(conn).get_followers(conn).into_iter().map(|f| f.ap_url).collect::<Vec<String>>();

View File

@ -1,5 +1,8 @@
use activitypub::{Actor, activity::{Accept, Follow as FollowAct}};
use diesel::{self, PgConnection, ExpressionMethods, QueryDsl, RunQueryDsl};
use activity_pub::{broadcast, Id, IntoId, actor::Actor as ApActor, inbox::{FromActivity, WithInbox}, sign::Signer};
use models::blogs::Blog;
use models::users::User;
use schema::follows;
@ -33,4 +36,37 @@ impl Follow {
.expect("Unable to load follow by id")
.into_iter().nth(0)
}
pub fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>(
conn: &PgConnection,
from: &A,
target: &B,
follow: FollowAct,
from_id: i32,
target_id: i32
) -> Follow {
let res = Follow::insert(conn, NewFollow {
follower_id: from_id,
following_id: target_id
});
let mut accept = Accept::default();
accept.accept_props.set_actor_link::<Id>(from.clone().into_id()).unwrap();
accept.accept_props.set_object_object(follow).unwrap();
broadcast(conn, &*from, accept, vec![target.clone()]);
res
}
}
impl FromActivity<FollowAct> for Follow {
fn from_activity(conn: &PgConnection, follow: FollowAct, _actor: Id) -> Follow {
let from = User::from_url(conn, follow.follow_props.actor.as_str().unwrap().to_string()).unwrap();
match User::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()) {
Some(u) => Follow::accept_follow(conn, &from, &u, follow, from.id, u.id),
None => {
let blog = Blog::from_url(conn, follow.follow_props.object.as_str().unwrap().to_string()).unwrap();
Follow::accept_follow(conn, &from, &blog, follow, from.id, blog.id)
}
}
}
}

View File

@ -4,8 +4,10 @@ use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
use serde_json;
use activity_pub::{
Id,
IntoId,
actor::Actor,
inbox::{FromActivity, Deletable},
object::Object
};
use models::{
@ -94,6 +96,29 @@ impl Like {
}
}
impl FromActivity<activity::Like> for Like {
fn from_activity(conn: &PgConnection, like: activity::Like, _actor: Id) -> Like {
let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string());
Like::insert(conn, NewLike {
post_id: post.unwrap().id,
user_id: liker.unwrap().id,
ap_url: like.object_props.id_string().unwrap_or(String::from(""))
})
}
}
impl Deletable for Like {
fn delete_activity(conn: &PgConnection, id: Id) -> bool {
if let Some(like) = Like::find_by_ap_url(conn, id.into()) {
like.delete(conn);
true
} else {
false
}
}
}
impl Object for Like {
fn serialize(&self, conn: &PgConnection) -> serde_json::Value {
json!({

View File

@ -10,6 +10,7 @@ use BASE_URL;
use activity_pub::{
PUBLIC_VISIBILTY, ap_url, Id, IntoId,
actor::Actor,
inbox::FromActivity,
object::Object
};
use models::{
@ -195,6 +196,20 @@ impl Post {
}
}
impl FromActivity<Article> for Post {
fn from_activity(conn: &PgConnection, article: Article, _actor: Id) -> Post {
Post::insert(conn, NewPost {
blog_id: 0, // TODO
slug: String::from(""), // TODO
title: article.object_props.name_string().unwrap(),
content: SafeString::new(&article.object_props.content_string().unwrap()),
published: true,
license: String::from("CC-0"),
ap_url: article.object_props.url_string().unwrap_or(String::from(""))
})
}
}
impl IntoId for Post {
fn into_id(self) -> Id {
Id::new(self.ap_url.clone())

View File

@ -1,8 +1,8 @@
use activitypub::activity;
use activitypub::activity::{Announce, Undo};
use chrono::NaiveDateTime;
use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods};
use activity_pub::{IntoId, actor::Actor, object::Object};
use activity_pub::{Id, IntoId, actor::Actor, inbox::FromActivity, object::Object};
use models::{posts::Post, users::User};
use schema::reshares;
@ -80,17 +80,17 @@ impl Reshare {
Post::get(conn, self.post_id)
}
pub fn delete(&self, conn: &PgConnection) -> activity::Undo {
pub fn delete(&self, conn: &PgConnection) -> Undo {
diesel::delete(self).execute(conn).unwrap();
let mut act = activity::Undo::default();
let mut act = Undo::default();
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
act
}
pub fn into_activity(&self, conn: &PgConnection) -> activity::Announce {
let mut act = activity::Announce::default();
pub fn into_activity(&self, conn: &PgConnection) -> Announce {
let mut act = Announce::default();
act.announce_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
act.announce_props.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap();
act.object_props.set_id_string(self.ap_url.clone()).unwrap();
@ -98,3 +98,15 @@ impl Reshare {
act
}
}
impl FromActivity<Announce> for Reshare {
fn from_activity(conn: &PgConnection, announce: Announce, _actor: Id) -> Reshare {
let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string());
let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string());
Reshare::insert(conn, NewReshare {
post_id: post.unwrap().id,
user_id: user.unwrap().id,
ap_url: announce.object_props.id_string().unwrap_or(String::from(""))
})
}
}

View File

@ -285,6 +285,16 @@ impl User {
users::table.filter(users::id.eq(any(follows))).load::<User>(conn).unwrap()
}
pub fn is_following(&self, conn: &PgConnection, other_id: i32) -> bool {
use schema::follows;
follows::table
.filter(follows::follower_id.eq(other_id))
.filter(follows::following_id.eq(self.id))
.load::<Follow>(conn)
.expect("Couldn't load follow relationship")
.len() > 0
}
pub fn has_liked(&self, conn: &PgConnection, post: &Post) -> bool {
use schema::likes;
use models::likes::Like;

View File

@ -83,7 +83,7 @@ fn create(conn: DbConn, data: Form<NewBlogForm>, user: User) -> Redirect {
is_owner: true
});
Redirect::to(format!("/~/{}/", slug).as_str())
Redirect::to(format!("/~/{}/", slug))
}
#[get("/~/<name>/outbox")]

View File

@ -55,5 +55,5 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form<NewComment
broadcast(&*conn, &user, comment.create_activity(&*conn), user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}/#comment-{}", blog, slug, comment.id).as_ref())
Redirect::to(format!("/~/{}/{}/#comment-{}", blog, slug, comment.id))
}

View File

@ -1,3 +1,4 @@
use gettextrs::gettext;
use rocket::{request::Form, response::Redirect};
use rocket_contrib::{Json, Template};
use serde_json;
@ -38,7 +39,7 @@ fn index(conn: DbConn, user: Option<User>) -> Template {
}
None => {
Template::render("errors/500", json!({
"error_message": "You need to configure your instance before using it."
"error_message": gettext("You need to configure your instance before using it.".to_string())
}))
}
}
@ -78,7 +79,7 @@ fn shared_inbox(conn: DbConn, data: String) -> String {
}
#[get("/nodeinfo")]
fn nodeinfo(conn: DbConn) -> Json {
fn nodeinfo(conn: DbConn) -> Json<serde_json::Value> {
Json(json!({
"version": "2.0",
"software": {

View File

@ -29,7 +29,7 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
broadcast(&*conn, &user, delete_act, user.get_followers(&*conn));
}
Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref())
Redirect::to(format!("/~/{}/{}/", blog, slug))
}
#[get("/~/<blog>/<slug>/like", rank = 2)]

View File

@ -36,7 +36,12 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Temp
json!({
"id": c.id,
"content": c.content,
"author": c.get_author(&*conn)
"author": ({
let author = &c.get_author(&*conn);
let mut json = serde_json::to_value(author).unwrap();
json["fqn"] = serde_json::Value::String(author.get_fqn(&*conn));
json
})
})
}).collect::<Vec<serde_json::Value>>(),
"n_likes": post.get_likes(&*conn).len(),
@ -115,5 +120,5 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
let act = post.create_activity(&*conn);
broadcast(&*conn, &user, act, user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}/", blog_name, slug).as_str())
Redirect::to(format!("/~/{}/{}/", blog_name, slug))
}

View File

@ -29,7 +29,7 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect {
broadcast(&*conn, &user, delete_act, user.get_followers(&*conn));
}
Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref())
Redirect::to(format!("/~/{}/{}/", blog, slug))
}
#[get("/~/<blog>/<slug>/reshare", rank=1)]

View File

@ -1,5 +1,6 @@
use gettextrs::gettext;
use rocket::{
http::{Cookie, Cookies},
http::{Cookie, Cookies, uri::Uri},
response::{Redirect, status::NotFound},
request::{Form,FlashMessage}
};
@ -42,19 +43,19 @@ fn create(conn: DbConn, data: Form<LoginForm>, flash: Option<FlashMessage>, mut
Some(usr) => Ok(usr),
None => match User::find_local(&*conn, form.email_or_name.to_string()) {
Some(usr) => Ok(usr),
None => Err("Invalid username or password")
None => Err(gettext("Invalid username or password"))
}
};
match user {
Ok(usr) => {
if usr.auth(form.password.to_string()) {
cookies.add_private(Cookie::new(AUTH_COOKIE, usr.id.to_string()));
Ok(Redirect::to(&flash
.and_then(|f| if f.name()=="callback" { Some(f.msg().to_owned()) } else { None })
Ok(Redirect::to(Uri::new(flash
.and_then(|f| if f.name() == "callback" { Some(f.msg().to_owned()) } else { None })
.unwrap_or("/".to_owned()))
)
))
} else {
Err(NotFound(String::from("Invalid username or password")))
Err(NotFound(gettext("Invalid username or password")))
}
},
Err(e) => Err(NotFound(String::from(e)))

View File

@ -27,7 +27,7 @@ use utils;
#[get("/me")]
fn me(user: Option<User>) -> Result<Redirect,Flash<Redirect>> {
match user {
Some(user) => Ok(Redirect::to(format!("/@/{}/", user.username).as_ref())),
Some(user) => Ok(Redirect::to(format!("/@/{}/", user.username))),
None => Err(utils::requires_login("", "/me"))
}
}
@ -41,7 +41,10 @@ fn details(name: String, conn: DbConn, account: Option<User>) -> Template {
let n_followers = user.get_followers(&*conn).len();
Template::render("users/details", json!({
"user": serde_json::to_value(user).unwrap(),
"user": serde_json::to_value(user.clone()).unwrap(),
"instance_url": user.get_instance(&*conn).public_domain,
"is_remote": user.instance_id != Instance::local_id(&*conn),
"follows": account.clone().map(|x| x.is_following(&*conn, user.id)).unwrap_or(false),
"account": account,
"recents": recents.into_iter().map(|p| {
json!({
@ -101,7 +104,7 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect {
act.follow_props.set_object_object(user.into_activity(&*conn)).unwrap();
act.object_props.set_id_string(format!("{}/follow/{}", user.ap_url, target.ap_url)).unwrap();
broadcast(&*conn, &user, act, vec![target]);
Redirect::to(format!("/@/{}/", name).as_ref())
Redirect::to(format!("/@/{}/", name))
}
#[get("/@/<name>/follow", rank = 2)]
@ -116,6 +119,9 @@ fn followers(name: String, conn: DbConn, account: Option<User>) -> Template {
Template::render("users/followers", json!({
"user": serde_json::to_value(user.clone()).unwrap(),
"instance_url": user.get_instance(&*conn).public_domain,
"is_remote": user.instance_id != Instance::local_id(&*conn),
"follows": account.clone().map(|x| x.is_following(&*conn, user.id)).unwrap_or(false),
"followers": user.get_followers(&*conn).into_iter().map(|f| {
let fqn = f.get_fqn(&*conn);
let mut json = serde_json::to_value(f).unwrap();
@ -123,7 +129,8 @@ fn followers(name: String, conn: DbConn, account: Option<User>) -> Template {
json
}).collect::<Vec<serde_json::Value>>(),
"account": account,
"is_self": account.map(|a| a.id == user_id).unwrap_or(false)
"is_self": account.map(|a| a.id == user_id).unwrap_or(false),
"n_followers": user.get_followers(&*conn).len()
}))
}
@ -202,7 +209,7 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> {
User::hash_pass(form.password.to_string()),
inst.id
)).update_boxes(&*conn);
Ok(Redirect::to(format!("/@/{}/", data.get().username).as_str()))
Ok(Redirect::to(format!("/@/{}/", data.get().username)))
} else {
Err(String::from("Passwords don't match"))
}

View File

@ -1,5 +1,9 @@
use gettextrs::gettext;
use heck::CamelCase;
use rocket::response::{Redirect, Flash};
use rocket::{
http::uri::Uri,
response::{Redirect, Flash}
};
/// Remove non alphanumeric characters and CamelCase a string
pub fn make_actor_id(name: String) -> String {
@ -12,5 +16,5 @@ pub fn make_actor_id(name: String) -> String {
}
pub fn requires_login(message: &str, url: &str) -> Flash<Redirect> {
Flash::new(Redirect::to(&format!("/login?m={}", message)), "callback", url)
Flash::new(Redirect::to(Uri::new(format!("/login?m={}", gettext(message.to_string())))), "callback", url)
}

View File

@ -2,28 +2,28 @@
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}{% endblock title %} ⋅ Plume</title>
<title>{% block title %}{% endblock title %} ⋅ {{ "Plume" | _ }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/main.css" />
<link rel="stylesheet" href="/static/fontawesome-5.0.10/css/fontawesome-all.min.css" />
</head>
<body>
<header>
<nav id="menu"><a href="#">Menu</a></nav>
<nav id="menu"><a href="#">{{ "Menu" | _ }}</a></nav>
<nav>
<a href="/" class="title">Plume</a>
<a href="/" class="title">{{ "Plume" | _ }}</a>
{% block header %}
{% endblock header %}
</nav>
<nav>
{% if account %}
<a href="/dashboard">Dashboard</a>
<a href="/notifications">Notifications</a>
<a href="/me">My account</a>
<a href="/logout">Log Out</a>
<a href="/dashboard">{{ "Dashboard" | _ }}</a>
<a href="/notifications">{{ "Notifications" | _ }}</a>
<a href="/me">{{ "My account" | _ }}</a>
<a href="/logout">{{ "Log Out" | _ }}</a>
{% else %}
<a href="/login">Log In</a>
<a href="/users/new">Register</a>
<a href="/login">{{ "Log In" | _ }}</a>
<a href="/users/new">{{ "Register" | _ }}</a>
{% endif %}
</nav>
</header>

View File

@ -10,12 +10,12 @@
<p>{{ blog.summary }}</p>
<section>
<h2>Latest articles</h2>
<h2>{{ "Latest articles" | _ }}</h2>
{% if recents | length < 1 %}
<p>No posts to see here yet.</p>
<p>{{ "No posts to see here yet." | _ }}</p>
{% endif %}
{% if is_author %}
<a href="new" class="button inline-block">New article</a>
<a href="new" class="button inline-block">{{ "New article" | _ }}</a>
{% endif %}
<div class="cards">
{% for article in recents %}

View File

@ -1,15 +1,15 @@
{% extends "base" %}
{% block title %}
New blog
{{ "New blog" | _ }}
{% endblock title %}
{% block content %}
<h1>Create a blog</h1>
<h1>{{ "Create a blog" | _ }}</h1>
<form method="post">
<label for="title">Title</label>
<label for="title">{{ "Title" | _ }}</label>
<input type="text" id="title" name="title" />
<input type="submit" value="Create blog" />
<input type="submit" value="{{ "Create blog"}}" />
</form>
{% endblock content %}
| _ {% endblock content %}

View File

@ -1,15 +1,15 @@
{% extends "base" %}
{% block title %}
Comment "{{ post.title }}"
{{ 'Comment "{{ post }}"' | _(post=post.title) }}
{% endblock title %}
{% block content %}
<h1>Comment "{{ post.title }}"</h1>
<h1>{{ 'Comment "{{ post }}"' | _(post=post.title) }}</h1>
<form method="post">
<label for="content">Content</label>
<label for="content">{{ "Content" | _ }}</label>
<textarea id="content" name="content"></textarea>
<input type="submit" value="Comment" />
<input type="submit" value="{{ "Submit comment" | _ }}" />
</form>
{% endblock content %}

View File

@ -1,6 +1,6 @@
{% extends "errors/base" %}
{% block error %}
<h1>Something broke on our side.</h1>
<p>Sorry about that. If you think this is a bug, please report it.</p>
<h1>{{ "Something broke on our side." | _ }}</h1>
<p>{{ "Sorry about that. If you think this is a bug, please report it." | _ }}</p>
{% endblock error %}

View File

@ -1,15 +1,15 @@
{% extends "base" %}
{% block title %}
Configuration
{{ "Configuration" | _ }}
{% endblock title %}
{% block content %}
<h1>Configure your instance</h1>
<h1>{{ "Configure your instance" | _ }}</h1>
<form method="post">
<label for="name">Name</label>
<label for="name">{{ "Name" | _ }}</label>
<input type="text" id="name" name="name" />
<input type="submit" value="Let's go!" />
<input type="submit" value="{{ "Let's go!" | _ }}" />
</form>
{% endblock content %}

View File

@ -6,9 +6,9 @@
{% endblock title %}
{% block content %}
<h1>Welcome on {{ instance.name }}</h1>
<h1>{{ "Welcome on {{ instance_name }}" | _(instance_name=instance.name) }}</h1>
<h2>Latest articles</h2>
<h2>{{ "Latest articles" | _ }}</h2>
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}

View File

@ -1,11 +1,16 @@
{% macro post_card(article) %}
{% if article.author.display_name %}
{% set name = article.author.display_name %}
{% else %}
{% set name = article.author.username %}
{% endif %}
<div class="card">
<h3><a href="{{ article.url }}">{{ article.post.title }}</a></h3>
<main
<p>{{ article.post.content | striptags | truncate(length=200) }}</p>
</main>
<p class="author">
By <a href="/@/{{ article.author.fqn }}/">{{ article.author.display_name }}</a> ⋅ {{ article.date | date(format="%B %e") }}
By <a href="/@/{{ article.author.fqn }}/">{{ name }}</a> ⋅ {{ article.date | date(format="%B %e") }}
</p>
</div>
{% endmacro post_card %}

View File

@ -1,11 +1,11 @@
{% extends "base" %}
{% block title %}
Notifications
{{ "Notifications" | _ }}
{% endblock title %}
{% block content %}
<h1>Notifications</h1>
<h1>{{ "Notifications" | _ }}</h1>
<div class="list">
{% for notification in notifications %}
<div class="card">

View File

@ -9,9 +9,24 @@
{% endblock header %}
{% block content %}
<<<<<<< HEAD
<h1 class="article">{{ post.title }}</h1>
<p class="article-info">
<span class="author">Written by <a href="/@/{{ author.fqn }}/">{{ author.display_name }}</a></span>
{% if author.display_name %}
{% set name = author.display_name %}
{% else %}
{% set name = author.username %}
{% endif %}
<span class="author">{{ "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name }}{{ link_3 }}" | _(
link_1='<a href="/@/',
url=author.fqn,
link_2='/">',
name=name,
link_3="</a>"
)
}}</a></span>
&mdash;
<span class="date">{{ date | date(format="%B %e, %Y") }}</span>
</p>
@ -20,43 +35,49 @@
</article>
<div class="article-meta">
<p>This article is under the {{ post.license }} license.</p>
<p>{{ "This article is under the {{ license }} license." | _(license=post.license) }}</p>
<div class="likes">
<p>
{{ n_likes }} like{{ n_likes | pluralize }}
{{ "{{ count }} likes" | _n(singular="One like", count=n_likes) }}
</p>
{% if has_liked %}
<a class="button liked" href="like">I don't like this anymore</a>
<a class="button liked" href="like">{{ "I don't like this anymore" | _ }}</a>
{% else %}
<a class="button" href="like">Like</a>
<a class="button" href="like">{{ "Add yours" | _ }}</a>
{% endif %}
<p>
{{ n_reshares }} reshare{{ n_reshares | pluralize }}
{{ "{{ count }} reshares" | _n(singular="One reshare", count=n_reshares) }}
</p>
<a class="button" href="reshare">
{% if has_reshared %}
I don't want to reshare this anymore
{{ "I don't want to reshare this anymore" | _ }}
{% else %}
Reshare
{{ "Reshare" | _ }}
{% endif %}
</a>
</div>
<div class="comments">
<h2>Comments</h2>
<a class="button" href="comment?">Comment</a>
<h2>{{ "Comments" | _ }}</h2>
<a class="button" href="comment?">{{ "Comment" | _ }}</a>
<div class="list">
{% for comment in comments %}
{% if comment.author.display_name %}
{% set comment_author_name = comment.author.display_name %}
{% else %}
{% set comment_author_name = comment.author.username %}
{% endif %}
<div class="comment" id="comment-{{ comment.id }}">
<a class="author" href="{{ comment.author.ap_url }}">
<span class="display-name">{{ comment.author.display_name }}</span>
<span class="username">@{{ comment.author.username }}</span>
</a>
<div class="text">{{ comment.content | safe }}</div>
<a class="button" href="comment?responding_to={{ comment.id }}">Respond</a>
<a class="button" href="comment?responding_to={{ comment.id }}">{{ "Respond" | _ }}</a>
</div>
{% endfor %}
</div>

View File

@ -1,18 +1,18 @@
{% extends "base" %}
{% block title %}
New post
{{ "New post" | _ }}
{% endblock title %}
{% block content %}
<h1>Create a post</h1>
<h1>{{ "Create a post" | _ }}</h1>
<form class="new-post" method="post">
<input type="text" class="title" name="title" placeholder="Title">
<textarea name="content" placeholder="Content..."></textarea>
<input type="text" class="title" name="title" placeholder="{{ "Title" | _ }}">
<textarea name="content" placeholder="{{ "Content" | _ }}"></textarea>
<label for="license">License</label>
<label for="license">{{ "License" | _ }}</label>
<input type="text" id="licence" name="license" />
<input type="submit" value="Publish" />
<input type="submit" value="{{ "Publish" | _ }}" />
</form>
{% endblock content %}

View File

@ -1,21 +1,21 @@
{% extends "base" %}
{% block title %}
Login
{{ "Login" | _ }}
{% endblock title %}
{% block content %}
<h1>Login</h1>
<h1>{{ "Login" | _ }}</h1>
{% if message %}
<p>{{ message }}</p>
{% endif %}
<form method="post">
<label for="email_or_name">Username or email</label>
<label for="email_or_name">{{ "Username or email" | _ }}</label>
<input type="text" id="email_or_name" name="email_or_name" />
<label for="password">Password</label>
<label for="password">{{ "Password" | _ }}</label>
<input type="password" id="password" name="password" />
<input type="submit" value="Login" />
<input type="submit" value="{{ "Login" | _ }}" />
</form>
{% endblock content %}

View File

@ -2,18 +2,18 @@
{% import "macros" as macros %}
{% block title %}
Dashboard
{{ "Dashboard" | _ }}
{% endblock title %}
{% block content %}
<h1>Your Dashboard</h1>
<h1>{{ "Your Dashboard" | _ }}</h1>
<section>
<h2>Your Blogs</h2>
<h2>{{ "Your Blogs" | _ }}</h2>
{% if blogs | length < 1 %}
<p>You don't have any blog yet. Create your own, or ask to join one.</p>
<p>{{ "You don't have any blog yet. Create your own, or ask to join one." | _ }}</p>
{% endif %}
<a class="button inline-block" href="/blogs/new">Start a new blog</a>
<a class="button inline-block" href="/blogs/new">{{ "Start a new blog" | _ }}</a>
<div class="list">
{% for blog in blogs %}
<div class="card">

View File

@ -2,37 +2,24 @@
{% import "macros" as macros %}
{% block title %}
{{ user.display_name }}
{% if user.display_name %}
{% set name = user.display_name %}
{% else %}
{% set name = user.username %}
{% endif %}
{{ name }}
{% endblock title %}
{% block content %}
<div>
<h1>
{{ user.display_name }}
{% if user.is_admin %}
<span class="badge">Admin</span>
{% endif %}
{% if is_self %}
<span class="badge">It is you</span>
{% endif %}
</h1>
{% if is_self %}
<a href="edit" class="button inline-block">Edit your profile</a>
{% endif %}
</div>
<div>
<a href="followers">{{ n_followers }} follower{{ n_followers | pluralize }}</a>
</div>
{% include "users/header" %}
<div>
{{ user.summary | safe }}
</div>
{% if recents | length != 0 %}
<h2>Latest articles</h2>
<h2>{{ "Latest articles" | _ }}</h2>
<div class="cards">
{% for article in recents %}
{{ macros::post_card(article=article) }}
@ -41,7 +28,7 @@
{% endif %}
{% if reshares | length != 0 %}
<h2>Recently reshared</h2>
<h2>{{ "Recently reshared" | _ }}</h2>
<div class="cards">
{% for article in reshares %}
{{ macros::post_card(article=article) }}

View File

@ -1,24 +1,24 @@
{% extends "base" %}
{% block title %}
Edit your account
{{ "Edit your account" | _ }}
{% endblock title %}
{% block content %}
<h1>Your Profile</h1>
<h1>{{ "Your Profile" | _ }}</h1>
<form method="post">
<!-- Rocket hack to use various HTTP methods -->
<input type=hidden name="_method" value="put">
<label for="display_name">Display Name</label>
<label for="display_name">{{ "Display Name" | _ }}</label>
<input name="display_name" value="{{ account.display_name }}">
<label for="email">Email</label>
<label for="email">{{ "Email" | _ }}</label>
<input name="email" value="{{ account.email }}">
<label for="summary">Summary</label>
<label for="summary">{{ "Summary" | _ }}</label>
<input name="summary" value="{{ account.summary }}">
<input type="submit" value="Update account"/>
<input type="submit" value="{{ "Update account" | _ }}"/>
</form>
{% endblock content %}

View File

@ -1,28 +1,28 @@
{% extends "base" %}
{% block title %}
{{ user.display_name }}'s Followers
{% if user.display_name %}
{% set name = user.display_name %}
{% else %}
{% set name = user.username %}
{% endif %}
{{ "{{ name }}'s followers" | _(name=name) }}
{% endblock title %}
{% block content %}
<div>
<h1>
{{ user.display_name }}
{% if user.is_admin %}
<span class="badge">Admin</span>
{% endif %}
{% include "users/header" %}
{% if is_self %}
<span class="badge">It is you</span>
{% endif %}
</h1>
</div>
<h2>Followers</h2>
<h2>{{ "Followers" | _ }}</h2>
<div class="cards">
{% for follower in followers %}
{% if follower.display_name %}
{% set follower_name = follower.display_name %}
{% else %}
{% set follower_name = follower.username %}
{% endif %}
<div class="card">
<h3><a href="{{ follower.ap_url }}/">{{ follower.display_name }}</a> &mdash; @{{ follower.fqn }}</h3>
<h3><a href="/@/{{ follower.fqn }}/">{{ follower_name }}</a> &mdash; @{{ follower.fqn }}</h3>
<main><p>{{ follower.summary | safe }}</p></main>
</div>
{% endfor %}

View File

@ -0,0 +1,32 @@
<div>
<h1>
{{ name }}
{% if user.is_admin %}
<span class="badge">{{ "Admin" | _ }}</span>
{% endif %}
{% if is_self %}
<span class="badge">{{ "It is you" | _ }}</span>
{% endif %}
</h1>
{% if is_self %}
<a href="edit" class="button inline-block">{{ "Edit your profile" | _ }}</a>
{% endif %}
{% if is_remote %}
<a class="inline-block button" href="{{ user.ap_url }}">{{ "Open on {{ instance_url }}" | _(instance_url=instance_url) }}</a>
{% endif %}
{% if not is_self and account %}
{% if follows %}
<a href="follow/" class="inline-block button">{{ "Follow" | _ }}</a>
{% else %}
<a href="follow/" class="inline-block button">{{ "Unfollow" | _ }}</a>
{% endif %}
{% endif %}
</div>
<div>
<a href="followers/">{{ "{{ count }} followers" | _n(singular="One follower", count=n_followers) }}</a>
</div>

View File

@ -1,24 +1,24 @@
{% extends "base" %}
{% block title %}
New Account
{{ "New Account" | _ }}
{% endblock title %}
{% block content %}
<h1>Create an account</h1>
<h1>{{ "Create an account" | _ }}</h1>
<form method="post">
<label for="username">Username</label>
<label for="username">{{ "Username" | _ }}</label>
<input type="text" id="username" name="username" />
<label for="email">Email</label>
<label for="email">{{ "Email" | _ }}</label>
<input type="email" id="email" name="email" />
<label for="password">Password</label>
<label for="password">{{ "Password" | _ }}</label>
<input type="password" id="password" name="password" />
<label for="password_confirmation">Password confirmation</label>
<label for="password_confirmation">{{ "Password confirmation" | _ }}</label>
<input type="password" id="password_confirmation" name="password_confirmation" />
<input type="submit" value="Create account" />
<input type="submit" value="{{ "Create account" | _ }}" />
</form>
{% endblock content %}